Import Cobalt 6.14197
diff --git a/src/base/atomicops.h b/src/base/atomicops.h
index 230a601..6d04858 100644
--- a/src/base/atomicops.h
+++ b/src/base/atomicops.h
@@ -31,6 +31,10 @@
 #include "base/basictypes.h"
 #include "build/build_config.h"
 
+#if defined(OS_STARBOARD)
+#include "starboard/atomic.h"
+#endif  // defined(OS_STARBOARD)
+
 #if (defined(OS_WIN) && defined(ARCH_CPU_64_BITS)) || defined(__LB_XB360__) || defined(__LB_XB1__)
 // windows.h #defines this (only on x64). This causes problems because the
 // public API also uses MemoryBarrier at the public name for this fence. So, on
@@ -43,6 +47,12 @@
 namespace base {
 namespace subtle {
 
+#if defined(OS_STARBOARD)
+typedef SbAtomic32 Atomic32;
+#if defined(ARCH_CPU_64_BITS)
+typedef SbAtomic64 Atomic64;
+#endif  // defined(ARCH_CPU_64_BITS)
+#else
 typedef int32 Atomic32;
 #ifdef ARCH_CPU_64_BITS
 // We need to be able to go between Atomic64 and AtomicWord implicitly.  This
@@ -55,6 +65,7 @@
 typedef intptr_t Atomic64;
 #endif
 #endif
+#endif  // defined(OS_STARBOARD)
 
 // Use AtomicWord for a machine-sized pointer.  It will use the Atomic32 or
 // Atomic64 routines below, depending on your architecture.
diff --git a/src/base/callback_helpers.h b/src/base/callback_helpers.h
index 52cb71b..ada0090 100644
--- a/src/base/callback_helpers.h
+++ b/src/base/callback_helpers.h
@@ -25,6 +25,27 @@
   return ret;
 }
 
+inline bool ResetAndRunIfNotNull(base::Closure* cb) {
+  if (cb->is_null()) {
+    return false;
+  }
+  base::Closure ret(*cb);
+  cb->Reset();
+  ret.Run();
+  return true;
+}
+
+template <typename Sig, typename ParamType>
+bool ResetAndRunIfNotNull(base::Callback<Sig>* cb, const ParamType& param) {
+  if (cb->is_null()) {
+    return false;
+  }
+  base::Callback<Sig> ret(*cb);
+  cb->Reset();
+  ret.Run(param);
+  return true;
+}
+
 }  // namespace base
 
 #endif  // BASE_CALLBACK_HELPERS_H_
diff --git a/src/base/file_util_proxy_unittest.cc b/src/base/file_util_proxy_unittest.cc
index e238b32..cfa5023 100644
--- a/src/base/file_util_proxy_unittest.cc
+++ b/src/base/file_util_proxy_unittest.cc
@@ -376,11 +376,17 @@
   ASSERT_EQ(10, info.size);
 
   // Run.
+  PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN |
+      PLATFORM_FILE_WRITE);
   FileUtilProxy::Truncate(
       file_task_runner(),
-      GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE),
+      file,
       7,
       Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+  FileUtilProxy::Flush(
+      file_task_runner(),
+      file,
+      Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
   MessageLoop::current()->Run();
 
   // Verify.
@@ -409,11 +415,17 @@
   ASSERT_EQ(10, info.size);
 
   // Run.
+  PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN |
+      PLATFORM_FILE_WRITE);
   FileUtilProxy::Truncate(
       file_task_runner(),
-      GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE),
+      file,
       53,
       Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
+  FileUtilProxy::Flush(
+      file_task_runner(),
+      file,
+      Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
   MessageLoop::current()->Run();
 
   // Verify.
diff --git a/src/base/file_util_unittest.cc b/src/base/file_util_unittest.cc
index 6f4e111..81fdcc7 100644
--- a/src/base/file_util_unittest.cc
+++ b/src/base/file_util_unittest.cc
@@ -211,7 +211,16 @@
 // Simple function to dump some text into a new file.
 void CreateTextFile(const FilePath& filename,
                     const std::wstring& contents) {
-#if defined(__LB_PS3__)
+#if defined(OS_STARBOARD)
+  SbFile file =
+      SbFileOpen(filename.value().c_str(), kSbFileCreateAlways | kSbFileWrite,
+                 NULL /*out_created*/, NULL /*out_error*/);
+  EXPECT_TRUE(file != kSbFileInvalid);
+  std::string utf8 = WideToUTF8(contents);
+  int bytes_written = SbFileWrite(file, utf8.c_str(), utf8.length());
+  EXPECT_TRUE(bytes_written == utf8.length());
+  SbFileClose(file);
+#elif defined(__LB_PS3__)
   FILE *file = fopen(filename.value().c_str(), "w");
   ASSERT_TRUE(file != NULL);
   fputws(contents.c_str(), file);
@@ -232,18 +241,35 @@
   ASSERT_TRUE(file.is_open());
   file << contents;
   file.close();
-#endif
+#endif  // defined(OS_STARBOARD)
 }
 
 // Simple function to take out some text from a file.
 std::wstring ReadTextFile(const FilePath& filename) {
+#if defined(OS_STARBOARD)
+  SbFile file =
+      SbFileOpen(filename.value().c_str(), kSbFileOpenOnly | kSbFileRead,
+                 NULL /*out_created*/, NULL /*out_error*/);
+  EXPECT_TRUE(file != kSbFileInvalid);
+  char utf8_buffer[64] = {0};
+  int bytes_read =
+      SbFileRead(file, utf8_buffer, SB_ARRAY_SIZE_INT(utf8_buffer));
+  EXPECT_TRUE(bytes_read >= 0);
+  SbFileClose(file);
+  std::wstring result;
+  bool did_convert =
+      UTF8ToWide(utf8_buffer, SbStringGetLength(utf8_buffer), &result);
+  EXPECT_TRUE(did_convert);
+  return result;
+#elif defined(__LB_PS3__)
   wchar_t contents[64];
-#if defined(__LB_PS3__)
   FILE *file = fopen(filename.value().c_str(), "r");
   EXPECT_TRUE(file != NULL);
   fgetws(contents, arraysize(contents), file);
   fclose(file);
+  return std::wstring(contents);
 #elif defined(__LB_WIIU__)
+  wchar_t contents[64];
   char mb_sequence_buffer[64];
   int fd = open(filename.value().c_str(), O_RDONLY);
   EXPECT_TRUE(fd >= 0);
@@ -253,14 +279,16 @@
   const char * mb_sequence = mb_sequence_buffer;
   size_t nbytes = mbsrtowcs(contents, &mb_sequence, sizeof(contents), NULL);
   EXPECT_TRUE(mb_sequence == NULL);
+  return std::wstring(contents);
 #else
+  wchar_t contents[64];
   std::wifstream file;
   file.open(filename.value().c_str());
   EXPECT_TRUE(file.is_open());
   file.getline(contents, arraysize(contents));
   file.close();
-#endif
   return std::wstring(contents);
+#endif
 }
 
 #if defined(OS_WIN)
diff --git a/src/base/i18n/file_util_icu.cc b/src/base/i18n/file_util_icu.cc
index fc07d13..0dd1523 100644
--- a/src/base/i18n/file_util_icu.cc
+++ b/src/base/i18n/file_util_icu.cc
@@ -159,7 +159,7 @@
     // Windows uses UTF-16 encoding for filenames.
     U16_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()),
              code_point);
-#elif defined(OS_POSIX) || defined(OS_STARBAORD)
+#elif defined(OS_POSIX) || defined(OS_STARBOARD)
     // Linux doesn't actually define an encoding. It basically allows anything
     // except for a few special ASCII characters.
     unsigned char cur_char = static_cast<unsigned char>((*file_name)[cursor++]);
diff --git a/src/base/location.cc b/src/base/location.cc
index b6ded2c..fe8581a 100644
--- a/src/base/location.cc
+++ b/src/base/location.cc
@@ -92,9 +92,11 @@
 BASE_EXPORT const void* GetProgramCounter() {
 #if defined(COMPILER_MSVC)
   return _ReturnAddress();
+#elif defined(COMPILER_SNC)
+  return __builtin_return_address(0);
 #elif defined(COMPILER_GCC) && !defined(__LB_PS3__) && !defined(__LB_WIIU__)
   return __builtin_extract_return_addr(__builtin_return_address(0));
-#endif  // COMPILER_GCC
+#endif  // defined(COMPILER_MSVC)
 
   return NULL;
 }
diff --git a/src/base/platform_file_unittest.cc b/src/base/platform_file_unittest.cc
index e73d930..607dc66 100644
--- a/src/base/platform_file_unittest.cc
+++ b/src/base/platform_file_unittest.cc
@@ -197,16 +197,8 @@
 
   // Make sure the file was extended.
   int64 file_size = 0;
-#if defined(__LB_SHELL__)
-  // At this point, the file buffer may not have been flushed to disk,
-  // so reading the new file size from disk is flaky.  Instead, read the file
-  // size from the file descriptor using GetPlatformFileInfo.
-  base::PlatformFileInfo file_info;
-  EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info));
-  file_size = file_info.size;
-#else
+  base::FlushPlatformFile(file);
   EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size));
-#endif
   EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size);
 
   // Make sure the file was zero-padded.
@@ -246,6 +238,7 @@
   const int kExtendedFileLength = 10;
   int64 file_size = 0;
   EXPECT_TRUE(base::TruncatePlatformFile(file, kExtendedFileLength));
+  base::FlushPlatformFile(file);
   EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size));
   EXPECT_EQ(kExtendedFileLength, file_size);
 
@@ -261,6 +254,7 @@
   // Truncate the file.
   const int kTruncatedFileLength = 2;
   EXPECT_TRUE(base::TruncatePlatformFile(file, kTruncatedFileLength));
+  base::FlushPlatformFile(file);
   EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size));
   EXPECT_EQ(kTruncatedFileLength, file_size);
 
diff --git a/src/base/stl_util.h b/src/base/stl_util.h
index c612769..483fd53 100644
--- a/src/base/stl_util.h
+++ b/src/base/stl_util.h
@@ -7,7 +7,7 @@
 
 #include <algorithm>
 #include <functional>
-#if defined(__LB_SHELL__)  // TODO: Starboard?
+#if defined(__LB_SHELL__) || defined(STARBOARD)
 #include <iterator>
 #endif
 #include <string>
diff --git a/src/base/stringprintf_unittest.cc b/src/base/stringprintf_unittest.cc
index d8fdfee..143fc42 100644
--- a/src/base/stringprintf_unittest.cc
+++ b/src/base/stringprintf_unittest.cc
@@ -180,7 +180,7 @@
 // lbshell platforms do not support positional parameters,
 // and lbshell does not use the few parts of chromium that
 // leverage positional parameter support in the OS.
-#if !defined(__LB_SHELL__)
+#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
 // Test that the positional parameters work.
 TEST(StringPrintfTest, PositionalParameters) {
   std::string out;
diff --git a/src/base/test/test_file_util_starboard.cc b/src/base/test/test_file_util_starboard.cc
index ef4cdad..f8127c6 100644
--- a/src/base/test/test_file_util_starboard.cc
+++ b/src/base/test/test_file_util_starboard.cc
@@ -36,7 +36,7 @@
 // Mostly a verbatim copy of CopyDirectory
 bool CopyRecursiveDirNoCache(const FilePath& source_dir,
                              const FilePath& dest_dir) {
-  char top_dir[PATH_MAX];
+  char top_dir[SB_FILE_MAX_PATH];
   if (base::strlcpy(top_dir, source_dir.value().c_str(), arraysize(top_dir)) >=
       arraysize(top_dir)) {
     return false;
diff --git a/src/base/third_party/nspr/prcpucfg_starboard.h b/src/base/third_party/nspr/prcpucfg_starboard.h
index 5bdf40d..3aaa298 100644
--- a/src/base/third_party/nspr/prcpucfg_starboard.h
+++ b/src/base/third_party/nspr/prcpucfg_starboard.h
@@ -55,7 +55,7 @@
 #  define IS_64
 #endif
 
-#if SB_IS(ARCH_PPC) && SB_IS(32_BIT)
+#if SB_IS(ARCH_PPC)
 #define PR_BYTES_PER_BYTE   1
 #define PR_BYTES_PER_SHORT  2
 #define PR_BYTES_PER_INT    4
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index cb23a87..ae86fb4 100644
--- a/src/cobalt/audio/async_audio_decoder.cc
+++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -19,6 +19,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "cobalt/audio/audio_file_reader.h"
+#include "cobalt/audio/audio_helpers.h"
 
 namespace cobalt {
 namespace audio {
@@ -34,11 +35,13 @@
       AudioFileReader::TryCreate(audio_data, size));
 
   if (reader) {
-    decode_finish_callback.Run(
-        reader->sample_rate(), reader->number_of_frames(),
-        reader->number_of_channels(), reader->sample_data());
+    decode_finish_callback.Run(reader->sample_rate(),
+                               reader->number_of_frames(),
+                               reader->number_of_channels(),
+                               reader->sample_data(), reader->sample_type());
   } else {
-    decode_finish_callback.Run(0.f, 0, 0, scoped_array<uint8>());
+    decode_finish_callback.Run(0.f, 0, 0, scoped_array<uint8>(),
+                               kSampleTypeFloat32);
   }
 }
 
diff --git a/src/cobalt/audio/async_audio_decoder.h b/src/cobalt/audio/async_audio_decoder.h
index b78d2a7..3827260 100644
--- a/src/cobalt/audio/async_audio_decoder.h
+++ b/src/cobalt/audio/async_audio_decoder.h
@@ -20,6 +20,7 @@
 #include "base/callback.h"
 #include "base/threading/thread.h"
 #include "cobalt/audio/audio_buffer.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/dom/array_buffer.h"
 
 namespace cobalt {
@@ -27,9 +28,10 @@
 
 class AsyncAudioDecoder {
  public:
-  typedef base::Callback<void(
-      float sample_rate, int32 number_of_frames, int32 number_of_channels,
-      scoped_array<uint8> channels_data)> DecodeFinishCallback;
+  typedef base::Callback<void(float sample_rate, int32 number_of_frames,
+                              int32 number_of_channels,
+                              scoped_array<uint8> channels_data,
+                              SampleType sample_type)> DecodeFinishCallback;
 
   AsyncAudioDecoder();
 
diff --git a/src/cobalt/audio/audio.gyp b/src/cobalt/audio/audio.gyp
index f13d2c3..350a599 100644
--- a/src/cobalt/audio/audio.gyp
+++ b/src/cobalt/audio/audio.gyp
@@ -37,6 +37,7 @@
         'audio_file_reader.h',
         'audio_file_reader_wav.cc',
         'audio_file_reader_wav.h',
+        'audio_helpers.h',
         'audio_node.cc',
         'audio_node.h',
         'audio_node_input.cc',
diff --git a/src/cobalt/audio/audio_buffer.cc b/src/cobalt/audio/audio_buffer.cc
index f6b8d6b..a93eba2 100644
--- a/src/cobalt/audio/audio_buffer.cc
+++ b/src/cobalt/audio/audio_buffer.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/audio/audio_buffer.h"
 
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/dom/dom_exception.h"
 
 namespace cobalt {
@@ -24,35 +25,54 @@
 AudioBuffer::AudioBuffer(script::EnvironmentSettings* settings,
                          float sample_rate, int32 number_of_frames,
                          int32 number_of_channels,
-                         scoped_array<uint8> channels_data)
-    : sample_rate_(sample_rate), length_(number_of_frames) {
+                         scoped_array<uint8> channels_data,
+                         SampleType sample_type)
+    : sample_rate_(sample_rate),
+      length_(number_of_frames),
+      sample_type_(sample_type) {
   DCHECK_GT(sample_rate_, 0);
   DCHECK_GT(length_, 0);
   DCHECK_GT(number_of_channels, 0);
 
   // Create an ArrayBuffer stores sample data from all channels.
+  const uint32 length =
+      number_of_frames * number_of_channels *
+      (sample_type == kSampleTypeFloat32 ? sizeof(float) : sizeof(int16));
   scoped_refptr<dom::ArrayBuffer> array_buffer(new dom::ArrayBuffer(
-      settings, dom::ArrayBuffer::kFromHeap, channels_data.Pass(),
-      number_of_frames * number_of_channels * sizeof(float)));
+      settings, dom::ArrayBuffer::kFromHeap, channels_data.Pass(), length));
 
-  channels_data_.resize(static_cast<size_t>(number_of_channels));
-  // Each channel should have |number_of_frames * sizeof(float)| bytes.  We
-  // create |number_of_channels| of Float32Array as views into the above
-  // ArrayBuffer, this doesn't need any extra allocation.
-  uint32 start_offset_in_bytes = 0;
-  for (int32 i = 0; i < number_of_channels; ++i) {
-    channels_data_[static_cast<size_t>(i)] =
-        new dom::Float32Array(settings, array_buffer, start_offset_in_bytes,
+  // Each channel should have |number_of_frames * size_of_sample_type| bytes.
+  // We create |number_of_channels| of {Float32,Int16}Array as views into the
+  // above ArrayBuffer.  This does not need any extra allocation.
+  if (sample_type == kSampleTypeFloat32) {
+    channels_data_.resize(static_cast<size_t>(number_of_channels));
+    uint32 start_offset_in_bytes = 0;
+    for (int32 i = 0; i < number_of_channels; ++i) {
+      channels_data_[static_cast<size_t>(i)] =
+          new dom::Float32Array(settings, array_buffer, start_offset_in_bytes,
+                                static_cast<uint32>(number_of_frames), NULL);
+      start_offset_in_bytes += number_of_frames * sizeof(float);
+    }
+  } else if (sample_type == kSampleTypeInt16) {
+    channels_int16_data_.resize(static_cast<size_t>(number_of_channels));
+    uint32 start_offset_in_bytes = 0;
+    for (int32 i = 0; i < number_of_channels; ++i) {
+      channels_int16_data_[static_cast<size_t>(i)] =
+          new dom::Int16Array(settings, array_buffer, start_offset_in_bytes,
                               static_cast<uint32>(number_of_frames), NULL);
-    start_offset_in_bytes += number_of_frames * sizeof(float);
+      start_offset_in_bytes += number_of_frames * sizeof(int16);
+    }
+  } else {
+    NOTREACHED();
   }
 }
 
 scoped_refptr<dom::Float32Array> AudioBuffer::GetChannelData(
     uint32 channel_index, script::ExceptionState* exception_state) const {
+  DCHECK_EQ(sample_type_, kSampleTypeFloat32);
   // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR
   // exception MUST be thrown.
-  if (channel_index >= static_cast<uint32>(number_of_channels())) {
+  if (channel_index >= channels_data_.size()) {
     dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
     return NULL;
   }
@@ -60,5 +80,18 @@
   return channels_data_[channel_index];
 }
 
+scoped_refptr<dom::Int16Array> AudioBuffer::GetChannelDataInt16(
+    uint32 channel_index, script::ExceptionState* exception_state) const {
+  DCHECK_EQ(sample_type_, kSampleTypeInt16);
+  // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR
+  // exception MUST be thrown.
+  if (channel_index >= channels_int16_data_.size()) {
+    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
+    return NULL;
+  }
+
+  return channels_int16_data_[channel_index];
+}
+
 }  // namespace audio
 }  // namespace cobalt
diff --git a/src/cobalt/audio/audio_buffer.h b/src/cobalt/audio/audio_buffer.h
index a1fab1e..c58fef5 100644
--- a/src/cobalt/audio/audio_buffer.h
+++ b/src/cobalt/audio/audio_buffer.h
@@ -21,7 +21,9 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"  // For scoped_array
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/dom/float32_array.h"
+#include "cobalt/dom/int16_array.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
 
@@ -45,7 +47,7 @@
   // half.
   AudioBuffer(script::EnvironmentSettings* settings, float sample_rate,
               int32 number_of_frames, int32 number_of_channels,
-              scoped_array<uint8> channels_data);
+              scoped_array<uint8> channels_data, SampleType sample_type);
 
   // Web API: AudioBuffer
   //
@@ -60,22 +62,35 @@
 
   // The number of discrete audio channels.
   int32 number_of_channels() const {
-    return static_cast<int32>(channels_data_.size());
+    if (sample_type_ == kSampleTypeFloat32) {
+      return static_cast<int32>(channels_data_.size());
+    } else if (sample_type_ == kSampleTypeInt16) {
+      return static_cast<int32>(channels_int16_data_.size());
+    } else {
+      NOTREACHED();
+      return 0;
+    }
   }
 
   // Represents the PCM audio data for the specific channel.
   scoped_refptr<dom::Float32Array> GetChannelData(
       uint32 channel_index, script::ExceptionState* exception_state) const;
 
+  scoped_refptr<dom::Int16Array> GetChannelDataInt16(
+      uint32 channel_index, script::ExceptionState* exception_state) const;
+
   DEFINE_WRAPPABLE_TYPE(AudioBuffer);
 
  private:
   typedef std::vector<scoped_refptr<dom::Float32Array> > Float32ArrayVector;
+  typedef std::vector<scoped_refptr<dom::Int16Array> > Int16ArrayVector;
 
   float sample_rate_;
   int32 length_;
+  SampleType sample_type_;
 
   Float32ArrayVector channels_data_;
+  Int16ArrayVector channels_int16_data_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioBuffer);
 };
diff --git a/src/cobalt/audio/audio_buffer_source_node.cc b/src/cobalt/audio/audio_buffer_source_node.cc
index 6f3538f..222ab7a 100644
--- a/src/cobalt/audio/audio_buffer_source_node.cc
+++ b/src/cobalt/audio/audio_buffer_source_node.cc
@@ -94,7 +94,7 @@
 }
 
 scoped_ptr<ShellAudioBus> AudioBufferSourceNode::PassAudioBusFromSource(
-    int32 number_of_frames) {
+    int32 number_of_frames, SampleType sample_type) {
   // This is called by Audio thread.
   audio_lock()->AssertLocked();
 
@@ -108,21 +108,42 @@
 
   size_t channels = static_cast<size_t>(buffer_->number_of_channels());
 
-  std::vector<float*> audio_buffer;
-  for (size_t i = 0; i < channels; ++i) {
-    scoped_refptr<dom::Float32Array> buffer_data =
-        buffer_->GetChannelData(static_cast<uint32>(i), NULL);
-    scoped_refptr<dom::Float32Array> sub_array = buffer_data->Subarray(
-        NULL, read_index_, read_index_ + number_of_frames);
-    audio_buffer.push_back(sub_array->data());
+  if (sample_type == kSampleTypeFloat32) {
+    std::vector<float*> audio_buffer(channels, NULL);
+    for (size_t i = 0; i < channels; ++i) {
+      scoped_refptr<dom::Float32Array> buffer_data =
+          buffer_->GetChannelData(static_cast<uint32>(i), NULL);
+      scoped_refptr<dom::Float32Array> sub_array = buffer_data->Subarray(
+          NULL, read_index_, read_index_ + number_of_frames);
+      audio_buffer[i] = sub_array->data();
+    }
+
+    read_index_ += number_of_frames;
+
+    scoped_ptr<ShellAudioBus> audio_bus(
+        new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer));
+
+    return audio_bus.Pass();
+  } else if (sample_type == kSampleTypeInt16) {
+    std::vector<int16*> audio_buffer(channels, NULL);
+    for (size_t i = 0; i < channels; ++i) {
+      scoped_refptr<dom::Int16Array> buffer_data =
+          buffer_->GetChannelDataInt16(static_cast<uint32>(i), NULL);
+      scoped_refptr<dom::Int16Array> sub_array = buffer_data->Subarray(
+          NULL, read_index_, read_index_ + number_of_frames);
+      audio_buffer[i] = sub_array->data();
+    }
+
+    read_index_ += number_of_frames;
+
+    scoped_ptr<ShellAudioBus> audio_bus(
+        new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer));
+
+    return audio_bus.Pass();
   }
 
-  read_index_ += number_of_frames;
-
-  scoped_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer));
-
-  return audio_bus.Pass();
+  NOTREACHED();
+  return scoped_ptr<ShellAudioBus>();
 }
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_buffer_source_node.h b/src/cobalt/audio/audio_buffer_source_node.h
index ee808d3..ce4f42f 100644
--- a/src/cobalt/audio/audio_buffer_source_node.h
+++ b/src/cobalt/audio/audio_buffer_source_node.h
@@ -72,7 +72,7 @@
   }
 
   scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames) OVERRIDE;
+      int32 number_of_frames, SampleType sample_type) OVERRIDE;
 
   DEFINE_WRAPPABLE_TYPE(AudioBufferSourceNode);
 
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc
index 178d7b6..0962977 100644
--- a/src/cobalt/audio/audio_context.cc
+++ b/src/cobalt/audio/audio_context.cc
@@ -85,13 +85,14 @@
 void AudioContext::DecodeFinish(int callback_id, float sample_rate,
                                 int32 number_of_frames,
                                 int32 number_of_channels,
-                                scoped_array<uint8> channels_data) {
+                                scoped_array<uint8> channels_data,
+                                SampleType sample_type) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
     main_message_loop_->PostTask(
         FROM_HERE,
         base::Bind(&AudioContext::DecodeFinish, weak_this_, callback_id,
                    sample_rate, number_of_frames, number_of_channels,
-                   base::Passed(&channels_data)));
+                   base::Passed(&channels_data), sample_type));
     return;
   }
 
@@ -105,7 +106,7 @@
   if (channels_data) {
     const scoped_refptr<AudioBuffer>& audio_buffer =
         new AudioBuffer(info->env_settings, sample_rate, number_of_frames,
-                        number_of_channels, channels_data.Pass());
+                        number_of_channels, channels_data.Pass(), sample_type);
     info->success_callback.value().Run(audio_buffer);
   } else if (info->error_callback) {
     info->error_callback.value().value().Run();
diff --git a/src/cobalt/audio/audio_context.h b/src/cobalt/audio/audio_context.h
index 5cd34a8..9b3ac6f 100644
--- a/src/cobalt/audio/audio_context.h
+++ b/src/cobalt/audio/audio_context.h
@@ -165,8 +165,8 @@
 
   void DecodeAudioDataInternal(scoped_ptr<DecodeCallbackInfo> info);
   void DecodeFinish(int callback_id, float sample_rate, int32 number_of_frames,
-                    int32 number_of_channels,
-                    scoped_array<uint8> channels_data);
+                    int32 number_of_channels, scoped_array<uint8> channels_data,
+                    SampleType sample_type);
 
   base::WeakPtrFactory<AudioContext> weak_ptr_factory_;
   // We construct a WeakPtr upon AudioContext's construction in order to
diff --git a/src/cobalt/audio/audio_destination_node.h b/src/cobalt/audio/audio_destination_node.h
index 7a760a0..14fab99 100644
--- a/src/cobalt/audio/audio_destination_node.h
+++ b/src/cobalt/audio/audio_destination_node.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "cobalt/audio/audio_device.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node.h"
 #include "media/base/shell_audio_bus.h"
 
@@ -47,8 +48,8 @@
   uint32 max_channel_count() const { return max_channel_count_; }
 
   // From AudioNode.
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 /*number_of_frames*/) OVERRIDE {
+  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32, /*number_of_frames*/
+                                                   SampleType) OVERRIDE {
     NOTREACHED();
     return scoped_ptr<ShellAudioBus>();
   }
diff --git a/src/cobalt/audio/audio_device.cc b/src/cobalt/audio/audio_device.cc
index 8ab8b57..e666970 100644
--- a/src/cobalt/audio/audio_device.cc
+++ b/src/cobalt/audio/audio_device.cc
@@ -18,6 +18,7 @@
 
 #include "base/debug/trace_event.h"
 #include "base/memory/scoped_ptr.h"
+#include "cobalt/audio/audio_helpers.h"
 #if defined(OS_STARBOARD)
 #include "starboard/audio_sink.h"
 #include "starboard/configuration.h"
@@ -46,41 +47,6 @@
 
 #if defined(SB_USE_SB_AUDIO_SINK)
 
-namespace {
-// Helper function to compute the size of the two valid starboard audio sample
-// types.
-size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
-  switch (sample_type) {
-    case kSbMediaAudioSampleTypeFloat32:
-      return sizeof(float);
-    case kSbMediaAudioSampleTypeInt16:
-      return sizeof(int16);
-  }
-  NOTREACHED();
-  return 0u;
-}
-
-const float kMaxInt16AsFloat32 = 32767.0f;
-
-template <typename SourceType, typename DestType>
-DestType ConvertSample(SourceType sample);
-
-template <>
-int16 ConvertSample<float, int16>(float sample) {
-  if (!(-1.0 <= sample && sample <= 1.0)) {
-    DLOG(WARNING) <<
-      "Sample of type float32 must lie on interval [-1.0, 1.0], got: " <<
-      sample << ".";
-  }
-  return static_cast<int16>(sample * kMaxInt16AsFloat32);
-}
-
-template <>
-float ConvertSample<float, float>(float sample) {
-  return sample;
-}
-}  // namespace
-
 class AudioDevice::Impl {
  public:
   Impl(int number_of_channels, RenderCallback* callback);
@@ -98,7 +64,7 @@
 
   void FillOutputAudioBus();
 
-  template <typename OutputType>
+  template <typename InputType, typename OutputType>
   inline void FillOutputAudioBusForType();
 
   int number_of_channels_;
@@ -126,16 +92,14 @@
 // AudioDevice::Impl.
 AudioDevice::Impl::Impl(int number_of_channels, RenderCallback* callback)
     : number_of_channels_(number_of_channels),
-      output_sample_type_(
-          SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)
-              ? kSbMediaAudioSampleTypeFloat32
-              : kSbMediaAudioSampleTypeInt16),
+      output_sample_type_(GetPreferredOutputStarboardSampleType()),
       render_callback_(callback),
       input_audio_bus_(static_cast<size_t>(number_of_channels),
                        static_cast<size_t>(kRenderBufferSizeFrames),
-                       ShellAudioBus::kFloat32, ShellAudioBus::kPlanar),
-      output_frame_buffer_(new uint8[kFramesPerChannel * number_of_channels_ *
-                                       GetSampleSize(output_sample_type_)]),
+                       GetPreferredOutputSampleType(), ShellAudioBus::kPlanar),
+      output_frame_buffer_(
+          new uint8[kFramesPerChannel * number_of_channels_ *
+                    GetStarboardSampleTypeSize(output_sample_type_)]),
       frames_rendered_(0),
       frames_consumed_(0),
       was_silence_last_update_(false),
@@ -147,7 +111,7 @@
       kSbMediaAudioFrameStorageTypeInterleaved))
       << "Only interleaved frame storage is supported.";
   DCHECK(SbAudioSinkIsAudioSampleTypeSupported(output_sample_type_))
-      << "Output sample type " << output_sample_type_ << " is not supported";
+      << "Output sample type " << output_sample_type_ << " is not supported.";
 
   frame_buffers_[0] = output_frame_buffer_.get();
   audio_sink_ = SbAudioSinkCreate(
@@ -231,7 +195,7 @@
   frames_consumed_ += frames_consumed;
 }
 
-template <typename OutputType>
+template <typename InputType, typename OutputType>
 inline void AudioDevice::Impl::FillOutputAudioBusForType() {
   // Determine the offset into the audio bus that represents the tail of
   // buffered data.
@@ -242,8 +206,10 @@
   output_buffer += channel_offset * number_of_channels_;
   for (size_t frame = 0; frame < kRenderBufferSizeFrames; ++frame) {
     for (size_t channel = 0; channel < input_audio_bus_.channels(); ++channel) {
-      *output_buffer = ConvertSample<float, OutputType>(
-          input_audio_bus_.GetFloat32Sample(channel, frame));
+      *output_buffer = ConvertSample<InputType, OutputType>(
+          input_audio_bus_
+              .GetSampleForType<InputType, media::ShellAudioBus::kPlanar>(
+                  channel, frame));
       ++output_buffer;
     }
   }
@@ -251,10 +217,20 @@
 
 void AudioDevice::Impl::FillOutputAudioBus() {
   TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::FillOutputAudioBus()");
-  if (output_sample_type_ == kSbMediaAudioSampleTypeFloat32) {
-    FillOutputAudioBusForType<float>();
-  } else if (output_sample_type_ == kSbMediaAudioSampleTypeInt16) {
-    FillOutputAudioBusForType<int16>();
+
+  const bool is_input_int16 =
+      input_audio_bus_.sample_type() == media::ShellAudioBus::kInt16;
+  const bool is_output_int16 =
+      output_sample_type_ == kSbMediaAudioSampleTypeInt16;
+
+  if (is_input_int16 && is_output_int16) {
+    FillOutputAudioBusForType<int16, int16>();
+  } else if (!is_input_int16 && is_output_int16) {
+    FillOutputAudioBusForType<float, int16>();
+  } else if (is_input_int16 && !is_output_int16) {
+    FillOutputAudioBusForType<int16, float>();
+  } else if (!is_input_int16 && !is_output_int16) {
+    FillOutputAudioBusForType<float, float>();
   } else {
     NOTREACHED();
   }
@@ -329,7 +305,7 @@
                              channel_layout, GetAudioHardwareSampleRate(),
                              bytes_per_sample * 8, kRenderBufferSizeFrames);
 
-  // Create 1 channel audio bus due to we only support interleaved.
+  // Create 1 channel audio bus since we only support interleaved.
   output_audio_bus_ =
       AudioBus::Create(1, kFramesPerChannel * number_of_channels);
 
diff --git a/src/cobalt/audio/audio_file_reader.h b/src/cobalt/audio/audio_file_reader.h
index 4897878..e1eacca 100644
--- a/src/cobalt/audio/audio_file_reader.h
+++ b/src/cobalt/audio/audio_file_reader.h
@@ -19,6 +19,8 @@
 
 #include "base/memory/scoped_ptr.h"  // For scoped_array
 
+#include "cobalt/audio/audio_helpers.h"
+
 namespace cobalt {
 namespace audio {
 
@@ -35,6 +37,7 @@
   virtual float sample_rate() const = 0;
   virtual int32 number_of_frames() const = 0;
   virtual int32 number_of_channels() const = 0;
+  virtual SampleType sample_type() const = 0;
 };
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader_wav.cc b/src/cobalt/audio/audio_file_reader_wav.cc
index 322fa0d..fad6ef0 100644
--- a/src/cobalt/audio/audio_file_reader_wav.cc
+++ b/src/cobalt/audio/audio_file_reader_wav.cc
@@ -175,32 +175,52 @@
   const uint8* data_samples = data + offset;
 
   // Set number of frames based on size of data chunk.
-  int32 bytes_per_src_sample =
+  const int32 bytes_per_src_sample =
       static_cast<int32>(is_sample_in_float ? sizeof(float) : sizeof(int16));
   number_of_frames_ =
       static_cast<int32>(size / (bytes_per_src_sample * number_of_channels_));
+  sample_type_ = GetPreferredOutputSampleType();
+  const int32 bytes_per_dest_sample =
+      static_cast<int32>(GetSampleTypeSize(sample_type_));
+  const bool is_dest_float = sample_type_ == kSampleTypeFloat32;
 
-  // We always store audio samples in float.
-  sample_data_.reset(
-      new uint8[number_of_frames_ * number_of_channels_ * sizeof(float)]);
+  // We store audio samples in the current platform's preferred format.
+  sample_data_.reset(new uint8[static_cast<size_t>(
+      number_of_frames_ * number_of_channels_ * bytes_per_dest_sample)]);
 
-  // The source data is stored interleaved.  We need to convert it into planar.
-  float* dest_sample = reinterpret_cast<float*>(sample_data_.get());
+  // Here we handle all 4 possible conversion cases.  Also note that the
+  // source data is stored interleaved, and that need to convert it to planar.
+  uint8* dest_sample = sample_data_.get();
   for (int32 i = 0; i < number_of_channels_; ++i) {
     const uint8* src_samples = data_samples + i * bytes_per_src_sample;
 
     for (int32 j = 0; j < number_of_frames_; ++j) {
-      float sample;
-      if (is_sample_in_float) {
-        uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
-        sample = bit_cast<float>(sample_as_uint32);
+      if (is_dest_float) {
+        float sample;
+        if (is_sample_in_float) {
+          uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
+          sample = bit_cast<float>(sample_as_uint32);
+        } else {
+          uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples);
+          int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned);
+          sample = ConvertSample<int16, float>(sample_pcm);
+        }
+        reinterpret_cast<float*>(dest_sample)[i * number_of_frames_ + j] =
+            sample;
+        src_samples += bytes_per_src_sample * number_of_channels_;
       } else {
-        uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples);
-        int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned);
-        sample = static_cast<float>(sample_pcm) / 32768.0f;
+        int16 sample;
+        if (is_sample_in_float) {
+          uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
+          float value = bit_cast<float>(sample_as_uint32);
+          sample = ConvertSample<float, int16>(value);
+        } else {
+          sample = bit_cast<int16>(load_uint16_little_endian(src_samples));
+        }
+        reinterpret_cast<int16*>(dest_sample)[i * number_of_frames_ + j] =
+            sample;
+        src_samples += bytes_per_src_sample * number_of_channels_;
       }
-      dest_sample[i * number_of_frames_ + j] = sample;
-      src_samples += bytes_per_src_sample * number_of_channels_;
     }
   }
 
diff --git a/src/cobalt/audio/audio_file_reader_wav.h b/src/cobalt/audio/audio_file_reader_wav.h
index cab1a1d..06e4883 100644
--- a/src/cobalt/audio/audio_file_reader_wav.h
+++ b/src/cobalt/audio/audio_file_reader_wav.h
@@ -18,6 +18,7 @@
 #define COBALT_AUDIO_AUDIO_FILE_READER_WAV_H_
 
 #include "cobalt/audio/audio_file_reader.h"
+#include "cobalt/audio/audio_helpers.h"
 
 namespace cobalt {
 namespace audio {
@@ -32,6 +33,7 @@
   float sample_rate() const OVERRIDE { return sample_rate_; }
   int32 number_of_frames() const OVERRIDE { return number_of_frames_; }
   int32 number_of_channels() const OVERRIDE { return number_of_channels_; }
+  SampleType sample_type() const OVERRIDE { return sample_type_; }
 
  private:
   AudioFileReaderWAV(const uint8* data, size_t size);
@@ -49,6 +51,7 @@
   float sample_rate_;
   int32 number_of_frames_;
   int32 number_of_channels_;
+  SampleType sample_type_;
 };
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_helpers.h b/src/cobalt/audio/audio_helpers.h
new file mode 100644
index 0000000..2f0c06a
--- /dev/null
+++ b/src/cobalt/audio/audio_helpers.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_AUDIO_AUDIO_HELPERS_H_
+#define COBALT_AUDIO_AUDIO_HELPERS_H_
+
+#include "media/base/shell_audio_bus.h"
+
+#if defined(OS_STARBOARD)
+#include "starboard/audio_sink.h"
+#include "starboard/media.h"
+#endif
+
+namespace cobalt {
+namespace audio {
+
+typedef ::media::ShellAudioBus::SampleType SampleType;
+const SampleType kSampleTypeInt16 = ::media::ShellAudioBus::kInt16;
+const SampleType kSampleTypeFloat32 = ::media::ShellAudioBus::kFloat32;
+
+const float kMaxInt16AsFloat32 = 32767.0f;
+
+#if defined(OS_STARBOARD)
+// Get the size in bytes of an SbMediaAudioSampleType.
+inline size_t GetStarboardSampleTypeSize(SbMediaAudioSampleType sample_type) {
+  switch (sample_type) {
+    case kSbMediaAudioSampleTypeFloat32:
+      return sizeof(float);
+    case kSbMediaAudioSampleTypeInt16:
+      return sizeof(int16);
+  }
+  NOTREACHED();
+  return 0u;
+}
+#endif
+
+// Get the size in bytes of an internal sample type, which is an alias for
+// media::ShellAudioBus::SampleType.
+inline size_t GetSampleTypeSize(SampleType sample_type) {
+  switch (sample_type) {
+    case kSampleTypeInt16:
+      return sizeof(int16);
+    case kSampleTypeFloat32:
+      return sizeof(float);
+  }
+  NOTREACHED();
+  return 0u;
+}
+
+// Get the sample type that we would prefer to output in using starboard, as
+// an internal SampleType.  If we are not running on starboard or using the
+// starboard media pipeline, then the preferred sample type is always float32.
+inline SampleType GetPreferredOutputSampleType() {
+#if defined(OS_STARBOARD)
+#if SB_CAN(MEDIA_USE_STARBOARD_PIPELINE)
+  if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) {
+    return kSampleTypeFloat32;
+  }
+  DCHECK(SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeInt16))
+      << "At least one starboard audio sample type must be supported if using "
+         "starboard media pipeline.";
+  return kSampleTypeInt16;
+#else   // SB_CAN(MEDIA_USE_STARBOARD_PIPELINE)
+  return kSampleTypeFloat32;
+#endif  // SB_CAN(MEDIA_USE_STARBOARD_PIPELINE)
+#else   // defined(OS_STARBOARD)
+  return kSampleTypeFloat32;
+#endif  // defined(OS_STARBOARD)
+}
+
+#if defined(OS_STARBOARD)
+// The same as GetPreferredOutputSampleType, only as an SbMediaAudioSample
+// rather than an internal SampleType.
+inline SbMediaAudioSampleType GetPreferredOutputStarboardSampleType() {
+  if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) {
+    return kSbMediaAudioSampleTypeFloat32;
+  }
+  return kSbMediaAudioSampleTypeInt16;
+}
+#endif
+
+// Convert a sample value from {int16,float} to {int16,float}.
+template <typename SourceType, typename DestType>
+inline DestType ConvertSample(SourceType sample);
+
+template <>
+inline int16 ConvertSample<float, int16>(float sample) {
+  if (!(-1.0 <= sample && sample <= 1.0)) {
+    DLOG(WARNING)
+        << "Sample of type float32 must lie on interval [-1.0, 1.0], got: "
+        << sample << ".";
+  }
+  return static_cast<int16>(sample * kMaxInt16AsFloat32);
+}
+
+template <>
+inline float ConvertSample<int16, float>(int16 sample) {
+  float int16_sample_as_float = static_cast<float>(sample);
+  return int16_sample_as_float / kMaxInt16AsFloat32;
+}
+
+template <>
+inline float ConvertSample<float, float>(float sample) {
+  return sample;
+}
+
+template <>
+inline int16 ConvertSample<int16, int16>(int16 sample) {
+  return sample;
+}
+
+}  // namespace audio
+}  // namespace cobalt
+
+#endif  // COBALT_AUDIO_AUDIO_HELPERS_H_
diff --git a/src/cobalt/audio/audio_node.h b/src/cobalt/audio/audio_node.h
index 11f4ae4..9564898 100644
--- a/src/cobalt/audio/audio_node.h
+++ b/src/cobalt/audio/audio_node.h
@@ -20,6 +20,7 @@
 #include <string>
 #include <vector>
 
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node_input.h"
 #include "cobalt/audio/audio_node_output.h"
 #include "cobalt/dom/dom_exception.h"
@@ -112,7 +113,7 @@
 
   // TODO: Support wrapping ShellAudioBus into another ShellAudioBus.
   virtual scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames) = 0;
+      int32 number_of_frames, SampleType sample_type) = 0;
 
   AudioLock* audio_lock() const { return audio_lock_.get(); }
 
diff --git a/src/cobalt/audio/audio_node_input.cc b/src/cobalt/audio/audio_node_input.cc
index 9be0c32..b0c8597 100644
--- a/src/cobalt/audio/audio_node_input.cc
+++ b/src/cobalt/audio/audio_node_input.cc
@@ -243,7 +243,8 @@
   for (std::set<AudioNodeOutput*>::iterator iter = outputs_.begin();
        iter != outputs_.end(); ++iter) {
     scoped_ptr<ShellAudioBus> audio_bus = (*iter)->PassAudioBusFromSource(
-        static_cast<int32>(output_audio_bus->frames()));
+        static_cast<int32>(output_audio_bus->frames()),
+        output_audio_bus->sample_type());
 
     if (audio_bus) {
       MixAudioBuffer(owner_node_->channel_interpretation(), audio_bus.get(),
diff --git a/src/cobalt/audio/audio_node_input_output_test.cc b/src/cobalt/audio/audio_node_input_output_test.cc
index b72579e..095217f 100644
--- a/src/cobalt/audio/audio_node_input_output_test.cc
+++ b/src/cobalt/audio/audio_node_input_output_test.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include "cobalt/audio/audio_context.h"
-
 #include "cobalt/audio/audio_buffer_source_node.h"
+#include "cobalt/audio/audio_context.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -37,8 +37,8 @@
   }
 
   // From AudioNode.
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 /*number_of_frames*/) OVERRIDE {
+  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32, /*number_of_frames*/
+                                                   SampleType) OVERRIDE {
     NOTREACHED();
     return scoped_ptr<ShellAudioBus>();
   }
@@ -58,7 +58,8 @@
   scoped_refptr<AudioBufferSourceNode> source(new AudioBufferSourceNode(NULL));
   scoped_refptr<AudioBuffer> buffer(
       new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames),
-                      static_cast<int32>(num_of_src_channel), src_data.Pass()));
+                      static_cast<int32>(num_of_src_channel), src_data.Pass(),
+                      GetPreferredOutputSampleType()));
   source->set_buffer(buffer);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
@@ -625,9 +626,10 @@
   memcpy(src_buffer_1, src_data_in_float_1, 200 * sizeof(uint8));
   scoped_refptr<AudioBufferSourceNode> source_1(
       new AudioBufferSourceNode(NULL));
-  scoped_refptr<AudioBuffer> buffer_1(new AudioBuffer(
-      NULL, 44100, static_cast<int32>(num_of_frames_1),
-      static_cast<int32>(num_of_src_channel), src_data_1.Pass()));
+  scoped_refptr<AudioBuffer> buffer_1(
+      new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_1),
+                      static_cast<int32>(num_of_src_channel), src_data_1.Pass(),
+                      GetPreferredOutputSampleType()));
   source_1->set_buffer(buffer_1);
 
   size_t num_of_frames_2 = 50;
@@ -642,9 +644,10 @@
   memcpy(src_buffer_2, src_data_in_float_2, 400 * sizeof(uint8));
   scoped_refptr<AudioBufferSourceNode> source_2(
       new AudioBufferSourceNode(NULL));
-  scoped_refptr<AudioBuffer> buffer_2(new AudioBuffer(
-      NULL, 44100, static_cast<int32>(num_of_frames_2),
-      static_cast<int32>(num_of_src_channel), src_data_2.Pass()));
+  scoped_refptr<AudioBuffer> buffer_2(
+      new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_2),
+                      static_cast<int32>(num_of_src_channel), src_data_2.Pass(),
+                      GetPreferredOutputSampleType()));
   source_2->set_buffer(buffer_2);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
diff --git a/src/cobalt/audio/audio_node_output.cc b/src/cobalt/audio/audio_node_output.cc
index d2ce36c..747bc01 100644
--- a/src/cobalt/audio/audio_node_output.cc
+++ b/src/cobalt/audio/audio_node_output.cc
@@ -58,12 +58,13 @@
 }
 
 scoped_ptr<ShellAudioBus> AudioNodeOutput::PassAudioBusFromSource(
-    int32 number_of_frames) {
+    int32 number_of_frames, SampleType sample_type) {
   // This is called by Audio thread.
   owner_node_->audio_lock()->AssertLocked();
 
   // Pull audio buffer from its owner node.
-  return owner_node_->PassAudioBusFromSource(number_of_frames).Pass();
+  return owner_node_->PassAudioBusFromSource(number_of_frames, sample_type)
+      .Pass();
 }
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_node_output.h b/src/cobalt/audio/audio_node_output.h
index f01b274..decd54c 100644
--- a/src/cobalt/audio/audio_node_output.h
+++ b/src/cobalt/audio/audio_node_output.h
@@ -22,6 +22,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/audio/audio_buffer.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "media/base/shell_audio_bus.h"
 
 namespace cobalt {
@@ -44,7 +45,8 @@
 
   void DisconnectAll();
 
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames);
+  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type);
 
  private:
   AudioNode* const owner_node_;
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
index 20bae1a..38699b2 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
@@ -222,6 +222,15 @@
 JSBool set_arbitraryProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ArbitraryInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
index 67e7e5f..2599232 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
@@ -220,6 +220,15 @@
 JSBool set_booleanProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<BooleanTypeTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
index 8ebf68c..fce394f 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
@@ -224,6 +224,15 @@
 JSBool set_callbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<CallbackFunctionInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -277,6 +286,15 @@
 JSBool set_nullableCallbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<CallbackFunctionInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
index e510b12..468faf3 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
@@ -224,6 +224,15 @@
 JSBool set_callbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<CallbackInterfaceInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
index 12fda2f..2610e00 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
@@ -223,6 +223,15 @@
 JSBool set_enabledAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ConditionalInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -278,6 +287,15 @@
 JSBool set_disabledAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ConditionalInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
index 4524287..7092da5 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
@@ -220,6 +220,15 @@
 JSBool set_property(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DOMStringTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -335,6 +344,15 @@
 JSBool set_nullIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DOMStringTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -388,6 +406,15 @@
 JSBool set_undefinedIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DOMStringTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -441,6 +468,15 @@
 JSBool set_nullableUndefinedIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DOMStringTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
index 647ef55..1c88411 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
@@ -436,6 +436,15 @@
 JSBool set_propertyOnDerivedClass(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DerivedGetterSetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
index 5fb88e5..5e3f995 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
@@ -222,6 +222,15 @@
 JSBool set_disabledProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DisabledInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
index ea07296..38c99f1 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
@@ -231,6 +231,15 @@
 JSBool set_enumProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<EnumerationInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
index b23ae9f..6ab9aab 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
@@ -222,6 +222,15 @@
 JSBool set_attributeThrowsException(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ExceptionsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
index df1b224..9ba778e 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
@@ -233,6 +233,15 @@
 JSBool set_previous(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<GarbageCollectionTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -286,6 +295,15 @@
 JSBool set_next(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<GarbageCollectionTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
index 7f0e6b7..e027bc0 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
@@ -436,6 +436,15 @@
 JSBool set_propertyOnBaseClass(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NamedIndexedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
index c7945ee..9b732fe 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
@@ -224,6 +224,15 @@
 JSBool set_nestedForwardingAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NestedPutForwardsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
index a2d5622..e7533d8 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
@@ -224,6 +224,15 @@
 JSBool set_nullableBooleanProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NullableTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -277,6 +286,15 @@
 JSBool set_nullableNumericProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NullableTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -330,6 +348,15 @@
 JSBool set_nullableStringProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NullableTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -383,6 +410,15 @@
 JSBool set_nullableObjectProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NullableTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
index c64b5d9..eb604d8 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
@@ -220,6 +220,15 @@
 JSBool set_byteProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -273,6 +282,15 @@
 JSBool set_octetProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -326,6 +344,15 @@
 JSBool set_shortProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -379,6 +406,15 @@
 JSBool set_unsignedShortProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -432,6 +468,15 @@
 JSBool set_longProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -485,6 +530,15 @@
 JSBool set_unsignedLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -538,6 +592,15 @@
 JSBool set_longLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -591,6 +654,15 @@
 JSBool set_unsignedLongLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -644,6 +716,15 @@
 JSBool set_doubleProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -697,6 +778,15 @@
 JSBool set_unrestrictedDoubleProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NumericTypesTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
index d25a5b1..77631f6 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
@@ -232,6 +232,15 @@
 JSBool set_arbitraryObject(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ObjectTypeBindingsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -316,6 +325,15 @@
 JSBool set_derivedInterface(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ObjectTypeBindingsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -369,6 +387,15 @@
 JSBool set_objectProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ObjectTypeBindingsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
index 325c6fd..dfc7ffa 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
@@ -224,6 +224,15 @@
 JSBool set_forwardingAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<PutForwardsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
index 30cfd0a..428506c 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
@@ -220,6 +220,15 @@
 JSBool set_theStringifierAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<StringifierAttributeInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
index a81efa7..e6cf73b 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
@@ -228,6 +228,15 @@
 JSBool set_unionProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<UnionTypesInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -281,6 +290,15 @@
 JSBool set_unionWithNullableMemberProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<UnionTypesInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -334,6 +352,15 @@
 JSBool set_nullableUnionProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<UnionTypesInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -387,6 +414,15 @@
 JSBool set_unionBaseProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<UnionTypesInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
index 673aed8..212ecca 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
@@ -413,6 +413,15 @@
 JSBool set_windowProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<Window>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
diff --git a/src/cobalt/bindings/mozjs/templates/interface.cc.template b/src/cobalt/bindings/mozjs/templates/interface.cc.template
index 2dfc9eb..3307b67 100644
--- a/src/cobalt/bindings/mozjs/templates/interface.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/interface.cc.template
@@ -485,6 +485,7 @@
 JSBool set_{{attribute.idl_name}}(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JSBool strict, JS::MutableHandleValue vp) {
+{{ check_if_object_implements_interface() }}
 {{ nonstatic_function_prologue(impl_class)}}
 {% endif %} {#- attribute.is_static #}
 {{ set_attribute_implementation(attribute, impl_class) -}}
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 9385cc0..170907c 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -222,6 +222,8 @@
                           &options->scratch_surface_cache_size_in_bytes);
   SetIntegerIfSwitchIsSet(browser::switches::kSkiaCacheSizeInBytes,
                           &options->skia_cache_size_in_bytes);
+  SetIntegerIfSwitchIsSet(browser::switches::kSoftwareSurfaceCacheSizeInBytes,
+                          &options->software_surface_cache_size_in_bytes);
 }
 
 void ApplyCommandLineSettingsToWebModuleOptions(WebModule::Options* options) {
diff --git a/src/cobalt/browser/browser_bindings.gyp b/src/cobalt/browser/browser_bindings.gyp
index 329d8e2..e73cd8b 100644
--- a/src/cobalt/browser/browser_bindings.gyp
+++ b/src/cobalt/browser/browser_bindings.gyp
@@ -179,6 +179,8 @@
         '../web_animations/KeyframeEffectReadOnly.idl',
 
         '../webdriver/ScriptExecutor.idl',
+        '../webdriver/ScriptExecutorParams.idl',
+        '../webdriver/ScriptExecutorResult.idl',
 
         '../xhr/XMLHttpRequest.idl',
         '../xhr/XMLHttpRequestEventTarget.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 72f1dd8..2f8beaf 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -273,6 +273,13 @@
       array_buffer_allocator_.get();
   options.dom_settings_options.array_buffer_cache = array_buffer_cache_.get();
 #endif  // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
+#if defined(ENABLE_FAKE_MICROPHONE)
+  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kFakeMicrophone) ||
+      CommandLine::ForCurrentProcess()->HasSwitch(switches::kInputFuzzer)) {
+    options.dom_settings_options.enable_fake_microphone = true;
+  }
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+
   options.image_cache_capacity_multiplier_when_playing_video =
       COBALT_IMAGE_CACHE_CAPACITY_MULTIPLIER_WHEN_PLAYING_VIDEO;
   web_module_.reset(new WebModule(
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 73b734a..6332a9d 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -45,6 +45,10 @@
 // Additional base directory for accessing web files via file://.
 const char kExtraWebFileDir[] = "web_file_path";
 
+// If this flag is set, fake microphone will be used to mock the user voice
+// input.
+const char kFakeMicrophone[] = "fake_microphone";
+
 // Setting this switch causes all certificate errors to be ignored.
 const char kIgnoreCertificateErrors[] = "ignore_certificate_errors";
 
@@ -129,6 +133,13 @@
 // setting may affect GPU memory usage.
 const char kSkiaCacheSizeInBytes[] = "skia_cache_size_in_bytes";
 
+// Only relevant if you are using the Blitter API.
+// Determines the capacity of the software surface cache, which is used to
+// cache all surfaces that are rendered via a software rasterizer to avoid
+// re-rendering them.
+const char kSoftwareSurfaceCacheSizeInBytes[] =
+    "software_surface_cache_size_in_bytes";
+
 // Determines the capacity of the surface cache.  The surface cache tracks which
 // render tree nodes are being re-used across frames and stores the nodes that
 // are most CPU-expensive to render into surfaces.  While it depends on the
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index fe02027..a9b3d5f 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -29,6 +29,7 @@
 extern const char kDisableWebmVp9[];
 extern const char kEnableWebDriver[];
 extern const char kExtraWebFileDir[];
+extern const char kFakeMicrophone[];
 extern const char kIgnoreCertificateErrors[];
 extern const char kInputFuzzer[];
 extern const char kMinLogLevel[];
@@ -50,6 +51,7 @@
 extern const char kRemoteTypefaceCacheSizeInBytes[];
 extern const char kScratchSurfaceCacheSizeInBytes[];
 extern const char kSkiaCacheSizeInBytes[];
+extern const char kSoftwareSurfaceCacheSizeInBytes[];
 extern const char kSurfaceCacheSizeInBytes[];
 extern const char kViewport[];
 
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index a23cdb6..c21d48b 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -29,8 +29,10 @@
 #include "cobalt/browser/switches.h"
 #include "cobalt/browser/web_module_stat_tracker.h"
 #include "cobalt/debug/debug_server_module.h"
+#include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/storage.h"
+#include "cobalt/dom/url.h"
 #include "cobalt/h5vcc/h5vcc.h"
 #include "cobalt/storage/storage_manager.h"
 
@@ -111,9 +113,8 @@
 #endif  // ENABLE_DEBUG_CONSOLE
 
   // Called to inject a keyboard event into the web module.
-  void InjectKeyboardEvent(
-      scoped_refptr<dom::Element> element,
-      const dom::KeyboardEvent::Data& event);
+  void InjectKeyboardEvent(scoped_refptr<dom::Element> element,
+                           const dom::KeyboardEvent::Data& event);
 
   // Called to execute JavaScript in this WebModule. Sets the |result|
   // output parameter and signals |got_result|.
@@ -239,6 +240,9 @@
   // Object to register and retrieve MediaSource object with a string key.
   scoped_ptr<dom::MediaSource::Registry> media_source_registry_;
 
+  // Object to register and retrieve Blob objects with a string key.
+  scoped_ptr<dom::Blob::Registry> blob_registry_;
+
   // The Window object wraps all DOM-related components.
   scoped_refptr<dom::Window> window_;
 
@@ -311,8 +315,11 @@
       base::Bind(&WebModule::Impl::OnError, base::Unretained(this))));
   DCHECK(dom_parser_);
 
+  blob_registry_.reset(new dom::Blob::Registry);
+
   fetcher_factory_.reset(new loader::FetcherFactory(
-      data.network_module, data.options.extra_web_file_dir));
+      data.network_module, data.options.extra_web_file_dir,
+      dom::URL::MakeBlobResolverCallback(blob_registry_.get())));
   DCHECK(fetcher_factory_);
 
   loader_factory_.reset(
@@ -390,8 +397,9 @@
 
   environment_settings_.reset(new dom::DOMSettings(
       kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module, window_,
-      media_source_registry_.get(), data.media_module, javascript_engine_.get(),
-      global_environment_.get(), data.options.dom_settings_options));
+      media_source_registry_.get(), blob_registry_.get(), data.media_module,
+      javascript_engine_.get(), global_environment_.get(),
+      data.options.dom_settings_options));
   DCHECK(environment_settings_);
 
   global_environment_->CreateGlobalObject(window_, environment_settings_.get());
@@ -458,6 +466,7 @@
   window_weak_.reset();
   window_ = NULL;
   media_source_registry_.reset();
+  blob_registry_.reset();
   script_runner_.reset();
   execution_state_.reset();
   global_environment_ = NULL;
@@ -757,20 +766,17 @@
   impl_.reset(new Impl(data));
 }
 
-void WebModule::InjectKeyboardEvent(
-    const dom::KeyboardEvent::Data& event) {
+void WebModule::InjectKeyboardEvent(const dom::KeyboardEvent::Data& event) {
   DCHECK(message_loop());
   DCHECK(impl_);
   message_loop()->PostTask(FROM_HERE,
                            base::Bind(&WebModule::Impl::InjectKeyboardEvent,
                                       base::Unretained(impl_.get()),
-                                      scoped_refptr<dom::Element>(),
-                                      event));
+                                      scoped_refptr<dom::Element>(), event));
 }
 
-void WebModule::InjectKeyboardEvent(
-    scoped_refptr<dom::Element> element,
-    const dom::KeyboardEvent::Data& event) {
+void WebModule::InjectKeyboardEvent(scoped_refptr<dom::Element> element,
+                                    const dom::KeyboardEvent::Data& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
                event.type);
   DCHECK(message_loop());
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index b82f6f7..ba8bf30 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -35,6 +35,7 @@
 #include "cobalt/debug/debug_server.h"
 #include "cobalt/debug/render_overlay.h"
 #endif  // ENABLE_DEBUG_CONSOLE
+#include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/keyboard_event.h"
@@ -163,13 +164,11 @@
   // Event is directed at a specific element if the element is non-null.
   // Otherwise, the currently focused element receives the event.
   // If element is specified, we must be on the WebModule's message loop
-  void InjectKeyboardEvent(
-      scoped_refptr<dom::Element> element,
-      const dom::KeyboardEvent::Data& event);
+  void InjectKeyboardEvent(scoped_refptr<dom::Element> element,
+                           const dom::KeyboardEvent::Data& event);
 
   // Call this to inject a keyboard event into the web module.
-  void InjectKeyboardEvent(
-      const dom::KeyboardEvent::Data& event);
+  void InjectKeyboardEvent(const dom::KeyboardEvent::Data& event);
 
   // Call this to execute Javascript code in this web module.  The calling
   // thread will block until the JavaScript has executed and the output results
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index 8352629..a9747b9 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -64,6 +64,7 @@
         '<(DEPTH)/cobalt/trace_event/trace_event.gyp:*',
         '<(DEPTH)/cobalt/web_animations/web_animations.gyp:*',
         '<(DEPTH)/cobalt/webdriver/webdriver.gyp:*',
+        '<(DEPTH)/cobalt/webdriver/webdriver_test.gyp:*',
         '<(DEPTH)/cobalt/xhr/xhr.gyp:*',
         '<(DEPTH)/crypto/crypto.gyp:crypto_unittests',
         '<(DEPTH)/sql/sql.gyp:sql_unittests',
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index d690671..4868c74 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-13403
\ No newline at end of file
+14197
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index 8ba37eb..314650b 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -95,9 +95,11 @@
     'lbshell_root%': '<(DEPTH)/lbshell',
 
     # The relative path from src/ to the directory containing the
-    # starboard_platform.gyp file, or the empty string if not an autodiscovered
-    # platform.
-    'starboard_path%': '',
+    # starboard_platform.gyp file.  It is currently set to
+    # 'starboard/<(target_arch)' to make semi-starboard platforms work.
+    # TODO: Set the default value to '' once all semi-starboard platforms are
+    # moved to starboard.
+    'starboard_path%': 'starboard/<(target_arch)',
 
     # The source of EGL and GLES headers and libraries.
     # Valid values (case and everything sensitive!):
@@ -160,6 +162,12 @@
     # typefaces downloaded from a web page.
     'remote_typeface_cache_size_in_bytes%': 5 * 1024 * 1024,
 
+    # Only relevant if you are using the Blitter API.
+    # Determines the capacity of the software surface cache, which is used to
+    # cache all surfaces that are rendered via a software rasterizer to avoid
+    # re-rendering them.
+    'software_surface_cache_size_in_bytes%': 10 * 1024 * 1024,
+
     # Modifying this value to be non-1.0f will result in the image cache
     # capacity being cleared and then temporarily reduced for the duration that
     # a video is playing.  This can be useful for some platforms if they are
@@ -449,11 +457,13 @@
         'cobalt_copy_debug_console': 1,
         'cobalt_copy_test_data': 1,
         'enable_about_scheme': 1,
+        'enable_fake_microphone': 1,
         'enable_file_scheme': 1,
         'enable_network_logging': 1,
         'enable_remote_debugging%': 1,
         'enable_screenshot': 1,
         'enable_webdriver%': 1,
+        'sb_allows_memory_tracking': 1,
       },
     },
     {
@@ -461,11 +471,13 @@
         'cobalt_copy_debug_console': 0,
         'cobalt_copy_test_data': 0,
         'enable_about_scheme': 0,
+        'enable_fake_microphone': 0,
         'enable_file_scheme': 0,
         'enable_network_logging': 0,
         'enable_remote_debugging%': 0,
         'enable_screenshot': 0,
         'enable_webdriver': 0,
+        'sb_allows_memory_tracking': 0,
       },
     }],
   ],
diff --git a/src/cobalt/build/gyp_cobalt b/src/cobalt/build/gyp_cobalt
index 0e60bb9..030de0f 100755
--- a/src/cobalt/build/gyp_cobalt
+++ b/src/cobalt/build/gyp_cobalt
@@ -218,6 +218,7 @@
         full_starboard_path = platforms[platform]
         assert full_starboard_path[:len(source_tree_dir)] == source_tree_dir
         starboard_path = full_starboard_path[len(source_tree_dir) + 1:]
+        starboard_path.replace(os.sep, '/')
         assert starboard_path[0] not in [ os.sep, os.altsep ]
         variables['starboard_path'] = starboard_path
     _AppendVariables(variables, self.common_args)
diff --git a/src/cobalt/css_parser/grammar.y b/src/cobalt/css_parser/grammar.y
index 2b32f70..e4bb9a1 100644
--- a/src/cobalt/css_parser/grammar.y
+++ b/src/cobalt/css_parser/grammar.y
@@ -221,6 +221,7 @@
 // %token kLeftToken                    // left - also property name token
 %token kMaroonToken                     // maroon
 %token kMiddleToken                     // middle
+%token kMonoscopicToken                 // monoscopic
 %token kMonospaceToken                  // monospace
 %token kNavyToken                       // navy
 %token kNoneToken                       // none
@@ -248,6 +249,8 @@
 %token kStaticToken                     // static
 %token kStepEndToken                    // step-end
 %token kStepStartToken                  // step-start
+%token kStereoscopicLeftRightToken      // stereoscopic-left-right
+%token kStereoscopicTopBottomToken      // stereoscopic-top-bottom
 %token kTealToken                       // teal
 %token kToToken                         // to
 // %token kTopToken                     // top - also property name token
@@ -793,6 +796,11 @@
 %type <cobalt_mtm_resolution_matched_mesh> cobalt_mtm_resolution_matched_mesh
 %destructor { delete $$; } <cobalt_mtm_resolution_matched_mesh>
 
+%union { cssom::KeywordValue* stereo_mode; }
+%type <stereo_mode> maybe_cobalt_mtm_stereo_mode;
+%type <stereo_mode> cobalt_mtm_stereo_mode;
+%destructor { SafeRelease($$); } <stereo_mode>
+
 %union { cssom::TimeListValue::Builder* time_list; }
 %type <time_list> comma_separated_time_list
 %destructor { delete $$; } <time_list>
@@ -1752,6 +1760,9 @@
   | kMiddleToken {
     $$ = TrivialStringPiece::FromCString(cssom::kMiddleKeywordName);
   }
+  | kMonoscopicToken {
+    $$ = TrivialStringPiece::FromCString(cssom::kMonoscopicKeywordName);
+  }
   | kMonospaceToken {
     $$ = TrivialStringPiece::FromCString(cssom::kMonospaceKeywordName);
   }
@@ -1830,6 +1841,14 @@
   | kStepStartToken {
     $$ = TrivialStringPiece::FromCString(cssom::kStepStartKeywordName);
   }
+  | kStereoscopicLeftRightToken {
+    $$ = TrivialStringPiece::FromCString(
+             cssom::kStereoscopicLeftRightKeywordName);
+  }
+  | kStereoscopicTopBottomToken {
+    $$ = TrivialStringPiece::FromCString(
+             cssom::kStereoscopicTopBottomKeywordName);
+  }
   | kTealToken {
     $$ = TrivialStringPiece::FromCString(cssom::kTealKeywordName);
   }
@@ -3418,7 +3437,7 @@
 validated_box_shadow_list:
     box_shadow_list {
     scoped_ptr<ShadowPropertyInfo> shadow_property_info($1);
-    if (!shadow_property_info->IsShadowPropertyValid(ShadowType::kBoxShadow)) {
+    if (!shadow_property_info->IsShadowPropertyValid(kBoxShadow)) {
       parser_impl->LogWarning(@1, "invalid box shadow property.");
       $$ = NULL;
     } else {
@@ -4236,7 +4255,7 @@
 validated_text_shadow_list:
     text_shadow_list {
     scoped_ptr<ShadowPropertyInfo> shadow_property_info($1);
-    if (!shadow_property_info->IsShadowPropertyValid(ShadowType::kTextShadow)) {
+    if (!shadow_property_info->IsShadowPropertyValid(kTextShadow)) {
       parser_impl->LogWarning(@1, "invalid text shadow property.");
       $$ = NULL;
     } else {
@@ -6482,7 +6501,8 @@
   // Encodes an mtm filter. Currently the only type of filter function supported.
     kCobaltMtmFunctionToken maybe_whitespace url
         cobalt_mtm_resolution_matched_mesh_list comma angle angle comma
-        cobalt_mtm_transform_function ')' maybe_whitespace {
+        cobalt_mtm_transform_function maybe_cobalt_mtm_stereo_mode
+        ')' maybe_whitespace {
     scoped_ptr<cssom::MTMFunction::ResolutionMatchedMeshListBuilder>
         resolution_matched_mesh_urls($4);
     scoped_ptr<glm::mat4> transform($9);
@@ -6492,7 +6512,8 @@
         resolution_matched_mesh_urls->Pass(),
         $6,
         $7,
-        *transform);
+        *transform,
+        MakeScopedRefPtrAndRelease($10));
   }
   ;
 
@@ -6544,3 +6565,24 @@
     $$->push_back($3);
   }
   ;
+
+maybe_cobalt_mtm_stereo_mode:
+    /* empty */ {
+    $$ = AddRef(cssom::KeywordValue::GetMonoscopic().get());
+  }
+  | comma cobalt_mtm_stereo_mode {
+    $$ = $2;
+  }
+  ;
+
+cobalt_mtm_stereo_mode:
+    kMonoscopicToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetMonoscopic().get());
+  }
+  | kStereoscopicLeftRightToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetStereoscopicLeftRight().get());
+  }
+  | kStereoscopicTopBottomToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetStereoscopicTopBottom().get());
+  }
+  ;
diff --git a/src/cobalt/css_parser/parser_test.cc b/src/cobalt/css_parser/parser_test.cc
index fe7b721..2de5da7 100644
--- a/src/cobalt/css_parser/parser_test.cc
+++ b/src/cobalt/css_parser/parser_test.cc
@@ -8311,6 +8311,9 @@
 
   EXPECT_EQ(static_cast<float>(M_PI), mtm_function->horizontal_fov());
   EXPECT_EQ(1.5f, mtm_function->vertical_fov());
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kMonoscopic);
 }
 
 TEST_F(ParserTest, ParsesMtmResolutionMatchedUrlsFilter) {
@@ -8345,6 +8348,9 @@
   EXPECT_EQ(
       "yeehaw.msh",
       dynamic_cast<cssom::URLValue*>(meshes[1]->mesh_url().get())->value());
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kMonoscopic);
 }
 
 TEST_F(ParserTest, ParsesMtmTransformMatrixFilter) {
@@ -8375,6 +8381,122 @@
   EXPECT_EQ(2.0f, actual[1][1]);
   EXPECT_EQ(0.0f, actual[2][3]);
   EXPECT_EQ(4.0f, actual[3][3]);
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kMonoscopic);
+}
+
+TEST_F(ParserTest, ParsesMtmMonoscopicStereoModeFilter) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(p.msh),"
+          "                    100deg 60deg,"
+          "                    matrix3d(1, 0, 0, 5,"
+          "                             0, 2, 0, 0,"
+          "                             6, 0, 3, 0,"
+          "                             0, 7, 0, 4),"
+          "                    monoscopic);",
+          source_location_);
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_TRUE(filter_list);
+  ASSERT_EQ(1, filter_list->value().size());
+
+  const cssom::MTMFunction* mtm_function =
+      dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]);
+  ASSERT_TRUE(mtm_function);
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kMonoscopic);
+}
+
+TEST_F(ParserTest, ParsesMtmStereoscopicLeftRightStereoModeFilter) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(p.msh),"
+          "                    100deg 60deg,"
+          "                    matrix3d(1, 0, 0, 5,"
+          "                             0, 2, 0, 0,"
+          "                             6, 0, 3, 0,"
+          "                             0, 7, 0, 4),"
+          "                    stereoscopic-left-right);",
+          source_location_);
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_TRUE(filter_list);
+  ASSERT_EQ(1, filter_list->value().size());
+
+  const cssom::MTMFunction* mtm_function =
+      dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]);
+  ASSERT_TRUE(mtm_function);
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kStereoscopicLeftRight);
+}
+
+TEST_F(ParserTest, ParsesMtmStereoscopicTopBottomStereoModeFilter) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(p.msh),"
+          "                    100deg 60deg,"
+          "                    matrix3d(1, 0, 0, 5,"
+          "                             0, 2, 0, 0,"
+          "                             6, 0, 3, 0,"
+          "                             0, 7, 0, 4),"
+          "                    stereoscopic-top-bottom);",
+          source_location_);
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_TRUE(filter_list);
+  ASSERT_EQ(1, filter_list->value().size());
+
+  const cssom::MTMFunction* mtm_function =
+      dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]);
+  ASSERT_TRUE(mtm_function);
+
+  EXPECT_EQ(mtm_function->stereo_mode()->value(),
+            cssom::KeywordValue::Value::kStereoscopicTopBottom);
+}
+
+TEST_F(ParserTest, HandlesInvalidMtmStereoMode) {
+  EXPECT_CALL(parser_observer_,
+              OnWarning("[object ParserTest]:1:9: warning: unsupported value"));
+
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(p.msh),"
+          "                    100deg 60deg,"
+          "                    matrix3d(1, 0, 0, 5,"
+          "                             0, 2, 0, 0,"
+          "                             6, 0, 3, 0,"
+          "                             0, 7, 0, 4),"
+          "                    invalid-stereo-mode);",
+          source_location_);
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_FALSE(filter_list);
+}
+
+TEST_F(ParserTest, EmptyPropertyValueRemovesProperty) {
+  // Test that parsing an empty property value removes the previously declared
+  // property value.
+  scoped_refptr<cssom::CSSDeclaredStyleData> style_data =
+      parser_.ParseStyleDeclarationList("display: inline;", source_location_);
+
+  scoped_refptr<cssom::CSSDeclaredStyleDeclaration> style =
+      new cssom::CSSDeclaredStyleDeclaration(style_data, &parser_);
+
+  style->SetPropertyValue(std::string("display"), std::string(), NULL);
+  EXPECT_EQ(style->GetPropertyValue("display"), "");
+  EXPECT_FALSE(style_data->GetPropertyValue(cssom::kDisplayProperty));
 }
 
 }  // namespace css_parser
diff --git a/src/cobalt/css_parser/scanner.cc b/src/cobalt/css_parser/scanner.cc
index b295346..a19db4d 100644
--- a/src/cobalt/css_parser/scanner.cc
+++ b/src/cobalt/css_parser/scanner.cc
@@ -2204,6 +2204,10 @@
         *property_value_token = kStepStartToken;
         return true;
       }
+      if (IsEqualToCssIdentifier(name.begin, cssom::kMonoscopicKeywordName)) {
+        *property_value_token = kMonoscopicToken;
+        return true;
+      }
       return false;
 
     case 11:
@@ -2262,6 +2266,19 @@
         return true;
       }
       return false;
+
+    case 23:
+      if (IsEqualToCssIdentifier(name.begin,
+                                 cssom::kStereoscopicLeftRightKeywordName)) {
+        *property_value_token = kStereoscopicLeftRightToken;
+        return true;
+      }
+      if (IsEqualToCssIdentifier(name.begin,
+                                 cssom::kStereoscopicTopBottomKeywordName)) {
+        *property_value_token = kStereoscopicTopBottomToken;
+        return true;
+      }
+      return false;
   }
 
   return false;
diff --git a/src/cobalt/cssom/CSSStyleDeclaration.idl b/src/cobalt/cssom/CSSStyleDeclaration.idl
index fc13d7a..12793bf 100644
--- a/src/cobalt/cssom/CSSStyleDeclaration.idl
+++ b/src/cobalt/cssom/CSSStyleDeclaration.idl
@@ -111,6 +111,11 @@
   readonly attribute unsigned long length;
   getter DOMString? item(unsigned long index);
   DOMString getPropertyValue(DOMString property);
-  [RaisesException] void setPropertyValue(DOMString property, DOMString value);
+  [RaisesException] void setProperty(
+      DOMString property, [TreatNullAs=EmptyString] DOMString value,
+      [TreatNullAs=EmptyString] optional DOMString priority);
+  [RaisesException] void setPropertyValue(
+      DOMString property, [TreatNullAs=EmptyString] DOMString value);
+  [RaisesException] DOMString removeProperty(DOMString property);
   readonly attribute CSSRule? parentRule;
 };
diff --git a/src/cobalt/cssom/computed_style.cc b/src/cobalt/cssom/computed_style.cc
index 756819f..5fa9201 100644
--- a/src/cobalt/cssom/computed_style.cc
+++ b/src/cobalt/cssom/computed_style.cc
@@ -363,6 +363,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -379,6 +380,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -458,6 +461,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNone:
@@ -475,6 +479,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -561,6 +567,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -578,6 +585,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -680,6 +689,7 @@
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
     case KeywordValue::kMonospace:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
@@ -696,6 +706,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -798,6 +810,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
@@ -814,6 +827,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -916,6 +931,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -933,6 +949,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -1029,6 +1047,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -1046,6 +1065,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -1138,6 +1159,7 @@
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
     case KeywordValue::kMonospace:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
     case KeywordValue::kNoWrap:
@@ -1153,6 +1175,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -1551,6 +1575,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
@@ -1567,6 +1592,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -1824,6 +1851,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -1841,6 +1869,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -2071,6 +2101,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNormal:
     case KeywordValue::kNoRepeat:
@@ -2087,6 +2118,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
@@ -2455,6 +2488,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
@@ -2471,6 +2505,8 @@
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
     case KeywordValue::kStatic:
+    case KeywordValue::kStereoscopicLeftRight:
+    case KeywordValue::kStereoscopicTopBottom:
     case KeywordValue::kTop:
     case KeywordValue::kUppercase:
     case KeywordValue::kVisible:
diff --git a/src/cobalt/cssom/css_computed_style_data.cc b/src/cobalt/cssom/css_computed_style_data.cc
index 3866deb..509a9b3 100644
--- a/src/cobalt/cssom/css_computed_style_data.cc
+++ b/src/cobalt/cssom/css_computed_style_data.cc
@@ -67,16 +67,6 @@
 
 CSSComputedStyleData::~CSSComputedStyleData() {}
 
-unsigned int CSSComputedStyleData::length() const {
-  // Computed style declarations have all known longhand properties.
-  return kMaxLonghandPropertyKey + 1;
-}
-
-const char* CSSComputedStyleData::Item(unsigned int index) const {
-  if (index >= length()) return NULL;
-  return GetPropertyName(GetLexicographicalLonghandPropertyKey(index));
-}
-
 const scoped_refptr<PropertyValue>&
 CSSComputedStyleData::GetPropertyValueReference(PropertyKey key) const {
   DCHECK_GT(key, kNoneProperty);
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h
index c2c82c1..fb12f37 100644
--- a/src/cobalt/cssom/css_computed_style_data.h
+++ b/src/cobalt/cssom/css_computed_style_data.h
@@ -62,16 +62,6 @@
   CSSComputedStyleData();
   ~CSSComputedStyleData();
 
-  // The length attribute must return the number of CSS declarations in the
-  // declarations.
-  //   https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-length
-  unsigned int length() const;
-
-  // The item(index) method must return the property name of the CSS declaration
-  // at position index.
-  //  https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-item
-  const char* Item(unsigned int index) const;
-
   void SetPropertyValue(const PropertyKey key,
                         const scoped_refptr<PropertyValue>& value);
 
diff --git a/src/cobalt/cssom/css_computed_style_declaration.cc b/src/cobalt/cssom/css_computed_style_declaration.cc
index ed4b4c8..1ca1566 100644
--- a/src/cobalt/cssom/css_computed_style_declaration.cc
+++ b/src/cobalt/cssom/css_computed_style_declaration.cc
@@ -42,7 +42,8 @@
 // declarations.
 //   https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-length
 unsigned int CSSComputedStyleDeclaration::length() const {
-  return data_ ? data_->length() : 0;
+  // Computed style declarations have all known longhand properties.
+  return kMaxLonghandPropertyKey + 1;
 }
 
 // The item(index) method must return the property name of the CSS declaration
@@ -50,8 +51,9 @@
 //   https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-item
 base::optional<std::string> CSSComputedStyleDeclaration::Item(
     unsigned int index) const {
-  const char* item = data_ ? data_->Item(index) : NULL;
-  return item ? base::optional<std::string>(item) : base::nullopt;
+  if (index >= length()) return base::nullopt;
+  return base::optional<std::string>(
+      GetPropertyName(GetLexicographicalLonghandPropertyKey(index)));
 }
 
 std::string CSSComputedStyleDeclaration::GetDeclaredPropertyValueStringByKey(
@@ -72,6 +74,13 @@
                            exception_state);
 }
 
+void CSSComputedStyleDeclaration::SetProperty(
+    const std::string& /*property_name*/, const std::string& /*property_value*/,
+    const std::string& /*priority*/, script::ExceptionState* exception_state) {
+  dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr,
+                           exception_state);
+}
+
 void CSSComputedStyleDeclaration::SetData(
     const scoped_refptr<const CSSComputedStyleData>& data) {
   data_ = data;
diff --git a/src/cobalt/cssom/css_computed_style_declaration.h b/src/cobalt/cssom/css_computed_style_declaration.h
index e166979..1735690 100644
--- a/src/cobalt/cssom/css_computed_style_declaration.h
+++ b/src/cobalt/cssom/css_computed_style_declaration.h
@@ -38,6 +38,8 @@
 // for computed styles.
 class CSSComputedStyleDeclaration : public CSSStyleDeclaration {
  public:
+  using CSSStyleDeclaration::SetProperty;
+
   CSSComputedStyleDeclaration() {}
   // From CSSStyleDeclaration.
 
@@ -53,6 +55,11 @@
                         const std::string& property_value,
                         script::ExceptionState* exception_state) OVERRIDE;
 
+  void SetProperty(const std::string& property_name,
+                   const std::string& property_value,
+                   const std::string& priority,
+                   script::ExceptionState* exception_state) OVERRIDE;
+
   // Custom.
 
   const scoped_refptr<const CSSComputedStyleData>& data() const {
diff --git a/src/cobalt/cssom/css_computed_style_declaration_test.cc b/src/cobalt/cssom/css_computed_style_declaration_test.cc
new file mode 100644
index 0000000..d5abd5a
--- /dev/null
+++ b/src/cobalt/cssom/css_computed_style_declaration_test.cc
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/cssom/css_computed_style_data.h"
+#include "cobalt/cssom/css_computed_style_declaration.h"
+#include "cobalt/cssom/keyword_value.h"
+#include "cobalt/cssom/length_value.h"
+#include "cobalt/cssom/property_definitions.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/script/exception_state.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace cssom {
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace {
+class FakeExceptionState : public script::ExceptionState {
+ public:
+  void SetException(
+      const scoped_refptr<script::ScriptException>& exception) OVERRIDE {
+    dom_exception_ = make_scoped_refptr(
+        base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  }
+  void SetSimpleException(script::MessageType /*message_type*/, ...) OVERRIDE {
+    // no-op
+  }
+  dom::DOMException::ExceptionCode GetExceptionCode() {
+    return dom_exception_ ? static_cast<dom::DOMException::ExceptionCode>(
+                                dom_exception_->code())
+                          : dom::DOMException::kNone;
+  }
+
+ private:
+  scoped_refptr<dom::DOMException> dom_exception_;
+};
+}  // namespace
+
+TEST(CSSComputedStyleDeclarationTest, CSSTextSetterRaisesException) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  FakeExceptionState exception_state;
+
+  const std::string css_text = "font-size: 100px; color: #0047ab;";
+  style->set_css_text(css_text, &exception_state);
+  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+            exception_state.GetExceptionCode());
+}
+
+TEST(CSSComputedStyleDeclarationTest, DISABLED_CSSTextGetter) {
+  scoped_refptr<LengthValue> background_size =
+      new LengthValue(100, kPixelsUnit);
+  scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit);
+
+  scoped_refptr<CSSComputedStyleData> style_data = new CSSComputedStyleData();
+  style_data->SetPropertyValue(kBackgroundSizeProperty, background_size);
+  style_data->SetPropertyValue(kBottomProperty, bottom);
+
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(style_data);
+  EXPECT_EQ(style->css_text(NULL), "background-size: 100px; bottom: 16px;");
+}
+
+TEST(CSSComputedStyleDeclarationTest, PropertyValueSetterRaisesException) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  const std::string background = "rgba(0, 0, 0, .8)";
+  FakeExceptionState exception_state;
+
+  style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background,
+                          &exception_state);
+  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+            exception_state.GetExceptionCode());
+}
+
+TEST(CSSComputedStyleDeclarationTest,
+     PropertySetterWithTwoValuesRaisesException) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  const std::string background = "rgba(0, 0, 0, .8)";
+  FakeExceptionState exception_state;
+
+  style->SetProperty(GetPropertyName(kBackgroundProperty), background,
+                     &exception_state);
+  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+            exception_state.GetExceptionCode());
+}
+
+TEST(CSSComputedStyleDeclarationTest,
+     PropertySetterWithThreeValuesRaisesException) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  const std::string background = "rgba(0, 0, 0, .8)";
+  FakeExceptionState exception_state;
+
+  style->SetProperty(GetPropertyName(kBackgroundProperty), background,
+                     std::string(), &exception_state);
+  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+            exception_state.GetExceptionCode());
+}
+
+TEST(CSSComputedStyleDeclarationTest, RemovePropertyRaisesException) {
+  scoped_refptr<CSSComputedStyleData> initial_style =
+      new CSSComputedStyleData();
+  initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline());
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(initial_style);
+
+  const std::string background = "rgba(0, 0, 0, .8)";
+  FakeExceptionState exception_state;
+
+  style->RemoveProperty(GetPropertyName(kDisplayProperty), &exception_state);
+  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+            exception_state.GetExceptionCode());
+}
+
+TEST(CSSComputedStyleDeclarationTest, PropertyValueGetter) {
+  scoped_refptr<CSSComputedStyleData> initial_style =
+      new CSSComputedStyleData();
+  initial_style->SetPropertyValue(kTextAlignProperty,
+                                  KeywordValue::GetCenter());
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(initial_style);
+
+  EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)),
+            "center");
+}
+
+TEST(CSSComputedStyleDeclarationTest,
+     UnknownDeclaredStylePropertyValueShouldBeEmpty) {
+  scoped_refptr<CSSComputedStyleData> initial_style =
+      new CSSComputedStyleData();
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(initial_style);
+
+  EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), "");
+}
+
+TEST(CSSComputedStyleDeclarationTest, LengthAttributeGetterEmpty) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+
+  EXPECT_EQ(style->length(), kMaxLonghandPropertyKey + 1);
+}
+
+TEST(CSSComputedStyleDeclarationTest, LengthAttributeGetterNotEmpty) {
+  scoped_refptr<CSSComputedStyleData> initial_style =
+      new CSSComputedStyleData();
+  initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline());
+  initial_style->SetPropertyValue(kTextAlignProperty,
+                                  KeywordValue::GetCenter());
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(initial_style);
+
+  EXPECT_EQ(style->length(), kMaxLonghandPropertyKey + 1);
+}
+
+TEST(CSSComputedStyleDeclarationTest, ItemGetterEmpty) {
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+
+  // Computed styles are never actually empty.
+  EXPECT_TRUE(style->Item(0));
+}
+
+TEST(CSSComputedStyleDeclarationTest, ItemGetterNotEmpty) {
+  scoped_refptr<CSSComputedStyleData> initial_style =
+      new CSSComputedStyleData();
+  initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline());
+  initial_style->SetPropertyValue(kTextAlignProperty,
+                                  KeywordValue::GetCenter());
+  scoped_refptr<CSSComputedStyleDeclaration> style =
+      new CSSComputedStyleDeclaration();
+  style->SetData(initial_style);
+
+  int property_count = 0;
+  for (unsigned int key = 0; key <= kMaxLonghandPropertyKey; ++key) {
+    // The order is not important, as long as all properties are represented.
+    EXPECT_TRUE(style->Item(key));
+    if (style->Item(key).value() == GetPropertyName(kDisplayProperty)) {
+      ++property_count;
+    }
+    if (style->Item(key).value() == GetPropertyName(kTextAlignProperty)) {
+      ++property_count;
+    }
+  }
+  EXPECT_EQ(property_count, 2);
+}
+
+}  // namespace cssom
+}  // namespace cobalt
diff --git a/src/cobalt/cssom/css_declared_style_declaration.cc b/src/cobalt/cssom/css_declared_style_declaration.cc
index 6c4557e..2e9b046 100644
--- a/src/cobalt/cssom/css_declared_style_declaration.cc
+++ b/src/cobalt/cssom/css_declared_style_declaration.cc
@@ -125,6 +125,22 @@
   RecordMutation();
 }
 
+void CSSDeclaredStyleDeclaration::SetProperty(
+    const std::string& property_name, const std::string& property_value,
+    const std::string& priority, script::ExceptionState* /*exception_state*/) {
+  DLOG(INFO) << "CSSDeclaredStyleDeclaration::SetProperty(" << property_name
+             << "," << property_value << "," << priority << ")";
+  DCHECK(css_parser_);
+  if (!data_) {
+    data_ = new CSSDeclaredStyleData();
+  }
+  css_parser_->ParsePropertyIntoDeclarationData(
+      property_name, property_value, non_trivial_static_fields.Get().location,
+      data_.get());
+
+  RecordMutation();
+}
+
 void CSSDeclaredStyleDeclaration::RecordMutation() {
   if (mutation_observer_) {
     // Trigger layout update.
diff --git a/src/cobalt/cssom/css_declared_style_declaration.h b/src/cobalt/cssom/css_declared_style_declaration.h
index 344513d..9466df9 100644
--- a/src/cobalt/cssom/css_declared_style_declaration.h
+++ b/src/cobalt/cssom/css_declared_style_declaration.h
@@ -39,6 +39,8 @@
 // for declared styles, such as css style rules and inline styles.
 class CSSDeclaredStyleDeclaration : public CSSStyleDeclaration {
  public:
+  using CSSStyleDeclaration::SetProperty;
+
   explicit CSSDeclaredStyleDeclaration(CSSParser* css_parser);
 
   CSSDeclaredStyleDeclaration(const scoped_refptr<CSSDeclaredStyleData>& data,
@@ -58,6 +60,11 @@
                         const std::string& property_value,
                         script::ExceptionState* exception_state) OVERRIDE;
 
+  void SetProperty(const std::string& property_name,
+                   const std::string& property_value,
+                   const std::string& priority,
+                   script::ExceptionState* exception_state) OVERRIDE;
+
   // Custom.
 
   const scoped_refptr<CSSDeclaredStyleData>& data() const { return data_; }
diff --git a/src/cobalt/cssom/css_style_declaration_test.cc b/src/cobalt/cssom/css_declared_style_declaration_test.cc
similarity index 85%
rename from src/cobalt/cssom/css_style_declaration_test.cc
rename to src/cobalt/cssom/css_declared_style_declaration_test.cc
index 0e71d24..fb8d97d 100644
--- a/src/cobalt/cssom/css_style_declaration_test.cc
+++ b/src/cobalt/cssom/css_declared_style_declaration_test.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Google Inc. All Rights Reserved.
+ * Copyright 2016 Google Inc. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#include "cobalt/cssom/css_style_declaration.h"
-
 #include "cobalt/cssom/css_declared_style_data.h"
 #include "cobalt/cssom/css_declared_style_declaration.h"
 #include "cobalt/cssom/css_parser.h"
@@ -37,12 +35,14 @@
 using ::testing::_;
 using ::testing::Return;
 
+namespace {
 class MockMutationObserver : public MutationObserver {
  public:
   MOCK_METHOD0(OnCSSMutation, void());
 };
+}  // namespace
 
-TEST(CSSStyleDeclarationTest, BackgroundSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -58,7 +58,7 @@
   style->set_background(background, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BackgroundColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -74,7 +74,7 @@
   style->set_background_color(background_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BackgroundImageSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundImageSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -90,7 +90,7 @@
   style->set_background_image(background_image, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BackgroundPositionSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundPositionSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -106,7 +106,7 @@
   style->set_background_position(background_position, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BackgroundRepeatSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundRepeatSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -122,7 +122,7 @@
   style->set_background_repeat(background_repeat, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BackgroundSizeSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundSizeSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -138,7 +138,7 @@
   style->set_background_size(background_size, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -153,7 +153,7 @@
   style->set_border(border, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderBottomSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -169,7 +169,7 @@
   style->set_border_bottom(border_bottom, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderBottomColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -185,7 +185,7 @@
   style->set_border_bottom_color(border_bottom_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderBottomStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -201,7 +201,7 @@
   style->set_border_bottom_style(border_bottom_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderBottomWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> width =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -217,7 +217,7 @@
   width->set_border_bottom_width(border_bottom_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -233,7 +233,7 @@
   style->set_border_color(border_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderLeftSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -249,7 +249,7 @@
   style->set_border_left(border_left, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderLeftColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -265,7 +265,7 @@
   style->set_border_left_color(border_left_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderLeftStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -281,7 +281,7 @@
   style->set_border_left_style(border_left_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderLeftWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> width =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -297,7 +297,7 @@
   width->set_border_left_width(border_left_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderRadiusSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderRadiusSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -313,7 +313,7 @@
   style->set_border_radius(border_radius, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderRightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -329,7 +329,7 @@
   style->set_border_right(border_right, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderRightColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -345,7 +345,7 @@
   style->set_border_right_color(border_right_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderRightStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -361,7 +361,7 @@
   style->set_border_right_style(border_right_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderRightWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> width =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -377,7 +377,7 @@
   width->set_border_right_width(border_right_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -393,7 +393,7 @@
   style->set_border_style(border_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderTopSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -409,7 +409,7 @@
   style->set_border_top(border_top, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderTopColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -425,7 +425,7 @@
   style->set_border_top_color(border_top_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderTopStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -441,7 +441,7 @@
   style->set_border_top_style(border_top_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BorderTopWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> width =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -457,7 +457,7 @@
   width->set_border_top_width(border_top_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BottomSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BottomSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -472,7 +472,7 @@
   style->set_bottom(bottom, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, BoxShadowSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, BoxShadowSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -488,7 +488,7 @@
   style->set_box_shadow(box_shadow, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, ColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, ColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -503,7 +503,7 @@
   style->set_color(color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, ContentSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, ContentSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -519,7 +519,7 @@
   style->set_content(content, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, DisplaySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, DisplaySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -535,7 +535,7 @@
   style->set_display(display, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, FontSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, FontSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -550,7 +550,7 @@
   style->set_font(font, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, FontFamilySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, FontFamilySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -566,7 +566,7 @@
   style->set_font_family(font_family, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, FontSizeSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, FontSizeSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -582,7 +582,7 @@
   style->set_font_size(font_size, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, FontStyleSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, FontStyleSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -598,7 +598,7 @@
   style->set_font_style(font_style, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, FontWeightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, FontWeightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -614,7 +614,7 @@
   style->set_font_weight(font_weight, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, HeightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, HeightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -629,7 +629,7 @@
   style->set_height(height, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, LeftSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, LeftSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -644,7 +644,7 @@
   style->set_left(left, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, LineHeightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, LineHeightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -660,7 +660,7 @@
   style->set_line_height(line_height, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MarginSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MarginSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -675,7 +675,7 @@
   style->set_margin(margin, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MarginBottomSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MarginBottomSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -691,7 +691,7 @@
   style->set_margin_bottom(margin_bottom, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MarginLeftSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MarginLeftSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -707,7 +707,7 @@
   style->set_margin_left(margin_left, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MarginRightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MarginRightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -723,7 +723,7 @@
   style->set_margin_right(margin_right, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MarginTopSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MarginTopSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -739,7 +739,7 @@
   style->set_margin_top(margin_top, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MaxHeightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MaxHeightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -755,7 +755,7 @@
   style->set_max_height(max_height, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MaxWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MaxWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -771,7 +771,7 @@
   style->set_max_width(max_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MinHeightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MinHeightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -787,7 +787,7 @@
   style->set_min_height(min_height, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, MinWidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, MinWidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -803,7 +803,7 @@
   style->set_min_width(min_width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, OpacitySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, OpacitySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -819,7 +819,7 @@
   style->set_opacity(opacity, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, OverflowSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, OverflowSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -835,7 +835,7 @@
   style->set_overflow(overflow, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, OverflowWrapSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, OverflowWrapSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -852,7 +852,7 @@
   style->set_overflow_wrap(overflow_wrap, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PaddingSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PaddingSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -868,7 +868,7 @@
   style->set_padding(padding, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PaddingBottomSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PaddingBottomSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -884,7 +884,7 @@
   style->set_padding_bottom(padding_bottom, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PaddingLeftSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PaddingLeftSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -900,7 +900,7 @@
   style->set_padding_left(padding_left, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PaddingRightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PaddingRightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -916,7 +916,7 @@
   style->set_padding_right(padding_right, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PaddingTopSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PaddingTopSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -932,7 +932,7 @@
   style->set_padding_top(padding_top, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PositionSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PositionSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -948,7 +948,7 @@
   style->set_position(position, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, RightSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, RightSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -963,7 +963,7 @@
   style->set_right(right, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextAlignSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextAlignSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -979,7 +979,7 @@
   style->set_text_align(text_align, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextDecorationColorSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextDecorationColorSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -996,7 +996,7 @@
   style->set_text_decoration_color(text_decoration_color, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextDecorationLineSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextDecorationLineSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1013,7 +1013,7 @@
   style->set_text_decoration_line(text_decoration_line, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextIndentSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextIndentSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1030,7 +1030,7 @@
   style->set_text_indent(text_indent, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextOverflowSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextOverflowSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1047,7 +1047,7 @@
   style->set_text_overflow(text_overflow, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextShadowSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextShadowSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1064,7 +1064,7 @@
   style->set_text_shadow(text_shadow, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TextTransformSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TextTransformSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1081,7 +1081,7 @@
   style->set_text_transform(text_transform, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TopSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TopSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1096,7 +1096,7 @@
   style->set_top(top, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransformSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransformSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1112,7 +1112,7 @@
   style->set_transform(transform, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransformOriginSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransformOriginSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1128,7 +1128,7 @@
   style->set_transform_origin(transform_origin, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransitionSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransitionSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1144,7 +1144,7 @@
   style->set_transition(transition, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransitionDelaySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransitionDelaySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1160,7 +1160,7 @@
   style->set_transition_delay(transition_delay, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransitionDurationSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransitionDurationSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1176,7 +1176,7 @@
   style->set_transition_duration(transition_duration, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransitionPropertySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransitionPropertySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1192,7 +1192,7 @@
   style->set_transition_property(transition_property, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, TransitionTimingFunctionSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, TransitionTimingFunctionSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1209,7 +1209,7 @@
   style->set_transition_timing_function(transition_timing_function, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, VerticalAlignSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, VerticalAlignSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1225,7 +1225,7 @@
   style->set_vertical_align(vertical_align, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, VisibilitySetter) {
+TEST(CSSDeclaredStyleDeclarationTest, VisibilitySetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1241,7 +1241,7 @@
   style->set_visibility(visibility, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, WhiteSpaceSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, WhiteSpaceSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1258,7 +1258,7 @@
   style->set_white_space(white_space, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, WidthSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, WidthSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1273,7 +1273,7 @@
   style->set_width(width, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, WordWrapSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, WordWrapSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1290,7 +1290,7 @@
   style->set_word_wrap(word_wrap, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, ZIndexSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, ZIndexSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1306,7 +1306,7 @@
   style->set_z_index(z_index, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, CSSTextSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1318,7 +1318,7 @@
   style->set_css_text(css_text, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, CSSTextSetterEmptyString) {
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetterEmptyString) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleData> initial_style =
       new CSSDeclaredStyleData();
@@ -1338,7 +1338,7 @@
   style->set_css_text(css_text, NULL);
 }
 
-TEST(CSSStyleDeclarationTest, CSSTextGetter) {
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextGetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<PercentageValue> background_size = new PercentageValue(0.50f);
   scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit);
@@ -1357,7 +1357,7 @@
 // TODO: Add GetPropertyValue tests, property getter tests and tests
 // that checking if the attributes' setter and the getter are consistent when
 // fully support converting PropertyValue to std::string.
-TEST(CSSStyleDeclarationTest, PropertyValueSetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PropertyValueSetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1374,7 +1374,57 @@
                           NULL);
 }
 
-TEST(CSSStyleDeclarationTest, PropertyValueGetter) {
+TEST(CSSDeclaredStyleDeclarationTest, PropertyValueSetterTwice) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background = "rgba(0, 0, 0, .8)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBackgroundProperty), background, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background,
+                          NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PropertySetterWithTwoValues) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background = "rgba(0, 0, 0, .8)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBackgroundProperty), background, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->SetProperty(GetPropertyName(kBackgroundProperty), background, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PropertySetterWithThreeValues) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background = "rgba(0, 0, 0, .8)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBackgroundProperty), background, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->SetProperty(GetPropertyName(kBackgroundProperty), background,
+                     std::string(), NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PropertyValueGetter) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleData> initial_style =
       new CSSDeclaredStyleData();
@@ -1387,7 +1437,8 @@
             "center");
 }
 
-TEST(CSSStyleDeclarationTest, UnknownDeclaredStylePropertyValueShouldBeEmpty) {
+TEST(CSSDeclaredStyleDeclarationTest,
+     UnknownDeclaredStylePropertyValueShouldBeEmpty) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleData> initial_style =
       new CSSDeclaredStyleData();
@@ -1397,7 +1448,26 @@
   EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), "");
 }
 
-TEST(CSSStyleDeclarationTest, LengthAttributeGetterEmpty) {
+TEST(CSSDeclaredStyleDeclarationTest, RemoveProperty) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleData> initial_style =
+      new CSSDeclaredStyleData();
+  initial_style->SetPropertyValueAndImportance(
+      kDisplayProperty, KeywordValue::GetInline(), false);
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
+
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kDisplayProperty), std::string(""), _, _));
+  style->RemoveProperty(GetPropertyName(kDisplayProperty), NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, LengthAttributeGetterEmpty) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1405,7 +1475,7 @@
   EXPECT_EQ(style->length(), 0);
 }
 
-TEST(CSSStyleDeclarationTest, LengthAttributeGetterNotEmpty) {
+TEST(CSSDeclaredStyleDeclarationTest, LengthAttributeGetterNotEmpty) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleData> initial_style =
       new CSSDeclaredStyleData();
@@ -1419,7 +1489,7 @@
   EXPECT_EQ(style->length(), 2);
 }
 
-TEST(CSSStyleDeclarationTest, ItemGetterEmpty) {
+TEST(CSSDeclaredStyleDeclarationTest, ItemGetterEmpty) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleDeclaration> style =
       new CSSDeclaredStyleDeclaration(&css_parser);
@@ -1427,7 +1497,7 @@
   EXPECT_FALSE(style->Item(0));
 }
 
-TEST(CSSStyleDeclarationTest, ItemGetterNotEmpty) {
+TEST(CSSDeclaredStyleDeclarationTest, ItemGetterNotEmpty) {
   testing::MockCSSParser css_parser;
   scoped_refptr<CSSDeclaredStyleData> initial_style =
       new CSSDeclaredStyleData();
@@ -1442,7 +1512,7 @@
   EXPECT_TRUE(style->Item(1));
   EXPECT_FALSE(style->Item(2));
 
-  // The order is not important, as long as with properties are represented.
+  // The order is not important, as long as declared properties are represented.
   if (style->Item(0).value() == GetPropertyName(kDisplayProperty)) {
     EXPECT_EQ(style->Item(1).value(), GetPropertyName(kTextAlignProperty));
   } else {
diff --git a/src/cobalt/cssom/css_style_declaration.h b/src/cobalt/cssom/css_style_declaration.h
index 6bfcd8b..dc542d2 100644
--- a/src/cobalt/cssom/css_style_declaration.h
+++ b/src/cobalt/cssom/css_style_declaration.h
@@ -427,6 +427,26 @@
                                 const std::string& property_value,
                                 script::ExceptionState* exception_state) = 0;
 
+  virtual void SetProperty(const std::string& property_name,
+                           const std::string& property_value,
+                           const std::string& priority,
+                           script::ExceptionState* exception_state) = 0;
+
+  void SetProperty(const std::string& property_name,
+                   const std::string& property_value,
+                   script::ExceptionState* exception_state) {
+    SetPropertyValue(property_name, property_value, exception_state);
+  }
+
+  std::string RemoveProperty(const std::string& property_name,
+                             script::ExceptionState* exception_state) {
+    std::string retval = GetPropertyValue(property_name);
+    if (!retval.empty()) {
+      SetPropertyValue(property_name, "", exception_state);
+    }
+    return retval;
+  }
+
   // The parent rule is the CSS rule that the CSS declaration block is
   // associated with, if any, or null otherwise.
   //   https://www.w3.org/TR/2013/WD-cssom-20131205/#css-declaration-block
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp
index a776fa4..3c57b62 100644
--- a/src/cobalt/cssom/cssom_test.gyp
+++ b/src/cobalt/cssom/cssom_test.gyp
@@ -26,14 +26,15 @@
         'computed_style_test.cc',
         'css_computed_style_data_property_set_matcher_test.cc',
         'css_computed_style_data_test.cc',
+        'css_computed_style_declaration_test.cc',
         'css_declared_style_data_test.cc',
+        'css_declared_style_declaration_test.cc',
         'css_font_face_declaration_data_test.cc',
         'css_font_face_rule_test.cc',
         'css_grouping_rule_test.cc',
         'css_property_definitions_test.cc',
         'css_rule_list_test.cc',
         'css_rule_visitor_test.cc',
-        'css_style_declaration_test.cc',
         'css_style_sheet_test.cc',
         'css_transition_set_test.cc',
         'interpolate_property_value_test.cc',
diff --git a/src/cobalt/cssom/keyword_names.cc b/src/cobalt/cssom/keyword_names.cc
index 28cbfbf..34fde90 100644
--- a/src/cobalt/cssom/keyword_names.cc
+++ b/src/cobalt/cssom/keyword_names.cc
@@ -73,6 +73,7 @@
 const char kLineThroughKeywordName[] = "line-through";
 const char kMaroonKeywordName[] = "maroon";
 const char kMiddleKeywordName[] = "middle";
+const char kMonoscopicKeywordName[] = "monoscopic";
 const char kMonospaceKeywordName[] = "monospace";
 const char kNavyKeywordName[] = "navy";
 const char kNoneKeywordName[] = "none";
@@ -100,6 +101,8 @@
 const char kStaticKeywordName[] = "static";
 const char kStepEndKeywordName[] = "step-end";
 const char kStepStartKeywordName[] = "step-start";
+const char kStereoscopicLeftRightKeywordName[] = "stereoscopic-left-right";
+const char kStereoscopicTopBottomKeywordName[] = "stereoscopic-top-bottom";
 const char kTealKeywordName[] = "teal";
 const char kToKeywordName[] = "to";
 const char kTopKeywordName[] = "top";
diff --git a/src/cobalt/cssom/keyword_names.h b/src/cobalt/cssom/keyword_names.h
index ccac4d5..86f0eda 100644
--- a/src/cobalt/cssom/keyword_names.h
+++ b/src/cobalt/cssom/keyword_names.h
@@ -75,6 +75,7 @@
 extern const char kLineThroughKeywordName[];
 extern const char kMaroonKeywordName[];
 extern const char kMiddleKeywordName[];
+extern const char kMonoscopicKeywordName[];
 extern const char kMonospaceKeywordName[];
 extern const char kNavyKeywordName[];
 extern const char kNoneKeywordName[];
@@ -102,6 +103,8 @@
 extern const char kStaticKeywordName[];
 extern const char kStepEndKeywordName[];
 extern const char kStepStartKeywordName[];
+extern const char kStereoscopicLeftRightKeywordName[];
+extern const char kStereoscopicTopBottomKeywordName[];
 extern const char kTealKeywordName[];
 extern const char kToKeywordName[];
 extern const char kTopKeywordName[];
diff --git a/src/cobalt/cssom/keyword_value.cc b/src/cobalt/cssom/keyword_value.cc
index 73aba27..0d90535 100644
--- a/src/cobalt/cssom/keyword_value.cc
+++ b/src/cobalt/cssom/keyword_value.cc
@@ -56,6 +56,7 @@
         left_value(new KeywordValue(KeywordValue::kLeft)),
         line_through_value(new KeywordValue(KeywordValue::kLineThrough)),
         middle_value(new KeywordValue(KeywordValue::kMiddle)),
+        monoscopic_value(new KeywordValue(KeywordValue::kMonoscopic)),
         monospace_value(new KeywordValue(KeywordValue::kMonospace)),
         none_value(new KeywordValue(KeywordValue::kNone)),
         no_repeat_value(new KeywordValue(KeywordValue::kNoRepeat)),
@@ -73,6 +74,10 @@
         solid_value(new KeywordValue(KeywordValue::kSolid)),
         start_value(new KeywordValue(KeywordValue::kStart)),
         static_value(new KeywordValue(KeywordValue::kStatic)),
+        stereoscopic_left_right_value(
+            new KeywordValue(KeywordValue::kStereoscopicLeftRight)),
+        stereoscopic_top_bottom_value(
+            new KeywordValue(KeywordValue::kStereoscopicTopBottom)),
         top_value(new KeywordValue(KeywordValue::kTop)),
         uppercase_value(new KeywordValue(KeywordValue::kUppercase)),
         visible_value(new KeywordValue(KeywordValue::kVisible)) {}
@@ -107,6 +112,7 @@
   const scoped_refptr<KeywordValue> left_value;
   const scoped_refptr<KeywordValue> line_through_value;
   const scoped_refptr<KeywordValue> middle_value;
+  const scoped_refptr<KeywordValue> monoscopic_value;
   const scoped_refptr<KeywordValue> monospace_value;
   const scoped_refptr<KeywordValue> none_value;
   const scoped_refptr<KeywordValue> no_repeat_value;
@@ -124,6 +130,8 @@
   const scoped_refptr<KeywordValue> solid_value;
   const scoped_refptr<KeywordValue> start_value;
   const scoped_refptr<KeywordValue> static_value;
+  const scoped_refptr<KeywordValue> stereoscopic_left_right_value;
+  const scoped_refptr<KeywordValue> stereoscopic_top_bottom_value;
   const scoped_refptr<KeywordValue> top_value;
   const scoped_refptr<KeywordValue> uppercase_value;
   const scoped_refptr<KeywordValue> visible_value;
@@ -259,6 +267,10 @@
   return non_trivial_static_fields.Get().middle_value;
 }
 
+const scoped_refptr<KeywordValue>& KeywordValue::GetMonoscopic() {
+  return non_trivial_static_fields.Get().monoscopic_value;
+}
+
 const scoped_refptr<KeywordValue>& KeywordValue::GetMonospace() {
   return non_trivial_static_fields.Get().monospace_value;
 }
@@ -327,6 +339,14 @@
   return non_trivial_static_fields.Get().static_value;
 }
 
+const scoped_refptr<KeywordValue>& KeywordValue::GetStereoscopicLeftRight() {
+  return non_trivial_static_fields.Get().stereoscopic_left_right_value;
+}
+
+const scoped_refptr<KeywordValue>& KeywordValue::GetStereoscopicTopBottom() {
+  return non_trivial_static_fields.Get().stereoscopic_top_bottom_value;
+}
+
 const scoped_refptr<KeywordValue>& KeywordValue::GetTop() {
   return non_trivial_static_fields.Get().top_value;
 }
@@ -407,6 +427,8 @@
       return kMiddleKeywordName;
     case kMonospace:
       return kMonospaceKeywordName;
+    case kMonoscopic:
+      return kMonoscopicKeywordName;
     case kNone:
       return kNoneKeywordName;
     case kNoRepeat:
@@ -439,6 +461,10 @@
       return kStartKeywordName;
     case kStatic:
       return kStaticKeywordName;
+    case kStereoscopicLeftRight:
+      return kStereoscopicLeftRightKeywordName;
+    case kStereoscopicTopBottom:
+      return kStereoscopicTopBottomKeywordName;
     case kTop:
       return kTopKeywordName;
     case kUppercase:
diff --git a/src/cobalt/cssom/keyword_value.h b/src/cobalt/cssom/keyword_value.h
index 2af8b00..bed4ba0 100644
--- a/src/cobalt/cssom/keyword_value.h
+++ b/src/cobalt/cssom/keyword_value.h
@@ -202,6 +202,10 @@
     //   https://www.w3.org/TR/css3-fonts/#generic-font-families
     kMonospace,
 
+    // "monoscopic" is a value of the "cobalt-mtm" property which indicates
+    // that the mesh should only be rendered through one eye.
+    kMonoscopic,
+
     // "none" is a value of "transform" property which means that HTML element
     // is rendered as is.
     //   https://www.w3.org/TR/css3-transforms/#transform-property
@@ -291,6 +295,15 @@
     //   https://www.w3.org/TR/CSS21/visuren.html#choose-position
     kStatic,
 
+    // "stereoscopic-left-right" is a value of the "cobalt-mtm" property which
+    // indicates that the mesh should be rendered in two views
+    // side-by-side.
+    kStereoscopicLeftRight,
+
+    // "stereoscopic-top-bottom" is a value of the "cobalt-mtm" property which
+    // indicates that the mesh should be rendered in two views above and below.
+    kStereoscopicTopBottom,
+
     // "top" is a value of "vertical-align" property that indicates that the
     // content should be aligned vertically at the top.
     //   https://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align
@@ -345,6 +358,7 @@
   static const scoped_refptr<KeywordValue>& GetLeft();
   static const scoped_refptr<KeywordValue>& GetLineThrough();
   static const scoped_refptr<KeywordValue>& GetMiddle();
+  static const scoped_refptr<KeywordValue>& GetMonoscopic();
   static const scoped_refptr<KeywordValue>& GetMonospace();
   static const scoped_refptr<KeywordValue>& GetNone();
   static const scoped_refptr<KeywordValue>& GetNoRepeat();
@@ -362,6 +376,8 @@
   static const scoped_refptr<KeywordValue>& GetSolid();
   static const scoped_refptr<KeywordValue>& GetStart();
   static const scoped_refptr<KeywordValue>& GetStatic();
+  static const scoped_refptr<KeywordValue>& GetStereoscopicLeftRight();
+  static const scoped_refptr<KeywordValue>& GetStereoscopicTopBottom();
   static const scoped_refptr<KeywordValue>& GetTop();
   static const scoped_refptr<KeywordValue>& GetUppercase();
   static const scoped_refptr<KeywordValue>& GetVisible();
diff --git a/src/cobalt/cssom/mtm_function.cc b/src/cobalt/cssom/mtm_function.cc
index ee51938..661060b 100644
--- a/src/cobalt/cssom/mtm_function.cc
+++ b/src/cobalt/cssom/mtm_function.cc
@@ -29,13 +29,16 @@
 MTMFunction::MTMFunction(
     const scoped_refptr<PropertyValue>& mesh_url,
     ResolutionMatchedMeshListBuilder resolution_matched_meshes,
-    float horizontal_fov, float vertical_fov, const glm::mat4& transform)
+    float horizontal_fov, float vertical_fov, const glm::mat4& transform,
+    const scoped_refptr<KeywordValue>& stereo_mode)
     : mesh_url_(mesh_url),
       resolution_matched_meshes_(resolution_matched_meshes.Pass()),
       horizontal_fov_(horizontal_fov),
       vertical_fov_(vertical_fov),
-      transform_(transform) {
+      transform_(transform),
+      stereo_mode_(stereo_mode) {
   DCHECK(mesh_url_);
+  DCHECK(stereo_mode_);
 }
 
 std::string MTMFunction::ToString() const {
@@ -65,7 +68,10 @@
       result.append(base::StringPrintf("%.7g", transform()[col][row]));
     }
   }
-  result.append("))");
+
+  result.append("), ");
+  result.append(stereo_mode()->ToString());
+  result.append(")");
 
   return result;
 }
@@ -114,7 +120,8 @@
 bool MTMFunction::operator==(const MTMFunction& rhs) const {
   if (!mesh_url()->Equals(*rhs.mesh_url()) ||
       horizontal_fov() != rhs.horizontal_fov() ||
-      horizontal_fov() != rhs.horizontal_fov()) {
+      horizontal_fov() != rhs.horizontal_fov() ||
+      !stereo_mode()->Equals(*rhs.stereo_mode())) {
     return false;
   }
   const ResolutionMatchedMeshListBuilder& lhs_meshes =
diff --git a/src/cobalt/cssom/mtm_function.h b/src/cobalt/cssom/mtm_function.h
index 59498b4..764c0cb 100644
--- a/src/cobalt/cssom/mtm_function.h
+++ b/src/cobalt/cssom/mtm_function.h
@@ -25,6 +25,7 @@
 #include "base/memory/scoped_vector.h"
 #include "cobalt/base/polymorphic_equatable.h"
 #include "cobalt/cssom/filter_function.h"
+#include "cobalt/cssom/keyword_value.h"
 #include "cobalt/cssom/property_value.h"
 #include "cobalt/cssom/url_value.h"
 #include "third_party/glm/glm/mat4x4.hpp"
@@ -56,7 +57,8 @@
   MTMFunction(const scoped_refptr<PropertyValue>& mesh_url,
               ResolutionMatchedMeshListBuilder resolution_matched_meshes,
               float horizontal_fov, float vertical_fov,
-              const glm::mat4& transform);
+              const glm::mat4& transform,
+              const scoped_refptr<KeywordValue>& stereo_mode);
 
   ~MTMFunction() OVERRIDE {}
 
@@ -68,6 +70,9 @@
   float horizontal_fov() const { return horizontal_fov_; }
   float vertical_fov() const { return vertical_fov_; }
   const glm::mat4& transform() const { return transform_; }
+  const scoped_refptr<KeywordValue>& stereo_mode() const {
+    return stereo_mode_;
+  }
 
   std::string ToString() const OVERRIDE;
 
@@ -83,6 +88,7 @@
   const float horizontal_fov_;
   const float vertical_fov_;
   const glm::mat4 transform_;
+  const scoped_refptr<KeywordValue> stereo_mode_;
 
   DISALLOW_COPY_AND_ASSIGN(MTMFunction);
 };
diff --git a/src/cobalt/cssom/property_value_is_equal_test.cc b/src/cobalt/cssom/property_value_is_equal_test.cc
index 0b647d0..263634b 100644
--- a/src/cobalt/cssom/property_value_is_equal_test.cc
+++ b/src/cobalt/cssom/property_value_is_equal_test.cc
@@ -604,12 +604,14 @@
       new URLValue("somemesh.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.70707f, 6.28f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 9.0f, 0.0f, 0.0f,
-                1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   filter_list_a.push_back(new MTMFunction(
       new URLValue("sphere.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.676f, 6.28f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetStereoscopicLeftRight()));
   scoped_refptr<FilterFunctionListValue> value_a(
       new FilterFunctionListValue(filter_list_a.Pass()));
 
@@ -618,12 +620,14 @@
       new URLValue("somemesh.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.70707f, 6.28f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 9.0f, 0.0f, 0.0f,
-                1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   filter_list_b.push_back(new MTMFunction(
       new URLValue("sphere.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.676f, 6.28f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetStereoscopicLeftRight()));
   scoped_refptr<FilterFunctionListValue> value_b(
       new FilterFunctionListValue(filter_list_b.Pass()));
 
@@ -636,7 +640,8 @@
       new URLValue("format.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f,
       glm::mat4(1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   scoped_refptr<FilterFunctionListValue> value_a(
       new FilterFunctionListValue(filter_list_a.Pass()));
 
@@ -645,7 +650,8 @@
       new URLValue("format.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   scoped_refptr<FilterFunctionListValue> value_b(
       new FilterFunctionListValue(filter_list_b.Pass()));
 
diff --git a/src/cobalt/cssom/property_value_to_string_test.cc b/src/cobalt/cssom/property_value_to_string_test.cc
index fd51fe9..7d18941 100644
--- a/src/cobalt/cssom/property_value_to_string_test.cc
+++ b/src/cobalt/cssom/property_value_to_string_test.cc
@@ -54,9 +54,9 @@
 namespace cssom {
 
 TEST(PropertyValueToStringTest, AbsoluteURLValue) {
-  GURL url("https://www.youtube.com");
+  GURL url("https://www.test.com");
   scoped_refptr<AbsoluteURLValue> property(new AbsoluteURLValue(url));
-  EXPECT_EQ(property->ToString(), "url(https://www.youtube.com/)");
+  EXPECT_EQ(property->ToString(), "url(https://www.test.com/)");
 }
 
 TEST(PropertyValueToStringTest, FontStyleValue) {
@@ -353,11 +353,13 @@
       new URLValue("-.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 2.5f, 3.14f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   EXPECT_EQ(
       "-cobalt-mtm(url(-.msh), "
       "2.5rad 3.14rad, "
-      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))",
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "monoscopic)",
       function->ToString());
 }
 
@@ -367,14 +369,16 @@
       1920, 2000000, new URLValue("a.msh")));
   meshes.push_back(
       new MTMFunction::ResolutionMatchedMesh(640, 5, new URLValue("b.msh")));
-  scoped_ptr<MTMFunction> function(new MTMFunction(
-      new URLValue("-.msh"), meshes.Pass(), 28.5f, 3.14f,
-      glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+  scoped_ptr<MTMFunction> function(
+      new MTMFunction(new URLValue("-.msh"), meshes.Pass(), 28.5f, 3.14f,
+                      glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+                                0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+                      KeywordValue::GetStereoscopicLeftRight()));
   EXPECT_EQ(
       "-cobalt-mtm(url(-.msh) 1920 2000000 url(a.msh) 640 5 url(b.msh), "
       "28.5rad 3.14rad, "
-      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))",
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "stereoscopic-left-right)",
       function->ToString());
 }
 
@@ -384,12 +388,26 @@
       new URLValue("-.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
   filter_list.push_back(new MTMFunction(
       new URLValue("world.msh"),
       MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f,
       glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)));
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetMonoscopic()));
+  filter_list.push_back(new MTMFunction(
+      new URLValue("stereoscopic-world.msh"),
+      MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f,
+      glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetStereoscopicLeftRight()));
+  filter_list.push_back(new MTMFunction(
+      new URLValue("stereoscopic-top-bottom-world.msh"),
+      MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f,
+      glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+      KeywordValue::GetStereoscopicTopBottom()));
 
   scoped_refptr<FilterFunctionListValue> property(
       new FilterFunctionListValue(filter_list.Pass()));
@@ -397,10 +415,20 @@
   EXPECT_EQ(
       "-cobalt-mtm(url(-.msh), "
       "8.5rad 3.14rad, "
-      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)) "
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "monoscopic) "
       "-cobalt-mtm(url(world.msh), "
       "8.5rad 39rad, "
-      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))",
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "monoscopic) "
+      "-cobalt-mtm(url(stereoscopic-world.msh), "
+      "8.5rad 39rad, "
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "stereoscopic-left-right) "
+      "-cobalt-mtm(url(stereoscopic-top-bottom-world.msh), "
+      "8.5rad 39rad, "
+      "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), "
+      "stereoscopic-top-bottom)",
       property->ToString());
 }
 
diff --git a/src/cobalt/cssom/property_value_visitor_test.cc b/src/cobalt/cssom/property_value_visitor_test.cc
index 550c8b9..1ff138d 100644
--- a/src/cobalt/cssom/property_value_visitor_test.cc
+++ b/src/cobalt/cssom/property_value_visitor_test.cc
@@ -121,7 +121,8 @@
 
   FilterFunctionListValue::Builder builder;
   builder.push_back(new MTMFunction(new URLValue("p.msh"), resMs.Pass(), 120,
-                                    60, glm::mat4(1.0f)));
+                                    60, glm::mat4(1.0f),
+                                    KeywordValue::GetMonoscopic()));
 
   scoped_refptr<FilterFunctionListValue> filter_list_value =
       new FilterFunctionListValue(builder.Pass());
diff --git a/src/cobalt/dom/Blob.idl b/src/cobalt/dom/Blob.idl
index aa0fcb6..edb972d 100644
--- a/src/cobalt/dom/Blob.idl
+++ b/src/cobalt/dom/Blob.idl
@@ -14,7 +14,16 @@
  * limitations under the License.
  */
 
-// https://www.w3.org/TR/FileAPI/#dfn-Blob
+// https://www.w3.org/TR/2015/WD-FileAPI-20150421/#dfn-Blob
 
-// Stub implementation of Blob so we can call "instanceof Blob".
-interface Blob {};
+[
+  //Constructor,
+  // TODO: When arrays or sequences are supported, support the constructor
+  // defined in the standard with multiple blobparts, and drop the single
+  // ArrayBuffer constructor.
+  Constructor(optional ArrayBuffer buffer),
+  ConstructorCallWith=EnvironmentSettings
+]
+interface Blob {
+  readonly attribute unsigned long long size;
+};
diff --git a/src/cobalt/dom/Int16Array.idl b/src/cobalt/dom/Int16Array.idl
new file mode 100644
index 0000000..3cc4d3c
--- /dev/null
+++ b/src/cobalt/dom/Int16Array.idl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// https://www.khronos.org/registry/typedarray/specs/latest/#7
+[
+  Constructor(unsigned long length),
+  Constructor(Int16Array array),
+  Constructor(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length),
+  ConstructorCallWith=EnvironmentSettings,
+  RaisesException=Constructor
+]
+interface Int16Array : ArrayBufferView {
+  const unsigned long BYTES_PER_ELEMENT = 2;
+
+  readonly attribute unsigned long length;
+
+  getter short get(unsigned long index);
+  setter void set(unsigned long index, short value);
+
+  // TODO: When arrays or sequences are supported, support them here.
+  // void set(sequence<long> array, optional unsigned long offset);
+
+  // Copy items into this array from source, starting at this[offset].
+  [RaisesException] void set(Int16Array source, optional unsigned long offset);
+
+  // Return a new Int16Array that is a view on top of this one.
+  // Contains this[start]..this[end]. end defaults to length if unspecified.
+  [CallWith=EnvironmentSettings] Int16Array subarray(long start, optional long end);
+};
diff --git a/src/cobalt/dom/URL.idl b/src/cobalt/dom/URL.idl
index 0273f57..8784d1d 100644
--- a/src/cobalt/dom/URL.idl
+++ b/src/cobalt/dom/URL.idl
@@ -19,5 +19,7 @@
 interface URL {
   [CallWith=EnvironmentSettings] static DOMString
       createObjectURL(MediaSource mediaSource);
+  [CallWith=EnvironmentSettings] static DOMString
+      createObjectURL(Blob blob);
   [CallWith=EnvironmentSettings] static void revokeObjectURL(DOMString url);
 };
diff --git a/src/cobalt/dom/blob.h b/src/cobalt/dom/blob.h
index 8f11422..d677e04 100644
--- a/src/cobalt/dom/blob.h
+++ b/src/cobalt/dom/blob.h
@@ -17,15 +17,45 @@
 #ifndef COBALT_DOM_BLOB_H_
 #define COBALT_DOM_BLOB_H_
 
+#include <vector>
+
+#include "cobalt/dom/array_buffer.h"
+#include "cobalt/dom/url_registry.h"
+#include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
+#include "googleurl/src/gurl.h"
 
 namespace cobalt {
 namespace dom {
 
-// Stub implementation of Blob so we can call "instanceof Blob".
+// A Blob object refers to a byte sequence, and has a size attribute which is
+// the total number of bytes in the byte sequence, and a type attribute, which
+// is an ASCII-encoded string in lower case representing the media type of the
+// byte sequence.
+//    https://www.w3.org/TR/2015/WD-FileAPI-20150421/#dfn-Blob
+//
+// Note: Cobalt currently does not implement nor need the type attribute.
 class Blob : public script::Wrappable {
  public:
+  typedef UrlRegistry<Blob> Registry;
+
+  Blob(script::EnvironmentSettings* settings,
+       const scoped_refptr<ArrayBuffer>& buffer = NULL)
+      : buffer_(
+            buffer ? buffer->Slice(settings, 0)
+                   : scoped_refptr<ArrayBuffer>(new ArrayBuffer(settings, 0))) {
+  }
+
+  const uint8* data() { return buffer_->data(); }
+
+  uint64 size() { return static_cast<uint64>(buffer_->byte_length()); }
+
   DEFINE_WRAPPABLE_TYPE(Blob);
+
+ private:
+  scoped_refptr<ArrayBuffer> buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(Blob);
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/blob_test.cc b/src/cobalt/dom/blob_test.cc
new file mode 100644
index 0000000..5d252da
--- /dev/null
+++ b/src/cobalt/dom/blob_test.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+
+#include "cobalt/dom/blob.h"
+#include "cobalt/dom/data_view.h"
+#include "cobalt/script/testing/mock_exception_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+namespace {
+
+using script::testing::MockExceptionState;
+using testing::_;
+using testing::SaveArg;
+using testing::StrictMock;
+
+TEST(BlobTest, Constructors) {
+  scoped_refptr<Blob> blob_default_buffer = new Blob(NULL);
+
+  EXPECT_EQ(0, blob_default_buffer->size());
+
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<ArrayBuffer> array_buffer = new ArrayBuffer(NULL, 5);
+  scoped_refptr<DataView> data_view =
+      new DataView(array_buffer, &exception_state);
+  data_view->SetInt16(0, static_cast<int16>(0x0607), &exception_state);
+
+  scoped_refptr<Blob> blob_with_buffer = new Blob(NULL, array_buffer);
+
+  ASSERT_EQ(5, blob_with_buffer->size());
+  ASSERT_TRUE(blob_with_buffer->data());
+
+  EXPECT_EQ(0x6, blob_with_buffer->data()[0]);
+  EXPECT_EQ(0x7, blob_with_buffer->data()[1]);
+  EXPECT_EQ(0, blob_with_buffer->data()[2]);
+  EXPECT_EQ(0, blob_with_buffer->data()[3]);
+  EXPECT_EQ(0, blob_with_buffer->data()[4]);
+}
+
+// Tests that further changes to a buffer from which a blob was constructed
+// no longer affect the blob's buffer, since it must be a separate copy of
+// its construction arguments.
+TEST(BlobTest, HasOwnBuffer) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<ArrayBuffer> array_buffer = new ArrayBuffer(NULL, 2);
+  scoped_refptr<DataView> data_view =
+      new DataView(array_buffer, &exception_state);
+  data_view->SetInt16(0, static_cast<int16>(0x0607), &exception_state);
+
+  scoped_refptr<Blob> blob_with_buffer = new Blob(NULL, array_buffer);
+
+  ASSERT_EQ(2, blob_with_buffer->size());
+  ASSERT_TRUE(blob_with_buffer->data());
+  EXPECT_NE(array_buffer->data(), blob_with_buffer->data());
+
+  data_view->SetUint8(1, static_cast<uint8>(0xff), &exception_state);
+
+  EXPECT_EQ(0x6, blob_with_buffer->data()[0]);
+  EXPECT_EQ(0x7, blob_with_buffer->data()[1]);
+}
+
+}  // namespace
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 6087406..4ffeaa6 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -234,6 +234,7 @@
         'uint8_array.h',
         'url.cc',
         'url.h',
+        'url_registry.h',
         'url_utils.cc',
         'url_utils.h',
         'window.cc',
diff --git a/src/cobalt/dom/dom_settings.cc b/src/cobalt/dom/dom_settings.cc
index 1eca2a2..0db0ac9 100644
--- a/src/cobalt/dom/dom_settings.cc
+++ b/src/cobalt/dom/dom_settings.cc
@@ -21,22 +21,22 @@
 namespace cobalt {
 namespace dom {
 
-DOMSettings::DOMSettings(const int max_dom_element_depth,
-                         loader::FetcherFactory* fetcher_factory,
-                         network::NetworkModule* network_module,
-                         const scoped_refptr<Window>& window,
-                         MediaSource::Registry* media_source_registry,
-                         media::CanPlayTypeHandler* can_play_type_handler,
-                         script::JavaScriptEngine* engine,
-                         script::GlobalEnvironment* global_environment,
-                         const Options& options)
+DOMSettings::DOMSettings(
+    const int max_dom_element_depth, loader::FetcherFactory* fetcher_factory,
+    network::NetworkModule* network_module, const scoped_refptr<Window>& window,
+    MediaSource::Registry* media_source_registry, Blob::Registry* blob_registry,
+    media::CanPlayTypeHandler* can_play_type_handler,
+    script::JavaScriptEngine* engine,
+    script::GlobalEnvironment* global_environment, const Options& options)
     : max_dom_element_depth_(max_dom_element_depth),
+      enable_fake_microphone_(options.enable_fake_microphone),
       fetcher_factory_(fetcher_factory),
       network_module_(network_module),
       window_(window),
       array_buffer_allocator_(options.array_buffer_allocator),
       array_buffer_cache_(options.array_buffer_cache),
       media_source_registry_(media_source_registry),
+      blob_registry_(blob_registry),
       can_play_type_handler_(can_play_type_handler),
       javascript_engine_(engine),
       global_environment_(global_environment) {
diff --git a/src/cobalt/dom/dom_settings.h b/src/cobalt/dom/dom_settings.h
index 5bd39e1..492d21b 100644
--- a/src/cobalt/dom/dom_settings.h
+++ b/src/cobalt/dom/dom_settings.h
@@ -20,6 +20,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
 #include "cobalt/dom/array_buffer.h"
+#include "cobalt/dom/blob.h"
 #include "cobalt/dom/media_source.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/media/can_play_type_handler.h"
@@ -45,7 +46,10 @@
  public:
   // Hold optional settings for DOMSettings.
   struct Options {
-    Options() : array_buffer_allocator(NULL), array_buffer_cache(NULL) {}
+    Options()
+        : array_buffer_allocator(NULL),
+          array_buffer_cache(NULL),
+          enable_fake_microphone(false) {}
 
     // ArrayBuffer allocates its memory on the heap by default and ArrayBuffers
     // may occupy a lot of memory.  It is possible to provide an allocator via
@@ -56,6 +60,8 @@
     // amount of ArrayBuffer inside main memory.  So we have provide the
     // following cache to manage ArrayBuffer in main memory.
     ArrayBuffer::Cache* array_buffer_cache;
+    // Use fake microphone if this flag is set to true.
+    bool enable_fake_microphone;
   };
 
   DOMSettings(const int max_dom_element_depth,
@@ -63,6 +69,7 @@
               network::NetworkModule* network_module,
               const scoped_refptr<Window>& window,
               MediaSource::Registry* media_source_registry,
+              Blob::Registry* blob_registry,
               media::CanPlayTypeHandler* can_play_type_handler,
               script::JavaScriptEngine* engine,
               script::GlobalEnvironment* global_environment_proxy,
@@ -70,6 +77,7 @@
   ~DOMSettings() OVERRIDE;
 
   int max_dom_element_depth() { return max_dom_element_depth_; }
+  bool enable_fake_microphone() const { return enable_fake_microphone_; }
 
   void set_window(const scoped_refptr<Window>& window) { window_ = window; }
   scoped_refptr<Window> window() const { return window_; }
@@ -100,18 +108,21 @@
   media::CanPlayTypeHandler* can_play_type_handler() const {
     return can_play_type_handler_;
   }
+  Blob::Registry* blob_registry() const { return blob_registry_; }
 
   // An absolute URL used to resolve relative URLs.
   virtual GURL base_url() const;
 
  private:
   const int max_dom_element_depth_;
+  const bool enable_fake_microphone_;
   loader::FetcherFactory* fetcher_factory_;
   network::NetworkModule* network_module_;
   scoped_refptr<Window> window_;
   ArrayBuffer::Allocator* array_buffer_allocator_;
   ArrayBuffer::Cache* array_buffer_cache_;
   MediaSource::Registry* media_source_registry_;
+  Blob::Registry* blob_registry_;
   media::CanPlayTypeHandler* can_play_type_handler_;
   script::JavaScriptEngine* javascript_engine_;
   script::GlobalEnvironment* global_environment_;
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 281ac59..b96ce65 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -24,6 +24,7 @@
       'target_name': 'dom_test',
       'type': '<(gtest_target_type)',
       'sources': [
+        'blob_test.cc',
         'comment_test.cc',
         'crypto_test.cc',
         'csp_delegate_test.cc',
diff --git a/src/cobalt/dom/event_listener.cc b/src/cobalt/dom/event_listener.cc
index 45da194..4d9959e 100644
--- a/src/cobalt/dom/event_listener.cc
+++ b/src/cobalt/dom/event_listener.cc
@@ -59,8 +59,9 @@
       snprintf(event_log_stack_[current_stack_depth_], kLogEntryMaxLength,
                "%s@%s", event->type().c_str(),
                event->current_target()->GetDebugName().c_str());
-    } else {
-      NOTREACHED();
+    } else if (current_stack_depth_ == base::UserLog::kEventStackMaxDepth) {
+      DLOG(WARNING) << "Reached maximum depth of " << kLogEntryMaxLength
+                    << ". Subsequent events will not be logged.";
     }
     current_stack_depth_++;
   }
@@ -68,7 +69,9 @@
   void PopEvent() {
     DCHECK(current_stack_depth_);
     current_stack_depth_--;
-    memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength);
+    if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) {
+      memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength);
+    }
   }
 
  private:
diff --git a/src/cobalt/dom/font_face_updater.cc b/src/cobalt/dom/font_face_updater.cc
index dbcd212..16de45f 100644
--- a/src/cobalt/dom/font_face_updater.cc
+++ b/src/cobalt/dom/font_face_updater.cc
@@ -158,6 +158,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kNone:
     case cssom::KeywordValue::kNoRepeat:
     case cssom::KeywordValue::kNormal:
@@ -172,6 +173,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index eda59cb..a2851ee 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -57,26 +57,11 @@
 
 #if LOG_MEDIA_ELEMENT_ACTIVITIES
 
-template <typename T>
-void LogMediaElementActivity(const char* function_name, const T& t) {
-  LOG(INFO) << function_name << " " << t;
-}
-
-template <typename T1, typename T2>
-void LogMediaElementActivity(const char* function_name, const T1& t1,
-                             const T2& t2) {
-  LOG(INFO) << function_name << " " << t1 << " " << t2;
-}
-
-#define MLOG() LOG(INFO) << __FUNCTION__
-#define MLOG_1(x) LogMediaElementActivity(__FUNCTION__, x)
-#define MLOG_2(x, y) LogMediaElementActivity(__FUNCTION__, x, y)
+#define MLOG() LOG(INFO) << __FUNCTION__ << ": "
 
 #else  // LOG_MEDIA_ELEMENT_ACTIVITIES
 
-#define MLOG() do {} while (false)
-#define MLOG_1(x) do {} while (false)
-#define MLOG_2(x, y) do {} while (false)
+#define MLOG() EAT_STREAM_PARAMETERS
 
 #endif  // LOG_MEDIA_ELEMENT_ACTIVITIES
 
@@ -157,24 +142,24 @@
 }
 
 scoped_refptr<MediaError> HTMLMediaElement::error() const {
-  MLOG_1(error_->code());
+  MLOG() << error_->code();
   return error_;
 }
 
 std::string HTMLMediaElement::src() const {
-  MLOG_1(GetAttribute("src").value_or(""));
+  MLOG() << GetAttribute("src").value_or("");
   return GetAttribute("src").value_or("");
 }
 
 void HTMLMediaElement::set_src(const std::string& src) {
-  MLOG_1(src);
+  MLOG() << src;
   SetAttribute("src", src);
   ClearMediaPlayer();
   ScheduleLoad();
 }
 
 uint16_t HTMLMediaElement::network_state() const {
-  MLOG_1(network_state_);
+  MLOG() << network_state_;
   return static_cast<uint16_t>(network_state_);
 }
 
@@ -182,17 +167,17 @@
   scoped_refptr<TimeRanges> buffered = new TimeRanges;
 
   if (!player_) {
-    MLOG_1("empty");
+    MLOG() << "(empty)";
     return buffered;
   }
 
   const ::media::Ranges<base::TimeDelta>& player_buffered =
       player_->GetBufferedTimeRanges();
 
-  MLOG_1("================================");
+  MLOG() << "================================";
   for (int i = 0; i < static_cast<int>(player_buffered.size()); ++i) {
-    MLOG_2(player_buffered.start(i).InSecondsF(),
-           player_buffered.end(i).InSecondsF());
+    MLOG() << player_buffered.start(i).InSecondsF() << " - "
+           << player_buffered.end(i).InSecondsF();
     buffered->Add(player_buffered.start(i).InSecondsF(),
                   player_buffered.end(i).InSecondsF());
   }
@@ -218,7 +203,7 @@
   std::string result =
       html_element_context()->can_play_type_handler()->CanPlayType(mime_type,
                                                                    key_system);
-  MLOG_2(mime_type + ',' + key_system, result);
+  MLOG() << "(" << mime_type << ", " << key_system << ") => " << result;
   DLOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
              << key_system << ") -> " << result;
   return result;
@@ -228,18 +213,18 @@
     const std::string& key_system,
     const base::optional<scoped_refptr<Uint8Array> >& init_data,
     script::ExceptionState* exception_state) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-generatekeyrequest
   // 1. If the first argument is null, throw a SYNTAX_ERR.
   if (key_system.empty()) {
-    MLOG_1("syntax error");
+    MLOG() << "syntax error";
     DOMException::Raise(DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   // 2. If networkState is NETWORK_EMPTY, throw an INVALID_STATE_ERR.
   if (network_state_ == kNetworkEmpty || !player_) {
-    MLOG_1("invalid state error");
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -256,7 +241,7 @@
   }
 
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
-    MLOG_2("exception:", exception);
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
@@ -266,25 +251,25 @@
     const base::optional<scoped_refptr<Uint8Array> >& init_data,
     const base::optional<std::string>& session_id,
     script::ExceptionState* exception_state) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-addkey
   // 1. If the first or second argument is null, throw a SYNTAX_ERR.
   if (key_system.empty() || !key) {
-    MLOG_1("syntax error");
+    MLOG() << "syntax error";
     DOMException::Raise(DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   // 2. If the second argument is an empty array, throw a TYPE_MISMATCH_ERR.
   if (!key->length()) {
-    MLOG_1("type mismatch error");
+    MLOG() << "type mismatch error";
     DOMException::Raise(DOMException::kTypeMismatchErr, exception_state);
     return;
   }
 
   // 3. If networkState is NETWORK_EMPTY, throw an INVALID_STATE_ERR.
   if (network_state_ == kNetworkEmpty || !player_) {
-    MLOG_1("invalid state error");
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -303,7 +288,7 @@
   }
 
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
-    MLOG_2("exception:", exception);
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
@@ -315,13 +300,13 @@
   // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-addkey
   // 1. If the first argument is null, throw a SYNTAX_ERR.
   if (key_system.empty()) {
-    MLOG_1("syntax error");
+    MLOG() << "syntax error";
     DOMException::Raise(DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   if (!player_) {
-    MLOG_1("invalid state error");
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -330,18 +315,18 @@
   WebMediaPlayer::MediaKeyException exception =
       player_->CancelKeyRequest(key_system, session_id.value_or(""));
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
-    MLOG_2("exception:", exception);
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
 
 WebMediaPlayer::ReadyState HTMLMediaElement::ready_state() const {
-  MLOG_1(ready_state_);
+  MLOG() << ready_state_;
   return ready_state_;
 }
 
 bool HTMLMediaElement::seeking() const {
-  MLOG_1(seeking_);
+  MLOG() << seeking_;
   return seeking_;
 }
 
@@ -350,17 +335,18 @@
   UNREFERENCED_PARAMETER(exception_state);
 
   if (!player_) {
-    MLOG_2("player is NULL", 0);
+    MLOG() << 0 << " (because player is NULL)";
     return 0;
   }
 
   if (seeking_) {
-    MLOG_2("seeking", last_seek_time_);
+    MLOG() << last_seek_time_ << " (seeking)";
     return last_seek_time_;
   }
 
-  MLOG_2("player time", player_->GetCurrentTime());
-  return player_->GetCurrentTime();
+  float time = player_->GetCurrentTime();
+  MLOG() << time << " (from player)";
+  return time;
 }
 
 void HTMLMediaElement::set_current_time(
@@ -371,36 +357,37 @@
   // WebMediaPlayer::kReadyStateHaveNothing, then raise an INVALID_STATE_ERR
   // exception.
   if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing || !player_) {
-    MLOG_1("invalid state error");
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
-  MLOG_2("seek to", time);
+  MLOG() << "seek to " << time;
   Seek(time);
 }
 
 float HTMLMediaElement::duration() const {
   if (player_ && ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata) {
-    MLOG_2("player duration", player_->GetDuration());
-    return player_->GetDuration();
+    float duration = player_->GetDuration();
+    MLOG() << "player duration: " << duration;
+    return duration;
   }
 
-  MLOG_1("NaN");
+  MLOG() << "NaN";
   return std::numeric_limits<float>::quiet_NaN();
 }
 
 bool HTMLMediaElement::paused() const {
-  MLOG_1(paused_);
+  MLOG() << paused_;
   return paused_;
 }
 
 float HTMLMediaElement::default_playback_rate() const {
-  MLOG_1(default_playback_rate_);
+  MLOG() << default_playback_rate_;
   return default_playback_rate_;
 }
 
 void HTMLMediaElement::set_default_playback_rate(float rate) {
-  MLOG_1(rate);
+  MLOG() << rate;
   if (default_playback_rate_ != rate) {
     default_playback_rate_ = rate;
     ScheduleEvent(base::Tokens::ratechange());
@@ -408,12 +395,12 @@
 }
 
 float HTMLMediaElement::playback_rate() const {
-  MLOG_1(playback_rate_);
+  MLOG() << playback_rate_;
   return playback_rate_;
 }
 
 void HTMLMediaElement::set_playback_rate(float rate) {
-  MLOG_1(rate);
+  MLOG() << rate;
   if (playback_rate_ != rate) {
     playback_rate_ = rate;
     ScheduleEvent(base::Tokens::ratechange());
@@ -442,10 +429,11 @@
 
 scoped_refptr<TimeRanges> HTMLMediaElement::seekable() const {
   if (player_ && player_->GetMaxTimeSeekable() != 0) {
-    MLOG_2(0, player_->GetMaxTimeSeekable());
-    return new TimeRanges(0, player_->GetMaxTimeSeekable());
+    double max_time_seekable = player_->GetMaxTimeSeekable();
+    MLOG() << "(0, " << max_time_seekable << ")";
+    return new TimeRanges(0, max_time_seekable);
   }
-  MLOG_1("empty");
+  MLOG() << "(empty)";
   return new TimeRanges;
 }
 
@@ -453,17 +441,18 @@
   // 4.8.10.8 Playing the media resource
   // The ended attribute must return true if the media element has ended
   // playback and the direction of playback is forwards, and false otherwise.
-  MLOG_1(EndedPlayback() && playback_rate_ > 0);
-  return EndedPlayback() && playback_rate_ > 0;
+  bool playback_ended = EndedPlayback() && playback_rate_ > 0;
+  MLOG() << playback_ended;
+  return playback_ended;
 }
 
 bool HTMLMediaElement::autoplay() const {
-  MLOG_1(HasAttribute("autoplay"));
+  MLOG() << HasAttribute("autoplay");
   return HasAttribute("autoplay");
 }
 
 void HTMLMediaElement::set_autoplay(bool autoplay) {
-  MLOG_1(autoplay);
+  MLOG() << autoplay;
   // The value of 'autoplay' is true when the 'autoplay' attribute is present.
   // The value of the attribute is irrelevant.
   if (autoplay) {
@@ -474,12 +463,12 @@
 }
 
 bool HTMLMediaElement::loop() const {
-  MLOG_1(loop_);
+  MLOG() << loop_;
   return loop_;
 }
 
 void HTMLMediaElement::set_loop(bool loop) {
-  MLOG_1(loop);
+  MLOG() << loop;
   loop_ = loop;
 }
 
@@ -528,25 +517,25 @@
 }
 
 bool HTMLMediaElement::controls() const {
-  MLOG_1(controls_);
+  MLOG() << controls_;
   return controls_;
 }
 
 void HTMLMediaElement::set_controls(bool controls) {
-  MLOG_1(controls);
+  MLOG() << controls_;
   controls_ = controls;
   ConfigureMediaControls();
 }
 
 float HTMLMediaElement::volume(script::ExceptionState* exception_state) const {
   UNREFERENCED_PARAMETER(exception_state);
-  MLOG_1(volume_);
+  MLOG() << volume_;
   return volume_;
 }
 
 void HTMLMediaElement::set_volume(float volume,
                                   script::ExceptionState* exception_state) {
-  MLOG_1(volume);
+  MLOG() << volume;
   if (volume < 0.0f || volume > 1.0f) {
     DOMException::Raise(DOMException::kIndexSizeErr, exception_state);
     return;
@@ -560,12 +549,12 @@
 }
 
 bool HTMLMediaElement::muted() const {
-  MLOG_1(muted_);
+  MLOG() << muted_;
   return muted_;
 }
 
 void HTMLMediaElement::set_muted(bool muted) {
-  MLOG_1(muted);
+  MLOG() << muted;
   if (muted_ != muted) {
     muted_ = muted;
     // Avoid recursion when the player reports volume changes.
@@ -976,7 +965,7 @@
 }
 
 void HTMLMediaElement::ScheduleEvent(base::Token event_name) {
-  MLOG_1(event_name);
+  MLOG() << event_name;
   scoped_refptr<Event> event =
       new Event(event_name, Event::kNotBubbles, Event::kCancelable);
   event->set_target(this);
@@ -1330,7 +1319,7 @@
 }
 
 void HTMLMediaElement::MediaEngineError(scoped_refptr<MediaError> error) {
-  MLOG_1(error->code());
+  MLOG() << error->code();
   DLOG(WARNING) << "HTMLMediaElement::MediaEngineError " << error->code();
 
   // 1 - The user agent should cancel the fetching process.
@@ -1474,7 +1463,7 @@
 
 void HTMLMediaElement::KeyAdded(const std::string& key_system,
                                 const std::string& session_id) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   event_queue_.Enqueue(new MediaKeyCompleteEvent(key_system, session_id));
 }
 
@@ -1482,7 +1471,7 @@
                                 const std::string& session_id,
                                 MediaKeyErrorCode error_code,
                                 uint16 system_code) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   MediaKeyError::Code code;
   switch (error_code) {
     case kUnknownError:
@@ -1517,7 +1506,7 @@
                                   const unsigned char* message,
                                   unsigned int message_length,
                                   const std::string& default_url) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   event_queue_.Enqueue(new MediaKeyMessageEvent(
       key_system, session_id,
       new Uint8Array(NULL, message, message_length, NULL), default_url));
@@ -1527,14 +1516,14 @@
                                  const std::string& session_id,
                                  const unsigned char* init_data,
                                  unsigned int init_data_length) {
-  MLOG_1(key_system);
+  MLOG() << key_system;
   event_queue_.Enqueue(new MediaKeyNeededEvent(
       key_system, session_id,
       new Uint8Array(NULL, init_data, init_data_length, NULL)));
 }
 
 void HTMLMediaElement::SetSourceState(MediaSource::ReadyState ready_state) {
-  MLOG_1(ready_state);
+  MLOG() << ready_state;
   if (!media_source_) {
     return;
   }
diff --git a/src/cobalt/dom/int16_array.h b/src/cobalt/dom/int16_array.h
new file mode 100644
index 0000000..a4a2821
--- /dev/null
+++ b/src/cobalt/dom/int16_array.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_DOM_INT16_ARRAY_H_
+#define COBALT_DOM_INT16_ARRAY_H_
+
+#include "cobalt/dom/typed_array.h"
+
+namespace cobalt {
+namespace dom {
+
+DEFINE_TYPED_ARRAY(Int16Array, int16);
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_INT16_ARRAY_H_
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index f20ffb0..83bc8c0 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -73,35 +73,6 @@
 
 }  // namespace
 
-void MediaSource::Registry::Register(
-    const std::string& blob_url,
-    const scoped_refptr<MediaSource>& media_source) {
-  DCHECK(media_source);
-  DCHECK(media_source_registry_.find(blob_url) == media_source_registry_.end());
-  media_source_registry_.insert(std::make_pair(blob_url, media_source));
-}
-
-scoped_refptr<MediaSource> MediaSource::Registry::Retrieve(
-    const std::string& blob_url) {
-  MediaSourceRegistry::iterator iter = media_source_registry_.find(blob_url);
-  if (iter == media_source_registry_.end()) {
-    DLOG(WARNING) << "Cannot find MediaSource object for blob url " << blob_url;
-    return NULL;
-  }
-
-  return iter->second;
-}
-
-void MediaSource::Registry::Unregister(const std::string& blob_url) {
-  MediaSourceRegistry::iterator iter = media_source_registry_.find(blob_url);
-  if (iter == media_source_registry_.end()) {
-    DLOG(WARNING) << "Cannot find MediaSource object for blob url " << blob_url;
-    return;
-  }
-
-  media_source_registry_.erase(iter);
-}
-
 MediaSource::MediaSource()
     : ready_state_(kReadyStateClosed),
       player_(NULL),
diff --git a/src/cobalt/dom/media_source.h b/src/cobalt/dom/media_source.h
index a30e5c3..b8aa55a 100644
--- a/src/cobalt/dom/media_source.h
+++ b/src/cobalt/dom/media_source.h
@@ -25,6 +25,7 @@
 #include "cobalt/dom/event_target.h"
 #include "cobalt/dom/source_buffer.h"
 #include "cobalt/dom/source_buffer_list.h"
+#include "cobalt/dom/url_registry.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "media/player/web_media_player.h"
@@ -42,27 +43,7 @@
 // Note that our implementation is based on the MSE draft on 09 August 2012.
 class MediaSource : public EventTarget {
  public:
-  // This class manages a registry of MediaSource objects.  Its user can
-  // associate a MediaSource object to a blob url as well as to retrieve a
-  // MediaSource object by a blob url.  This is because to assign a MediaSource
-  // object to the src of an HTMLMediaElement, we have to first convert the
-  // MediaSource object into a blob url by calling URL.createObjectURL().
-  // And eventually the HTMLMediaElement have to retrieve the MediaSource object
-  // from the blob url.
-  // Note: It is unsafe to directly encode the pointer to the MediaSource object
-  // in the url as the url is assigned from JavaScript.
-  class Registry {
-   public:
-    void Register(const std::string& blob_url,
-                  const scoped_refptr<MediaSource>& media_source);
-    scoped_refptr<MediaSource> Retrieve(const std::string& blob_url);
-    void Unregister(const std::string& blob_url);
-
-   private:
-    typedef base::hash_map<std::string, scoped_refptr<MediaSource> >
-        MediaSourceRegistry;
-    MediaSourceRegistry media_source_registry_;
-  };
+  typedef UrlRegistry<MediaSource> Registry;
 
   // Custom, not in any spec.
   //
diff --git a/src/cobalt/dom/time_ranges_test.cc b/src/cobalt/dom/time_ranges_test.cc
index 4404477..cc8f12f 100644
--- a/src/cobalt/dom/time_ranges_test.cc
+++ b/src/cobalt/dom/time_ranges_test.cc
@@ -33,7 +33,14 @@
   void SetSimpleException(script::MessageType /*message_type*/, ...) OVERRIDE {
     // no-op
   }
-  scoped_refptr<DOMException> dom_exception_;
+  dom::DOMException::ExceptionCode GetExceptionCode() {
+    return dom_exception_ ? static_cast<dom::DOMException::ExceptionCode>(
+                                dom_exception_->code())
+                          : dom::DOMException::kNone;
+  }
+
+ private:
+  scoped_refptr<dom::DOMException> dom_exception_;
 };
 }  // namespace
 
@@ -180,16 +187,12 @@
   {
     FakeExceptionState exception_state;
     time_ranges->Start(2, &exception_state);
-    ASSERT_TRUE(exception_state.dom_exception_);
-    EXPECT_EQ(DOMException::kIndexSizeErr,
-              exception_state.dom_exception_->code());
+    EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode());
   }
   {
     FakeExceptionState exception_state;
     time_ranges->End(2, &exception_state);
-    ASSERT_TRUE(exception_state.dom_exception_);
-    EXPECT_EQ(DOMException::kIndexSizeErr,
-              exception_state.dom_exception_->code());
+    EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode());
   }
 }
 
diff --git a/src/cobalt/dom/url.cc b/src/cobalt/dom/url.cc
index 7388773..911c619 100644
--- a/src/cobalt/dom/url.cc
+++ b/src/cobalt/dom/url.cc
@@ -50,6 +50,24 @@
 }
 
 // static
+std::string URL::CreateObjectURL(
+    script::EnvironmentSettings* environment_settings,
+    const scoped_refptr<Blob>& blob) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(environment_settings);
+  DCHECK(dom_settings);
+  DCHECK(dom_settings->blob_registry());
+  if (!blob) {
+    return "";
+  }
+
+  std::string blob_url = kBlobUrlProtocol;
+  blob_url += ':' + base::GenerateGUID();
+  dom_settings->blob_registry()->Register(blob_url, blob);
+  return blob_url;
+}
+
+// static
 void URL::RevokeObjectURL(script::EnvironmentSettings* environment_settings,
                           const std::string& url) {
   DOMSettings* dom_settings =
@@ -69,7 +87,26 @@
 
   // 2. Otherwise, user agents must remove the entry from the Blob URL Store for
   // url.
-  dom_settings->media_source_registry()->Unregister(url);
+  if (!dom_settings->media_source_registry()->Unregister(url) &&
+      !dom_settings->blob_registry()->Unregister(url)) {
+    DLOG(WARNING) << "Cannot find object for blob url " << url;
+  }
+}
+
+bool URL::BlobResolver(dom::Blob::Registry* registry, const GURL& url,
+                       const char** data, size_t* size) {
+  DCHECK(data);
+  DCHECK(size);
+  dom::Blob* blob = registry->Retrieve(url.spec()).get();
+
+  if (blob) {
+    *size = static_cast<size_t>(blob->size());
+    *data = reinterpret_cast<const char*>(blob->data());
+
+    return true;
+  } else {
+    return false;
+  }
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom/url.h b/src/cobalt/dom/url.h
index f2286aa..6899612 100644
--- a/src/cobalt/dom/url.h
+++ b/src/cobalt/dom/url.h
@@ -19,7 +19,9 @@
 
 #include <string>
 
+#include "cobalt/dom/blob.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/loader/blob_fetcher.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
 
@@ -34,18 +36,28 @@
 // The Media Source Extension extends it to create an url from a MediaSource
 // object so we can assign it to HTMLMediaElement.src.
 //   https://rawgit.com/w3c/media-source/cfb1b3d4309a6e6e2c01bd87e048758172a86e4b/media-source.html#dom-createobjecturl
-//
-// Note: We only implemented the MediaSource related functions as they are all
-// what we need for Cobalt.
 class URL : public script::Wrappable {
  public:
   static std::string CreateObjectURL(
       script::EnvironmentSettings* environment_settings,
       const scoped_refptr<MediaSource>& media_source);
+  static std::string CreateObjectURL(
+      script::EnvironmentSettings* environment_settings,
+      const scoped_refptr<Blob>& blob);
   static void RevokeObjectURL(script::EnvironmentSettings* environment_settings,
                               const std::string& url);
 
+  static loader::BlobFetcher::ResolverCallback MakeBlobResolverCallback(
+      dom::Blob::Registry* blob_registry) {
+    DCHECK(blob_registry);
+    return base::Bind(&BlobResolver, blob_registry);
+  }
+
   DEFINE_WRAPPABLE_TYPE(URL);
+
+ private:
+  static bool BlobResolver(dom::Blob::Registry* registry, const GURL& url,
+                           const char** data, size_t* size);
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/url_registry.h b/src/cobalt/dom/url_registry.h
new file mode 100644
index 0000000..3619762
--- /dev/null
+++ b/src/cobalt/dom/url_registry.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_DOM_URL_REGISTRY_H_
+#define COBALT_DOM_URL_REGISTRY_H_
+
+#include <string>
+
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+
+namespace cobalt {
+namespace dom {
+
+// This class manages a registry of objects.  Its user can associate a
+// object to a blob url as well as to retrieve a object by a blob url.
+// This is because to assign a object to the src of an HTMLMediaElement
+// (when the object is MediaSource) or to another kind of element or CSS
+// property (when the object is a Blob), we have to first convert the
+// object into a blob url by calling URL.createObjectURL(). And eventually
+// the element or property has to retrieve the object from the blob url.
+// Note: It is unsafe to directly encode the pointer to the object in the
+// url as the url is assigned from JavaScript.
+template <typename ObjectType>
+class UrlRegistry {
+ public:
+  void Register(const std::string& blob_url,
+                const scoped_refptr<ObjectType>& object);
+  scoped_refptr<ObjectType> Retrieve(const std::string& blob_url);
+  bool Unregister(const std::string& blob_url);
+
+ private:
+  typedef base::hash_map<std::string, scoped_refptr<ObjectType> > UrlMap;
+  UrlMap object_registry_;
+};
+
+template <typename ObjectType>
+void UrlRegistry<ObjectType>::Register(
+    const std::string& blob_url, const scoped_refptr<ObjectType>& object) {
+  DCHECK(object);
+  DCHECK(object_registry_.find(blob_url) == object_registry_.end());
+  object_registry_.insert(std::make_pair(blob_url, object));
+}
+
+template <typename ObjectType>
+scoped_refptr<ObjectType> UrlRegistry<ObjectType>::Retrieve(
+    const std::string& blob_url) {
+  typename UrlMap::iterator iter = object_registry_.find(blob_url);
+  if (iter == object_registry_.end()) {
+    DLOG(WARNING) << "Cannot find object for blob url " << blob_url;
+    return NULL;
+  }
+
+  return iter->second;
+}
+
+template <typename ObjectType>
+bool UrlRegistry<ObjectType>::Unregister(const std::string& blob_url) {
+  typename UrlMap::iterator iter = object_registry_.find(blob_url);
+  if (iter == object_registry_.end()) {
+    return false;
+  }
+
+  object_registry_.erase(iter);
+  return true;
+}
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_URL_REGISTRY_H_
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc
index 92736ea..70019a1 100644
--- a/src/cobalt/dom_parser/html_decoder_test.cc
+++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -382,6 +382,34 @@
   EXPECT_EQ("💩", text->data());
 }
 
+TEST_F(HTMLDecoderTest, CanParseUTF8SplitInChunks) {
+  const std::string input = "<p>💩</p>";
+
+  for (size_t first_chunk_size = 0; first_chunk_size < input.length();
+       first_chunk_size++) {
+    root_ = new dom::Element(document_, base::Token("element"));
+    html_decoder_.reset(new HTMLDecoder(
+        document_, root_, NULL, kDOMMaxElementDepth, source_location_,
+        base::Closure(), base::Bind(&MockErrorCallback::Run,
+                                    base::Unretained(&mock_error_callback_)),
+        true));
+
+    // This could cut the input in the middle of a UTF8 character.
+    html_decoder_->DecodeChunk(input.c_str(), first_chunk_size);
+    html_decoder_->DecodeChunk(input.c_str() + first_chunk_size,
+                               input.length() - first_chunk_size);
+    html_decoder_->Finish();
+
+    dom::Element* element = root_->first_element_child();
+    ASSERT_TRUE(element);
+    EXPECT_EQ("p", element->tag_name());
+
+    dom::Text* text = element->first_child()->AsText();
+    ASSERT_TRUE(text);
+    EXPECT_EQ("💩", text->data());
+  }
+}
+
 // Misnested tags: <b><i></b></i>
 //   https://www.w3.org/TR/html5/syntax.html#misnested-tags:-b-i-/b-/i
 //
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
index 968dbe5..7a05cb8 100644
--- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
@@ -110,7 +110,10 @@
     return;
   }
 
-  if (CheckInputAndUpdateSeverity(data, size) == kFatal) {
+  std::string current_chunk;
+  PreprocessChunk(data, size, &current_chunk);
+
+  if (max_severity() == kFatal) {
     return;
   }
 
@@ -120,9 +123,10 @@
     // when used for setting an element's innerHTML.
     htmlEmitImpliedRootLevelParagraph(0);
 
-    html_parser_context_ = htmlCreatePushParserCtxt(
-        &html_sax_handler, this, data, static_cast<int>(size),
-        NULL /*filename*/, XML_CHAR_ENCODING_UTF8);
+    html_parser_context_ =
+        htmlCreatePushParserCtxt(&html_sax_handler, this, current_chunk.c_str(),
+                                 static_cast<int>(current_chunk.size()),
+                                 NULL /*filename*/, XML_CHAR_ENCODING_UTF8);
 
     if (!html_parser_context_) {
       static const char kErrorUnableCreateParser[] =
@@ -135,7 +139,8 @@
     }
   } else {
     DCHECK(html_parser_context_);
-    htmlParseChunk(html_parser_context_, data, static_cast<int>(size),
+    htmlParseChunk(html_parser_context_, current_chunk.c_str(),
+                   static_cast<int>(current_chunk.size()),
                    0 /*do not terminate*/);
   }
 }
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
index ef19c762..a86753e 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -19,6 +19,8 @@
 #include "base/logging.h"
 #include "base/string_util.h"
 #include "base/stringprintf.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "base/utf_string_conversion_utils.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/cdata_section.h"
 #include "cobalt/dom/comment.h"
@@ -314,34 +316,32 @@
   node_stack_.top()->AppendChild(new dom::CDATASection(document_, value));
 }
 
-LibxmlParserWrapper::IssueSeverity
-LibxmlParserWrapper::CheckInputAndUpdateSeverity(const char* data,
-                                                 size_t size) {
-  if (max_severity_ == kFatal) {
-    return max_severity_;
-  }
-
+void LibxmlParserWrapper::PreprocessChunk(const char* data, size_t size,
+                                          std::string* current_chunk) {
+  DCHECK(current_chunk);
   // Check the total input size.
   total_input_size_ += size;
   if (total_input_size_ > kMaxTotalInputSize) {
     static const char kMessageInputTooLong[] = "Parser input is too long.";
     OnParsingIssue(kFatal, kMessageInputTooLong);
-    return max_severity_;
+    return;
   }
 
   // Check the encoding of the input.
-  if (!IsStringUTF8(std::string(data, size))) {
+  std::string input = next_chunk_start_ + std::string(data, size);
+  TruncateUTF8ToByteSize(input, input.size(), current_chunk);
+  next_chunk_start_ = input.substr(current_chunk->size());
+  if (!IsStringUTF8(*current_chunk)) {
+    current_chunk->clear();
     static const char kMessageInputNotUTF8[] =
         "Parser input contains non-UTF8 characters.";
     OnParsingIssue(kFatal, kMessageInputNotUTF8);
-    return max_severity_;
+    return;
   }
 
 #if defined(HANDLE_CORE_DUMP)
   libxml_parser_wrapper_log.Get().IncrementParsedBytes(static_cast<int>(size));
 #endif
-
-  return max_severity_;
 }
 
 }  // namespace dom_parser
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.h b/src/cobalt/dom_parser/libxml_parser_wrapper.h
index 98d210e..4c5e233 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.h
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.h
@@ -121,8 +121,10 @@
   // Returns true when the input is a full document, false when it's a fragment.
   bool IsFullDocument() { return document_ == parent_node_; }
 
-  // Checks the input, updates and returns the maximum issue severity.
-  IssueSeverity CheckInputAndUpdateSeverity(const char* data, size_t size);
+  // Preprocesses the input chunk and updates the max error severity level.
+  // Sets current chunk if successful.
+  void PreprocessChunk(const char* data, size_t size,
+                       std::string* current_chunk);
 
   const scoped_refptr<dom::Document>& document() { return document_; }
   const base::SourceLocation& first_chunk_location() {
@@ -136,6 +138,8 @@
     return node_stack_;
   }
 
+  IssueSeverity max_severity() const { return max_severity_; }
+
  private:
   // Maximum total input size, as specified in Libxml's value
   // XML_MAX_TEXT_LENGTH in parserInternals.h.
@@ -154,6 +158,7 @@
   IssueSeverity max_severity_;
   size_t total_input_size_;
 
+  std::string next_chunk_start_;
   std::stack<scoped_refptr<dom::Node> > node_stack_;
 
   DISALLOW_COPY_AND_ASSIGN(LibxmlParserWrapper);
diff --git a/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc
index 787970d..dfbb290 100644
--- a/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc
@@ -79,14 +79,17 @@
     return;
   }
 
-  if (CheckInputAndUpdateSeverity(data, size) == kFatal) {
+  std::string current_chunk;
+  PreprocessChunk(data, size, &current_chunk);
+
+  if (max_severity() == kFatal) {
     return;
   }
 
   if (!xml_parser_context_) {
-    xml_parser_context_ =
-        xmlCreatePushParserCtxt(&xml_sax_handler, this, data,
-                                static_cast<int>(size), NULL /*filename*/);
+    xml_parser_context_ = xmlCreatePushParserCtxt(
+        &xml_sax_handler, this, current_chunk.c_str(),
+        static_cast<int>(current_chunk.size()), NULL /*filename*/);
 
     if (!xml_parser_context_) {
       static const char kErrorUnableCreateParser[] =
@@ -94,7 +97,8 @@
       OnParsingIssue(kFatal, kErrorUnableCreateParser);
     }
   } else {
-    xmlParseChunk(xml_parser_context_, data, static_cast<int>(size),
+    xmlParseChunk(xml_parser_context_, current_chunk.c_str(),
+                  static_cast<int>(current_chunk.size()),
                   0 /*do not terminate*/);
   }
 }
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc
index d19d55e..7b646c0 100644
--- a/src/cobalt/layout/box_generator.cc
+++ b/src/cobalt/layout/box_generator.cc
@@ -247,6 +247,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kMonospace:
     case cssom::KeywordValue::kNoRepeat:
     case cssom::KeywordValue::kNormal:
@@ -263,6 +264,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
@@ -541,6 +544,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kMonospace:
     case cssom::KeywordValue::kNoRepeat:
     case cssom::KeywordValue::kNormal:
@@ -557,6 +561,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
@@ -688,6 +694,7 @@
       case cssom::KeywordValue::kLeft:
       case cssom::KeywordValue::kLineThrough:
       case cssom::KeywordValue::kMiddle:
+      case cssom::KeywordValue::kMonoscopic:
       case cssom::KeywordValue::kMonospace:
       case cssom::KeywordValue::kNoRepeat:
       case cssom::KeywordValue::kNoWrap:
@@ -703,6 +710,8 @@
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
       case cssom::KeywordValue::kStatic:
+      case cssom::KeywordValue::kStereoscopicLeftRight:
+      case cssom::KeywordValue::kStereoscopicTopBottom:
       case cssom::KeywordValue::kTop:
       case cssom::KeywordValue::kUppercase:
       case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index 1be9e1a..7bcb96a 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -60,6 +60,25 @@
 // means, as per https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width.
 const float kFallbackWidth = 300.0f;
 
+// Convert the parsed keyword value for a stereo mode into a stereo mode enum
+// value.
+render_tree::StereoMode ReadStereoMode(
+    const scoped_refptr<cssom::KeywordValue>& keyword_value) {
+  cssom::KeywordValue::Value value = keyword_value->value();
+
+  if (value == cssom::KeywordValue::kMonoscopic) {
+    return render_tree::kMono;
+  } else if (value == cssom::KeywordValue::kStereoscopicLeftRight) {
+    return render_tree::kLeftRight;
+  } else if (value == cssom::KeywordValue::kStereoscopicTopBottom) {
+    return render_tree::kTopBottom;
+  } else {
+    LOG(DFATAL) << "Stereo mode has an invalid non-NULL value, defaulting to "
+                << "monoscopic";
+    return render_tree::kMono;
+  }
+}
+
 }  // namespace
 
 ReplacedBox::ReplacedBox(
@@ -172,10 +191,10 @@
   return GetMarginBoxHeight();
 }
 
-namespace {
-
 #if !PUNCH_THROUGH_VIDEO_RENDERING
 
+namespace {
+
 void AddLetterboxFillRects(const LetterboxDimensions& dimensions,
                            CompositionNode::Builder* composition_node_builder) {
   const render_tree::ColorRGBA kSolidBlack(0, 0, 0, 1);
@@ -200,10 +219,7 @@
   AddLetterboxFillRects(dimensions, composition_node_builder);
 }
 
-#endif  // !PUNCH_THROUGH_VIDEO_RENDERING
-
 void AnimateCB(const ReplacedBox::ReplaceImageCB& replace_image_cb,
-               const ReplacedBox::SetBoundsCB& set_bounds_cb,
                math::SizeF destination_size,
                CompositionNode::Builder* composition_node_builder,
                base::TimeDelta time) {
@@ -212,14 +228,6 @@
   DCHECK(!replace_image_cb.is_null());
   DCHECK(composition_node_builder);
 
-#if PUNCH_THROUGH_VIDEO_RENDERING
-  // For systems that have their own path to blitting video to the display, we
-  // simply punch a hole through our scene so that the video can appear there.
-  PunchThroughVideoNode::Builder builder(math::RectF(destination_size),
-                                         set_bounds_cb);
-  composition_node_builder->AddChild(new PunchThroughVideoNode(builder));
-#else   // PUNCH_THROUGH_VIDEO_RENDERING
-  UNREFERENCED_PARAMETER(set_bounds_cb);
   scoped_refptr<render_tree::Image> image = replace_image_cb.Run();
 
   // TODO: Detect better when the intrinsic video size is used for the
@@ -234,11 +242,12 @@
         GetLetterboxDimensions(image->GetSize(), destination_size), image,
         composition_node_builder);
   }
-#endif  // PUNCH_THROUGH_VIDEO_RENDERING
 }
 
 }  // namespace
 
+#endif  // !PUNCH_THROUGH_VIDEO_RENDERING
+
 void ReplacedBox::RenderAndAnimateContent(
     CompositionNode::Builder* border_node_builder) const {
   if (computed_style()->visibility() != cssom::KeywordValue::GetVisible()) {
@@ -257,19 +266,35 @@
   scoped_refptr<CompositionNode> composition_node =
       new CompositionNode(composition_node_builder);
 
+#if PUNCH_THROUGH_VIDEO_RENDERING
+  // For systems that have their own path to blitting video to the display, we
+  // simply punch a hole through our scene so that the video can appear there.
+  PunchThroughVideoNode::Builder builder(math::RectF(content_box_size()),
+                                         set_bounds_cb_);
+  Node* frame_node = new PunchThroughVideoNode(builder);
+#else
   AnimateNode::Builder animate_node_builder;
   animate_node_builder.Add(
-      composition_node, base::Bind(AnimateCB, replace_image_cb_, set_bounds_cb_,
+      composition_node, base::Bind(AnimateCB, replace_image_cb_,
                                    content_box_size()));
 
-  Node* animate_node = new AnimateNode(animate_node_builder, composition_node);
+  Node* frame_node = new AnimateNode(animate_node_builder, composition_node);
+#endif  // PUNCH_THROUGH_VIDEO_RENDERING
 
   const cssom::MTMFunction* mtm_filter_function =
       cssom::MTMFunction::ExtractFromFilterList(computed_style()->filter());
 
-  border_node_builder->AddChild(
-      mtm_filter_function ? new FilterNode(MapToMeshFilter(), animate_node)
-                          : animate_node);
+  Node* content_node;
+  if (mtm_filter_function) {
+    const scoped_refptr<cssom::KeywordValue>& stereo_mode_keyword_value =
+        mtm_filter_function->stereo_mode();
+    render_tree::StereoMode stereo_mode =
+        ReadStereoMode(stereo_mode_keyword_value);
+    content_node = new FilterNode(MapToMeshFilter(stereo_mode), frame_node);
+  } else {
+    content_node = frame_node;
+  }
+  border_node_builder->AddChild(content_node);
 }
 
 void ReplacedBox::UpdateContentSizeAndMargins(
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc
index 02227e0..f252231 100644
--- a/src/cobalt/layout/used_style.cc
+++ b/src/cobalt/layout/used_style.cc
@@ -267,6 +267,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kMonospace:
     case cssom::KeywordValue::kNone:
     case cssom::KeywordValue::kNoRepeat:
@@ -284,6 +285,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
@@ -356,6 +359,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kNone:
     case cssom::KeywordValue::kNoRepeat:
     case cssom::KeywordValue::kNormal:
@@ -370,6 +374,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
@@ -1163,6 +1169,7 @@
     case cssom::KeywordValue::kLeft:
     case cssom::KeywordValue::kLineThrough:
     case cssom::KeywordValue::kMiddle:
+    case cssom::KeywordValue::kMonoscopic:
     case cssom::KeywordValue::kMonospace:
     case cssom::KeywordValue::kNone:
     case cssom::KeywordValue::kNoRepeat:
@@ -1180,6 +1187,8 @@
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
+    case cssom::KeywordValue::kStereoscopicLeftRight:
+    case cssom::KeywordValue::kStereoscopicTopBottom:
     case cssom::KeywordValue::kTop:
     case cssom::KeywordValue::kUppercase:
     case cssom::KeywordValue::kVisible:
@@ -1341,6 +1350,7 @@
       case cssom::KeywordValue::kLeft:
       case cssom::KeywordValue::kLineThrough:
       case cssom::KeywordValue::kMiddle:
+      case cssom::KeywordValue::kMonoscopic:
       case cssom::KeywordValue::kMonospace:
       case cssom::KeywordValue::kNone:
       case cssom::KeywordValue::kNoRepeat:
@@ -1358,6 +1368,8 @@
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
       case cssom::KeywordValue::kStatic:
+      case cssom::KeywordValue::kStereoscopicLeftRight:
+      case cssom::KeywordValue::kStereoscopicTopBottom:
       case cssom::KeywordValue::kTop:
       case cssom::KeywordValue::kUppercase:
       case cssom::KeywordValue::kVisible:
@@ -1411,6 +1423,7 @@
       case cssom::KeywordValue::kLeft:
       case cssom::KeywordValue::kLineThrough:
       case cssom::KeywordValue::kMiddle:
+      case cssom::KeywordValue::kMonoscopic:
       case cssom::KeywordValue::kMonospace:
       case cssom::KeywordValue::kNoRepeat:
       case cssom::KeywordValue::kNormal:
@@ -1427,6 +1440,8 @@
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
       case cssom::KeywordValue::kStatic:
+      case cssom::KeywordValue::kStereoscopicLeftRight:
+      case cssom::KeywordValue::kStereoscopicTopBottom:
       case cssom::KeywordValue::kTop:
       case cssom::KeywordValue::kUppercase:
       case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png
new file mode 100644
index 0000000..7c0e8e1
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html
new file mode 100644
index 0000000..aa33681
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<!--
+ | Loading an image from a Blob created from a buffer in memory.
+ -->
+<html>
+<head>
+  <style>
+    div {
+      width: 500px;
+      height: 400px;
+    }
+  </style>
+</head>
+<body>
+  <div id='image'></div>
+  <script>
+    if (window.testRunner) {
+      window.testRunner.waitUntilDone();
+    }
+
+    var image_bytes = [
+        137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0,
+        6, 0, 0, 0, 6, 8, 2, 0, 0, 0, 111, 174, 120, 31, 0, 0, 0, 3, 115, 66,
+        73, 84, 8, 8, 8, 219, 225, 79, 224, 0, 0, 0, 25, 116, 69, 88, 116, 83,
+        111, 102, 116, 119, 97, 114, 101, 0, 103, 110, 111, 109, 101, 45, 115,
+        99, 114, 101, 101, 110, 115, 104, 111, 116, 239, 3, 191, 62, 0, 0, 0,
+        36, 73, 68, 65, 84, 8, 153, 99, 252, 80, 238, 198, 128, 10, 88, 24, 24,
+        24, 154, 119, 190, 132, 243, 107, 221, 197, 153, 24, 48, 0, 113, 66,
+        140, 152, 198, 3, 0, 225, 21, 6, 103, 179, 203, 171, 64, 0, 0, 0, 0,
+        73, 69, 78, 68, 174, 66, 96, 130];
+
+    // TODO(jsalvadorp): When TypedArray supports setting from a Javascript
+    // array, replace the following loop.
+    var image_array = new Uint8Array(image_bytes.length);
+    for (var i = 0; i < image_array.length; i++) {
+      image_array[i] = image_bytes[i];
+    }
+
+    // TODO(jsalvadorp): When the proper Blob constructor that takes an array
+    // of arguments is supported, change the following to reflect the new
+    // signature.
+    var image_blob = new Blob(image_array.buffer);
+    var image_url = URL.createObjectURL(image_blob);
+
+    var image = new Image();
+    image.onload = function() {
+      var cobalt = document.getElementById('image');
+      cobalt.style.background = 'url(' + image_url + ')';
+
+      if (window.testRunner) {
+        window.testRunner.notifyDone();
+      }
+    }
+    image.src = image_url;
+  </script>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
index e336781..03292a0 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
@@ -8,6 +8,7 @@
 divs-with-background-color-and-text
 fixed-width-divs-with-background-color
 font-weight
+image-from-blob
 inline-box-with-overflow-words
 inline-style-allowed-while-cloning-objects
 onload_event_fired_even_though_link_file_does_not_exist
diff --git a/src/cobalt/loader/blob_fetcher.cc b/src/cobalt/loader/blob_fetcher.cc
new file mode 100644
index 0000000..d6a48b8
--- /dev/null
+++ b/src/cobalt/loader/blob_fetcher.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/loader/blob_fetcher.h"
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+
+namespace cobalt {
+namespace loader {
+
+BlobFetcher::BlobFetcher(const GURL& url, Handler* handler,
+                         const ResolverCallback& resolver_callback)
+    : Fetcher(handler),
+      url_(url),
+      resolver_callback_(resolver_callback),
+      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
+  DCHECK(!resolver_callback_.is_null());
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&BlobFetcher::Fetch, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BlobFetcher::Fetch() {
+  size_t buffer_size = 0;
+  const char* buffer_data = NULL;
+
+  if (resolver_callback_.Run(url_, &buffer_data, &buffer_size)) {
+    if (buffer_size > 0) {
+      handler()->OnReceived(this, buffer_data, buffer_size);
+    }
+    handler()->OnDone(this);
+  } else {
+    handler()->OnError(this, "Blob URL not found in object store.");
+  }
+}
+
+BlobFetcher::~BlobFetcher() {}
+
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/blob_fetcher.h b/src/cobalt/loader/blob_fetcher.h
new file mode 100644
index 0000000..aa8f991
--- /dev/null
+++ b/src/cobalt/loader/blob_fetcher.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_LOADER_BLOB_FETCHER_H_
+#define COBALT_LOADER_BLOB_FETCHER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "cobalt/loader/fetcher.h"
+
+namespace cobalt {
+namespace loader {
+
+// For fetching the 'blob:' scheme.
+class BlobFetcher : public Fetcher {
+ public:
+  // Returns true if the blob is succesfully fetched, and only then writes
+  // the address of the buffer and its size to |data| and |size|. |data| can
+  // be NULL when size is 0. This callback avoids a dependency from the
+  // fetcher to the actual blob implementation.
+  typedef base::Callback<bool(const GURL& url, const char** data, size_t* size)>
+      ResolverCallback;
+
+  explicit BlobFetcher(const GURL& url, Handler* handler,
+                       const ResolverCallback& resolver_callback);
+
+  void Fetch();
+
+  ~BlobFetcher() OVERRIDE;
+
+ private:
+  void GetData();
+
+  GURL url_;
+  ResolverCallback resolver_callback_;
+  base::WeakPtrFactory<BlobFetcher> weak_ptr_factory_;
+};
+
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_BLOB_FETCHER_H_
diff --git a/src/cobalt/loader/blob_fetcher_test.cc b/src/cobalt/loader/blob_fetcher_test.cc
new file mode 100644
index 0000000..a1e1fd6
--- /dev/null
+++ b/src/cobalt/loader/blob_fetcher_test.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/loader/blob_fetcher.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "cobalt/loader/fetcher_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AtLeast;
+using ::testing::InSequence;
+using ::testing::StrictMock;
+using ::testing::_;
+
+namespace cobalt {
+namespace loader {
+namespace {
+
+typedef std::map<std::string, std::vector<char> > TestRegistry;
+
+bool TestResolver(const TestRegistry& registry, const GURL& url,
+                  const char** data, size_t* size) {
+  DCHECK(data);
+  DCHECK(size);
+
+  TestRegistry::const_iterator match = registry.find(url.spec());
+  if (match != registry.end()) {
+    const std::vector<char>& buffer = match->second;
+    *size = buffer.size();
+    *data = &buffer[0];
+    return true;
+  } else {
+    return false;
+  }
+}
+
+TEST(BlobFetcherTest, NonExistentBlobURL) {
+  MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
+  base::RunLoop run_loop;
+
+  StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop);
+  EXPECT_CALL(fetcher_handler_mock, OnError(_, _));
+
+  TestRegistry registry;
+
+  scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr(
+      new loader::BlobFetcher(GURL("blob:sd98sdfuh8sdh"), &fetcher_handler_mock,
+                              base::Bind(&TestResolver, registry)));
+
+  run_loop.Run();
+
+  EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher());
+}
+
+TEST(BlobFetcherTest, EmptyBlob) {
+  MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
+  base::RunLoop run_loop;
+  InSequence dummy;
+
+  StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop);
+  EXPECT_CALL(fetcher_handler_mock, OnDone(_));
+
+  const char* url = "blob:28y3-fsdaf-dsfa";
+
+  TestRegistry registry;
+  registry[url];
+
+  scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr(
+      new loader::BlobFetcher(GURL(url), &fetcher_handler_mock,
+                              base::Bind(&TestResolver, registry)));
+
+  run_loop.Run();
+
+  EXPECT_EQ(0, fetcher_handler_mock.data().size());
+
+  EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher());
+}
+
+TEST(BlobFetcherTest, ValidBlob) {
+  MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
+  base::RunLoop run_loop;
+  InSequence dummy;
+
+  StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop);
+  EXPECT_CALL(fetcher_handler_mock, OnReceived(_, _, _)).Times(AtLeast(1));
+  EXPECT_CALL(fetcher_handler_mock, OnDone(_));
+
+  const char* url = "blob:28y3-fsdaf-dsfa";
+
+  TestRegistry registry;
+  std::vector<char>& buffer = registry[url];
+  buffer.push_back('a');
+  buffer.push_back(0);
+  buffer.push_back(7);
+
+  scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr(
+      new loader::BlobFetcher(GURL(url), &fetcher_handler_mock,
+                              base::Bind(&TestResolver, registry)));
+
+  run_loop.Run();
+
+  const std::string& data = fetcher_handler_mock.data();
+  ASSERT_EQ(3, data.size());
+  EXPECT_EQ('a', data[0]);
+  EXPECT_EQ(0, data[1]);
+  EXPECT_EQ(7, data[2]);
+
+  EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher());
+}
+
+}  // namespace
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/decoder.h b/src/cobalt/loader/decoder.h
index bdd4040..2a0adcb 100644
--- a/src/cobalt/loader/decoder.h
+++ b/src/cobalt/loader/decoder.h
@@ -17,6 +17,9 @@
 #ifndef COBALT_LOADER_DECODER_H_
 #define COBALT_LOADER_DECODER_H_
 
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
 #include "cobalt/loader/loader_types.h"
 #include "cobalt/render_tree/resource_provider.h"
 #include "net/http/http_response_headers.h"
@@ -43,6 +46,16 @@
   // This is the interface that chunks of bytes can be sent in to be decoded.
   virtual void DecodeChunk(const char* data, size_t size) = 0;
 
+  // A decoder can choose to implement |DecodeChunkPassed| in order to take
+  // ownership of the chunk data.  Taking ownership over the chunk data can
+  // allow data copies to be avoided, such as when passing the data off to be
+  // decoded asynchronously.  Not all fetchers are guaranteed to support this
+  // though, in which case they simply forward the scoped pointer data to
+  // DecodeChunk.
+  virtual void DecodeChunkPassed(scoped_ptr<std::string> data) {
+    DecodeChunk(data->data(), data->size());
+  }
+
   // This is called when all data are sent in and decoding should be finalized.
   virtual void Finish() = 0;
 
diff --git a/src/cobalt/loader/fetcher.h b/src/cobalt/loader/fetcher.h
index 4ca6d3f..e998f44 100644
--- a/src/cobalt/loader/fetcher.h
+++ b/src/cobalt/loader/fetcher.h
@@ -46,6 +46,14 @@
     virtual void OnDone(Fetcher* fetcher) = 0;
     virtual void OnError(Fetcher* fetcher, const std::string& error) = 0;
 
+    // By default, |OnReceivedPassed| forwards the scoped_ptr<std::string>
+    // data into |OnReceived|.  Implementations have the opportunity to hold
+    // onto the scoped_ptr through overriding |OnReceivedPassed|.
+    virtual void OnReceivedPassed(Fetcher* fetcher,
+                                  scoped_ptr<std::string> data) {
+      OnReceived(fetcher, data->data(), data->length());
+    }
+
    protected:
     Handler() {}
     virtual ~Handler() {}
diff --git a/src/cobalt/loader/fetcher_factory.cc b/src/cobalt/loader/fetcher_factory.cc
index e16c970..6b478e3 100644
--- a/src/cobalt/loader/fetcher_factory.cc
+++ b/src/cobalt/loader/fetcher_factory.cc
@@ -23,6 +23,7 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "cobalt/loader/about_fetcher.h"
+#include "cobalt/loader/blob_fetcher.h"
 #include "cobalt/loader/embedded_fetcher.h"
 #include "cobalt/loader/file_fetcher.h"
 #include "cobalt/loader/net_fetcher.h"
@@ -69,6 +70,16 @@
   file_thread_.Start();
 }
 
+FetcherFactory::FetcherFactory(
+    network::NetworkModule* network_module, const FilePath& extra_search_dir,
+    const BlobFetcher::ResolverCallback& blob_resolver)
+    : file_thread_("File"),
+      network_module_(network_module),
+      extra_search_dir_(extra_search_dir),
+      blob_resolver_(blob_resolver) {
+  file_thread_.Start();
+}
+
 scoped_ptr<Fetcher> FetcherFactory::CreateFetcher(const GURL& url,
                                                   Fetcher::Handler* handler) {
   return CreateSecureFetcher(url, csp::SecurityCallback(), handler).Pass();
@@ -104,7 +115,15 @@
     fetcher.reset(new AboutFetcher(handler));
   }
 #endif
-  else {  // NOLINT(readability/braces)
+  else if (url.SchemeIs("blob")) {  // NOLINT(readability/braces)
+    if (!blob_resolver_.is_null()) {
+      fetcher.reset(new BlobFetcher(url, handler, blob_resolver_));
+    } else {
+      LOG(ERROR) << "Fetcher factory not provided the blob registry, "
+                    "could not fetch the URL: "
+                 << url;
+    }
+  } else {  // NOLINT(readability/braces)
     DCHECK(network_module_) << "Network module required.";
     NetFetcher::Options options;
     fetcher.reset(new NetFetcher(url, url_security_callback, handler,
diff --git a/src/cobalt/loader/fetcher_factory.h b/src/cobalt/loader/fetcher_factory.h
index 3dd64a5..e4f5661 100644
--- a/src/cobalt/loader/fetcher_factory.h
+++ b/src/cobalt/loader/fetcher_factory.h
@@ -18,8 +18,10 @@
 #define COBALT_LOADER_FETCHER_FACTORY_H_
 
 #include "base/file_path.h"
+#include "base/optional.h"
 #include "base/threading/thread.h"
 #include "cobalt/csp/content_security_policy.h"
+#include "cobalt/loader/blob_fetcher.h"
 #include "cobalt/loader/fetcher.h"
 #include "googleurl/src/gurl.h"
 
@@ -35,6 +37,9 @@
   explicit FetcherFactory(network::NetworkModule* network_module);
   FetcherFactory(network::NetworkModule* network_module,
                  const FilePath& extra_search_dir);
+  FetcherFactory(network::NetworkModule* network_module,
+                 const FilePath& extra_search_dir,
+                 const BlobFetcher::ResolverCallback& blob_resolver);
 
   // Creates a fetcher. Returns NULL if the creation fails.
   scoped_ptr<Fetcher> CreateFetcher(const GURL& url, Fetcher::Handler* handler);
@@ -48,6 +53,7 @@
   base::Thread file_thread_;
   network::NetworkModule* network_module_;
   FilePath extra_search_dir_;
+  BlobFetcher::ResolverCallback blob_resolver_;
 };
 
 }  // namespace loader
diff --git a/src/cobalt/loader/fetcher_test.h b/src/cobalt/loader/fetcher_test.h
new file mode 100644
index 0000000..0b92378
--- /dev/null
+++ b/src/cobalt/loader/fetcher_test.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_LOADER_FETCHER_TEST_H_
+#define COBALT_LOADER_FETCHER_TEST_H_
+
+#include "cobalt/loader/fetcher.h"
+
+#include <string>
+
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::Invoke;
+using ::testing::_;
+
+namespace cobalt {
+namespace loader {
+
+class FetcherHandlerForTest : public Fetcher::Handler {
+ public:
+  explicit FetcherHandlerForTest(base::RunLoop* run_loop)
+      : fetcher_(NULL), run_loop_(run_loop) {}
+
+  // From Fetcher::Handler.
+  void OnReceived(Fetcher* fetcher, const char* data, size_t size) OVERRIDE {
+    CheckFetcher(fetcher);
+    data_.append(data, size);
+  }
+  void OnDone(Fetcher* fetcher) OVERRIDE {
+    CheckFetcher(fetcher);
+    MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
+  }
+  void OnError(Fetcher* fetcher, const std::string& error) OVERRIDE {
+    UNREFERENCED_PARAMETER(error);
+    CheckFetcher(fetcher);
+    MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
+  }
+
+  const std::string& data() const { return data_; }
+  Fetcher* fetcher() const { return fetcher_; }
+
+ private:
+  void CheckFetcher(Fetcher* fetcher) {
+    EXPECT_TRUE(fetcher);
+    if (fetcher_ == NULL) {
+      fetcher_ = fetcher;
+      return;
+    }
+    EXPECT_EQ(fetcher_, fetcher);
+  }
+
+  std::string data_;
+  Fetcher* fetcher_;
+  base::RunLoop* run_loop_;
+};
+
+class MockFetcherHandler : public Fetcher::Handler {
+ public:
+  explicit MockFetcherHandler(base::RunLoop* run_loop) : real_(run_loop) {
+    ON_CALL(*this, OnReceived(_, _, _))
+        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnReceived));
+    ON_CALL(*this, OnDone(_))
+        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnDone));
+    ON_CALL(*this, OnError(_, _))
+        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnError));
+  }
+
+  MOCK_METHOD2(OnResponseStarted,
+               LoadResponseType(
+                   Fetcher*, const scoped_refptr<net::HttpResponseHeaders>&));
+  MOCK_METHOD3(OnReceived, void(Fetcher*, const char*, size_t));
+  MOCK_METHOD1(OnDone, void(Fetcher*));
+  MOCK_METHOD2(OnError, void(Fetcher*, const std::string&));
+
+  const std::string& data() const { return real_.data(); }
+  Fetcher* fetcher() const { return real_.fetcher(); }
+
+ private:
+  FetcherHandlerForTest real_;
+};
+
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_FETCHER_TEST_H_
diff --git a/src/cobalt/loader/file_fetcher_test.cc b/src/cobalt/loader/file_fetcher_test.cc
index 932584a..0b22f71 100644
--- a/src/cobalt/loader/file_fetcher_test.cc
+++ b/src/cobalt/loader/file_fetcher_test.cc
@@ -22,83 +22,17 @@
 #include "base/message_loop.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "cobalt/loader/fetcher_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::AtLeast;
 using ::testing::InSequence;
-using ::testing::Invoke;
 using ::testing::StrictMock;
 using ::testing::_;
 
 namespace cobalt {
 namespace loader {
-namespace {
-
-class FetcherHandlerForTest : public Fetcher::Handler {
- public:
-  explicit FetcherHandlerForTest(base::RunLoop* run_loop)
-      : fetcher_(NULL), run_loop_(run_loop) {}
-
-  // From Fetcher::Handler.
-  void OnReceived(Fetcher* fetcher, const char* data, size_t size) OVERRIDE {
-    CheckFetcher(fetcher);
-    data_.append(data, size);
-  }
-  void OnDone(Fetcher* fetcher) OVERRIDE {
-    CheckFetcher(fetcher);
-    MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
-  }
-  void OnError(Fetcher* fetcher, const std::string& error) OVERRIDE {
-    UNREFERENCED_PARAMETER(error);
-    CheckFetcher(fetcher);
-    MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure());
-  }
-
-  const std::string& data() const { return data_; }
-  Fetcher* fetcher() const { return fetcher_; }
-
- private:
-  void CheckFetcher(Fetcher* fetcher) {
-    EXPECT_TRUE(fetcher);
-    if (fetcher_ == NULL) {
-      fetcher_ = fetcher;
-      return;
-    }
-    EXPECT_EQ(fetcher_, fetcher);
-  }
-
-  std::string data_;
-  Fetcher* fetcher_;
-  base::RunLoop* run_loop_;
-};
-
-class MockFetcherHandler : public Fetcher::Handler {
- public:
-  explicit MockFetcherHandler(base::RunLoop* run_loop) : real_(run_loop) {
-    ON_CALL(*this, OnReceived(_, _, _))
-        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnReceived));
-    ON_CALL(*this, OnDone(_))
-        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnDone));
-    ON_CALL(*this, OnError(_, _))
-        .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnError));
-  }
-
-  MOCK_METHOD2(OnResponseStarted,
-               LoadResponseType(
-                   Fetcher*, const scoped_refptr<net::HttpResponseHeaders>&));
-  MOCK_METHOD3(OnReceived, void(Fetcher*, const char*, size_t));
-  MOCK_METHOD1(OnDone, void(Fetcher*));
-  MOCK_METHOD2(OnError, void(Fetcher*, const std::string&));
-
-  const std::string& data() const { return real_.data(); }
-  Fetcher* fetcher() const { return real_.fetcher(); }
-
- private:
-  FetcherHandlerForTest real_;
-};
-
-}  // namespace
 
 class FileFetcherTest : public ::testing::Test {
  protected:
diff --git a/src/cobalt/loader/image/image_data_decoder.cc b/src/cobalt/loader/image/image_data_decoder.cc
index 7d91bb8..3698bff 100644
--- a/src/cobalt/loader/image/image_data_decoder.cc
+++ b/src/cobalt/loader/image/image_data_decoder.cc
@@ -102,11 +102,15 @@
   return state_ == kDone;
 }
 
-void ImageDataDecoder::AllocateImageData(const math::Size& size) {
+void ImageDataDecoder::AllocateImageData(const math::Size& size,
+                                         bool has_alpha) {
+  DCHECK(resource_provider_->AlphaFormatSupported(
+      render_tree::kAlphaFormatOpaque));
   DCHECK(resource_provider_->AlphaFormatSupported(
       render_tree::kAlphaFormatPremultiplied));
   image_data_ = resource_provider_->AllocateImageData(
-      size, pixel_format(), render_tree::kAlphaFormatPremultiplied);
+      size, pixel_format(), has_alpha ? render_tree::kAlphaFormatPremultiplied
+                                      : render_tree::kAlphaFormatOpaque);
 }
 
 void ImageDataDecoder::CalculatePixelFormat() {
diff --git a/src/cobalt/loader/image/image_data_decoder.h b/src/cobalt/loader/image/image_data_decoder.h
index 62beb19..d97c329 100644
--- a/src/cobalt/loader/image/image_data_decoder.h
+++ b/src/cobalt/loader/image/image_data_decoder.h
@@ -63,7 +63,7 @@
   // Subclass can override this function to get a last chance to do some work.
   virtual void FinishInternal() {}
 
-  void AllocateImageData(const math::Size& size);
+  void AllocateImageData(const math::Size& size, bool has_alpha);
 
   render_tree::ImageData* image_data() const { return image_data_.get(); }
 
diff --git a/src/cobalt/loader/image/jpeg_image_decoder.cc b/src/cobalt/loader/image/jpeg_image_decoder.cc
index a249c7e..6fe1d91 100644
--- a/src/cobalt/loader/image/jpeg_image_decoder.cc
+++ b/src/cobalt/loader/image/jpeg_image_decoder.cc
@@ -183,7 +183,8 @@
   }
 
   AllocateImageData(math::Size(static_cast<int>(info_.image_width),
-                               static_cast<int>(info_.image_height)));
+                               static_cast<int>(info_.image_height)),
+                    false);
   return true;
 }
 
diff --git a/src/cobalt/loader/image/png_image_decoder.cc b/src/cobalt/loader/image/png_image_decoder.cc
index e048d41..e87eeb9 100644
--- a/src/cobalt/loader/image/png_image_decoder.cc
+++ b/src/cobalt/loader/image/png_image_decoder.cc
@@ -236,7 +236,8 @@
   }
 
   AllocateImageData(
-      math::Size(static_cast<int>(width), static_cast<int>(height)));
+      math::Size(static_cast<int>(width), static_cast<int>(height)),
+      has_alpha_);
 
   set_state(kReadLines);
 }
diff --git a/src/cobalt/loader/image/stub_image_decoder.h b/src/cobalt/loader/image/stub_image_decoder.h
index 99c3979..1d8de85 100644
--- a/src/cobalt/loader/image/stub_image_decoder.h
+++ b/src/cobalt/loader/image/stub_image_decoder.h
@@ -47,7 +47,7 @@
     UNREFERENCED_PARAMETER(data);
     UNREFERENCED_PARAMETER(input_byte);
     if (!image_data()) {
-      AllocateImageData(math::Size(4, 4));
+      AllocateImageData(math::Size(4, 4), true);
     }
     set_state(kDone);
     return input_byte;
diff --git a/src/cobalt/loader/image/threaded_image_decoder_proxy.cc b/src/cobalt/loader/image/threaded_image_decoder_proxy.cc
new file mode 100644
index 0000000..53fa8ce
--- /dev/null
+++ b/src/cobalt/loader/image/threaded_image_decoder_proxy.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/loader/image/threaded_image_decoder_proxy.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/threading/thread.h"
+#include "cobalt/loader/image/image_decoder.h"
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+namespace {
+
+// Helper function that is run on the WebModule thread to run a Callback if
+// and only if |threaded_image_decoder_proxy| is still alive.
+template <typename Callback, typename Arg>
+void MaybeRun(
+    base::WeakPtr<ThreadedImageDecoderProxy> threaded_image_decoder_proxy,
+    const Callback& callback, const Arg& arg) {
+  if (threaded_image_decoder_proxy) {
+    callback.Run(arg);
+  }
+}
+
+// Helper function to post a Callback back to the WebModule thread, that
+// checks whether the ThreadedImageDecoderProxy it came from is alive or not
+// before it runs.
+template <typename Callback, typename Arg>
+void PostToMessageLoopChecked(
+    base::WeakPtr<ThreadedImageDecoderProxy> threaded_image_decoder_proxy,
+    const Callback& callback, MessageLoop* message_loop, const Arg& arg) {
+  message_loop->PostTask(
+      FROM_HERE, base::Bind(&MaybeRun<Callback, Arg>,
+                            threaded_image_decoder_proxy, callback, arg));
+}
+
+}  // namespace
+
+ThreadedImageDecoderProxy::ThreadedImageDecoderProxy(
+    render_tree::ResourceProvider* resource_provider,
+    const SuccessCallback& success_callback,
+    const FailureCallback& failure_callback,
+    const ErrorCallback& error_callback, MessageLoop* load_message_loop)
+    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(
+          weak_this_(weak_ptr_factory_.GetWeakPtr())),
+      load_message_loop_(load_message_loop),
+      result_message_loop_(MessageLoop::current()),
+      image_decoder_(new ImageDecoder(
+          resource_provider,
+          base::Bind(
+              &PostToMessageLoopChecked<SuccessCallback,
+                                        scoped_refptr<render_tree::Image> >,
+              weak_this_, success_callback, result_message_loop_),
+          base::Bind(&PostToMessageLoopChecked<FailureCallback, std::string>,
+                     weak_this_, failure_callback, result_message_loop_),
+          base::Bind(&PostToMessageLoopChecked<ErrorCallback, std::string>,
+                     weak_this_, error_callback, result_message_loop_))) {
+  DCHECK(load_message_loop_);
+  DCHECK(result_message_loop_);
+  DCHECK(image_decoder_);
+}
+
+// We're dying, but |image_decoder_| might still be doing work on the load
+// thread.  Because of this, we transfer ownership of it into the load message
+// loop, where it will be deleted after any pending tasks involving it are
+// done.
+ThreadedImageDecoderProxy::~ThreadedImageDecoderProxy() {
+  load_message_loop_->DeleteSoon(FROM_HERE, image_decoder_.release());
+}
+
+LoadResponseType ThreadedImageDecoderProxy::OnResponseStarted(
+    Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) {
+  UNREFERENCED_PARAMETER(fetcher);
+  return image_decoder_->OnResponseStarted(fetcher, headers);
+}
+
+void ThreadedImageDecoderProxy::DecodeChunk(const char* data, size_t size) {
+  scoped_ptr<std::string> scoped_data(new std::string(data, size));
+  load_message_loop_->PostTask(
+      FROM_HERE, base::Bind(&Decoder::DecodeChunkPassed,
+                            base::Unretained(image_decoder_.get()),
+                            base::Passed(&scoped_data)));
+}
+
+void ThreadedImageDecoderProxy::DecodeChunkPassed(
+    scoped_ptr<std::string> data) {
+  load_message_loop_->PostTask(
+      FROM_HERE,
+      base::Bind(&Decoder::DecodeChunkPassed,
+                 base::Unretained(image_decoder_.get()), base::Passed(&data)));
+}
+
+void ThreadedImageDecoderProxy::Finish() {
+  load_message_loop_->PostTask(
+      FROM_HERE, base::Bind(&ImageDecoder::Finish,
+                            base::Unretained(image_decoder_.get())));
+}
+
+bool ThreadedImageDecoderProxy::Suspend() {
+  load_message_loop_->PostTask(
+      FROM_HERE, base::Bind(base::IgnoreResult(&ImageDecoder::Suspend),
+                            base::Unretained(image_decoder_.get())));
+  return true;
+}
+
+void ThreadedImageDecoderProxy::Resume(
+    render_tree::ResourceProvider* resource_provider) {
+  load_message_loop_->PostTask(
+      FROM_HERE,
+      base::Bind(&ImageDecoder::Resume, base::Unretained(image_decoder_.get()),
+                 resource_provider));
+}
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/image/threaded_image_decoder_proxy.h b/src/cobalt/loader/image/threaded_image_decoder_proxy.h
new file mode 100644
index 0000000..c6b559b
--- /dev/null
+++ b/src/cobalt/loader/image/threaded_image_decoder_proxy.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_
+#define COBALT_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "cobalt/loader/decoder.h"
+#include "cobalt/loader/image/image_data_decoder.h"
+#include "cobalt/loader/image/image_decoder.h"
+#include "cobalt/render_tree/image.h"
+#include "cobalt/render_tree/resource_provider.h"
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+// This class handles the layer of indirection between images that web module
+// thread wants to decode, and the ResourceLoader thread that does the actual
+// work.
+class ThreadedImageDecoderProxy : public Decoder {
+ public:
+  typedef base::Callback<void(const scoped_refptr<render_tree::Image>&)>
+      SuccessCallback;
+  typedef base::Callback<void(const std::string&)> FailureCallback;
+  typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+  ThreadedImageDecoderProxy(render_tree::ResourceProvider* resource_provider,
+                            const SuccessCallback& success_callback,
+                            const FailureCallback& failure_callback,
+                            const ErrorCallback& error_callback,
+                            MessageLoop* load_message_loop_);
+
+  ~ThreadedImageDecoderProxy();
+
+  // From the Decoder interface.
+  LoadResponseType OnResponseStarted(
+      Fetcher* fetcher,
+      const scoped_refptr<net::HttpResponseHeaders>& headers) OVERRIDE;
+  void DecodeChunk(const char* data, size_t size) OVERRIDE;
+  void DecodeChunkPassed(scoped_ptr<std::string> data) OVERRIDE;
+  void Finish() OVERRIDE;
+  bool Suspend() OVERRIDE;
+  void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE;
+
+ private:
+  base::WeakPtrFactory<ThreadedImageDecoderProxy> weak_ptr_factory_;
+  base::WeakPtr<ThreadedImageDecoderProxy> weak_this_;
+
+  // The message loop that |image_decoder_| should run on.
+  MessageLoop* load_message_loop_;
+
+  // The message loop that the original callbacks passed into us should run
+  // on.
+  MessageLoop* result_message_loop_;
+
+  // The actual image decoder.
+  scoped_ptr<ImageDecoder> image_decoder_;
+};
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_
diff --git a/src/cobalt/loader/image/webp_image_decoder.cc b/src/cobalt/loader/image/webp_image_decoder.cc
index b735d54..53f24eb 100644
--- a/src/cobalt/loader/image/webp_image_decoder.cc
+++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -92,7 +92,7 @@
     int height = config_.input.height;
     *has_alpha = !!config_.input.has_alpha;
 
-    AllocateImageData(math::Size(width, height));
+    AllocateImageData(math::Size(width, height), *has_alpha);
     return true;
   } else if (status == VP8_STATUS_NOT_ENOUGH_DATA) {
     // Data is not enough for decoding the header.
diff --git a/src/cobalt/loader/loader.cc b/src/cobalt/loader/loader.cc
index a5d86e7..b01a21a 100644
--- a/src/cobalt/loader/loader.cc
+++ b/src/cobalt/loader/loader.cc
@@ -51,6 +51,11 @@
     UNREFERENCED_PARAMETER(fetcher);
     decoder_->DecodeChunk(data, size);
   }
+  void OnReceivedPassed(Fetcher* fetcher,
+                        scoped_ptr<std::string> data) OVERRIDE {
+    UNREFERENCED_PARAMETER(fetcher);
+    decoder_->DecodeChunkPassed(data.Pass());
+  }
   void OnDone(Fetcher* fetcher) OVERRIDE {
     UNREFERENCED_PARAMETER(fetcher);
     decoder_->Finish();
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index c0a7f79..b80a2cb 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -21,6 +21,8 @@
       'target_name': 'loader',
       'type': 'static_library',
       'sources': [
+        'blob_fetcher.cc',
+        'blob_fetcher.h',
         'decoder.h',
         'embedded_fetcher.cc',
         'embedded_fetcher.h',
@@ -30,9 +32,9 @@
         'fetcher_factory.h',
         'file_fetcher.cc',
         'file_fetcher.h',
+        'font/remote_typeface_cache.h',
         'font/typeface_decoder.cc',
         'font/typeface_decoder.h',
-        'font/remote_typeface_cache.h',
         'image/dummy_gif_image_decoder.cc',
         'image/dummy_gif_image_decoder.h',
         'image/image_cache.h',
@@ -45,6 +47,8 @@
         'image/png_image_decoder.cc',
         'image/png_image_decoder.h',
         'image/stub_image_decoder.h',
+        'image/threaded_image_decoder_proxy.cc',
+        'image/threaded_image_decoder_proxy.h',
         'image/webp_image_decoder.cc',
         'image/webp_image_decoder.h',
         'loader.cc',
@@ -91,7 +95,9 @@
       'target_name': 'loader_test',
       'type': '<(gtest_target_type)',
       'sources': [
+        'blob_fetcher_test.cc',
         'fetcher_factory_test.cc',
+        'fetcher_test.h',
         'file_fetcher_test.cc',
         'font/typeface_decoder_test.cc',
         'image/image_decoder_test.cc',
diff --git a/src/cobalt/loader/loader_factory.cc b/src/cobalt/loader/loader_factory.cc
index e39a512..c3d2138 100644
--- a/src/cobalt/loader/loader_factory.cc
+++ b/src/cobalt/loader/loader_factory.cc
@@ -16,13 +16,28 @@
 
 #include "cobalt/loader/loader_factory.h"
 
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "cobalt/loader/image/threaded_image_decoder_proxy.h"
+
 namespace cobalt {
 namespace loader {
 
+namespace {
+// The ResourceLoader thread uses the default stack size, which is requested
+// by passing in 0 for its stack size.
+const size_t kLoadThreadStackSize = 0;
+}  // namespace
+
 LoaderFactory::LoaderFactory(FetcherFactory* fetcher_factory,
                              render_tree::ResourceProvider* resource_provider)
     : fetcher_factory_(fetcher_factory),
-      resource_provider_(resource_provider) {}
+      resource_provider_(resource_provider),
+      load_thread_("ResourceLoader") {
+  base::Thread::Options options(MessageLoop::TYPE_DEFAULT, kLoadThreadStackSize,
+                                base::kThreadPriority_Low);
+  load_thread_.StartWithOptions(options);
+}
 
 scoped_ptr<Loader> LoaderFactory::CreateImageLoader(
     const GURL& url, const csp::SecurityCallback& url_security_callback,
@@ -33,9 +48,9 @@
 
   scoped_ptr<Loader> loader(new Loader(
       MakeFetcherCreator(url, url_security_callback),
-      scoped_ptr<Decoder>(
-          new image::ImageDecoder(resource_provider_, success_callback,
-                                  failure_callback, error_callback)),
+      scoped_ptr<Decoder>(new image::ThreadedImageDecoderProxy(
+          resource_provider_, success_callback, failure_callback,
+          error_callback, load_thread_.message_loop())),
       error_callback,
       base::Bind(&LoaderFactory::OnLoaderDestroyed, base::Unretained(this))));
   OnLoaderCreated(loader.get());
@@ -78,6 +93,13 @@
        iter != active_loaders_.end(); ++iter) {
     (*iter)->Suspend();
   }
+
+  // Wait for all loader thread messages to be flushed before returning.
+  base::WaitableEvent messages_flushed(true, false);
+  load_thread_.message_loop()->PostTask(
+      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
+                            base::Unretained(&messages_flushed)));
+  messages_flushed.Wait();
 }
 
 void LoaderFactory::Resume(render_tree::ResourceProvider* resource_provider) {
diff --git a/src/cobalt/loader/loader_factory.h b/src/cobalt/loader/loader_factory.h
index 000e7ad..fe79673 100644
--- a/src/cobalt/loader/loader_factory.h
+++ b/src/cobalt/loader/loader_factory.h
@@ -19,6 +19,7 @@
 
 #include <set>
 
+#include "base/threading/thread.h"
 #include "cobalt/csp/content_security_policy.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/font/typeface_decoder.h"
@@ -81,6 +82,10 @@
   // can be aborted.
   typedef std::set<Loader*> LoaderSet;
   LoaderSet active_loaders_;
+
+  // Thread to run asynchronous fetchers and decoders on.  At the moment,
+  // image decoding is the only thing done on this thread.
+  base::Thread load_thread_;
 };
 
 }  // namespace loader
diff --git a/src/cobalt/loader/loader_test.cc b/src/cobalt/loader/loader_test.cc
index c7e637c..ed43dfa 100644
--- a/src/cobalt/loader/loader_test.cc
+++ b/src/cobalt/loader/loader_test.cc
@@ -100,7 +100,7 @@
 
 struct MockFetcherFactory {
  public:
-  MockFetcherFactory(): count(0) {}
+  MockFetcherFactory() : count(0) {}
   // Way to access the last fetcher created by the fetcher factory.
   MockFetcher* fetcher;
   int count;
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc
index 0f60e72..1ed1376 100644
--- a/src/cobalt/loader/net_fetcher.cc
+++ b/src/cobalt/loader/net_fetcher.cc
@@ -156,7 +156,7 @@
     net_fetcher_log.Get().IncrementFetchedBytes(
         static_cast<int>(download_data->length()));
 #endif
-    handler()->OnReceived(this, download_data->data(), download_data->length());
+    handler()->OnReceivedPassed(this, download_data.Pass());
   }
 }
 
diff --git a/src/cobalt/loader/text_decoder.h b/src/cobalt/loader/text_decoder.h
index 5de3c4c..dd191d7 100644
--- a/src/cobalt/loader/text_decoder.h
+++ b/src/cobalt/loader/text_decoder.h
@@ -17,11 +17,13 @@
 #ifndef COBALT_LOADER_TEXT_DECODER_H_
 #define COBALT_LOADER_TEXT_DECODER_H_
 
+#include <algorithm>
 #include <string>
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/loader/decoder.h"
 
@@ -50,6 +52,21 @@
     }
     text_.append(data, size);
   }
+
+  void DecodeChunkPassed(scoped_ptr<std::string> data) OVERRIDE {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    DCHECK(data);
+    if (suspended_) {
+      return;
+    }
+
+    if (text_.empty()) {
+      std::swap(*data, text_);
+    } else {
+      text_.append(*data);
+    }
+  }
+
   void Finish() OVERRIDE {
     DCHECK(thread_checker_.CalledOnValidThread());
     if (suspended_) {
diff --git a/src/cobalt/render_tree/image.h b/src/cobalt/render_tree/image.h
index 8faa040..ee58ada 100644
--- a/src/cobalt/render_tree/image.h
+++ b/src/cobalt/render_tree/image.h
@@ -67,6 +67,12 @@
   // This alpha format implies standard alpha, where each component is
   // independent of the alpha.
   kAlphaFormatUnpremultiplied,
+
+  // Indicates that all alpha values in the image data are opaque.  If
+  // non-opaque alpha data is used with this format, visual output will be
+  // undefined. This information may be used to enable optimizations, and can
+  // result in Image::IsOpaque() returning true.
+  kAlphaFormatOpaque,
 };
 
 // Describes the format of a contiguous block of memory that represents an
@@ -216,6 +222,12 @@
                                BytesPerPixel(kPixelFormatRGBA8));
   }
 
+  // If an Image is able to know that it contains no alpha data (e.g. if it
+  // was constructed from ImageData whose alpha format is set to
+  // kAlphaFormatOpaque), then this method can return true, and code can
+  // be written to take advantage of this and perform optimizations.
+  virtual bool IsOpaque() const { return false; }
+
  protected:
   virtual ~Image() {}
 
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h
index 5451c11..0f996fa 100644
--- a/src/cobalt/render_tree/map_to_mesh_filter.h
+++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -17,14 +17,25 @@
 #ifndef COBALT_RENDER_TREE_MAP_TO_MESH_FILTER_H_
 #define COBALT_RENDER_TREE_MAP_TO_MESH_FILTER_H_
 
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+
 namespace cobalt {
 namespace render_tree {
 
+enum StereoMode { kMono, kLeftRight, kTopBottom };
+
 // A MapToMeshFilter can be used to map source content onto a 3D mesh, within a
 // specified well-defined viewport.
 class MapToMeshFilter {
  public:
-  MapToMeshFilter() {}
+  explicit MapToMeshFilter(const StereoMode stereo_mode)
+      : stereo_mode_(stereo_mode) {}
+
+  StereoMode GetStereoMode() const { return stereo_mode_; }
+
+ private:
+  StereoMode stereo_mode_;
 };
 
 }  // namespace render_tree
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
index cce9ab8..b41b78f 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
@@ -1,8 +1,11 @@
 attribute vec3 a_position;

 attribute vec2 a_tex_coord;

 varying vec2 v_tex_coord;

+uniform vec2 u_tex_offset;

+uniform vec2 u_tex_multiplier;

 uniform mat4 u_mvp_matrix;

 void main() {

   gl_Position = u_mvp_matrix * vec4(a_position.xyz, 1.0);

-  v_tex_coord = a_tex_coord;

+  v_tex_coord.x = a_tex_coord.x * u_tex_multiplier.x + u_tex_offset.x;

+  v_tex_coord.y = a_tex_coord.y * u_tex_multiplier.y + u_tex_offset.y;

 }

diff --git a/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc
new file mode 100644
index 0000000..7739988
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h"
+
+#include <utility>
+
+#include "cobalt/math/vector2d_f.h"
+#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+
+#if SB_HAS(BLITTER)
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace blitter {
+
+size_t CachedSoftwareRasterizer::Surface::GetEstimatedMemoryUsage() const {
+  // We assume 4-bytes-per-pixel.
+  return coord_mapping.output_bounds.size().GetArea() * 4;
+}
+
+CachedSoftwareRasterizer::CachedSoftwareRasterizer(SbBlitterDevice device,
+                                                   SbBlitterContext context,
+                                                   int cache_capacity)
+    : cache_capacity_(cache_capacity),
+      device_(device),
+      context_(context),
+      software_rasterizer_(0),
+      cache_memory_usage_(
+          "Memory.CachedSoftwareRasterizer.CacheUsage", 0,
+          "Total memory occupied by cached software-rasterized surfaces.") {}
+
+CachedSoftwareRasterizer::~CachedSoftwareRasterizer() {
+  // Clean up any leftover surfaces.
+  for (CacheMap::iterator iter = surface_map_.begin();
+       iter != surface_map_.end(); ++iter) {
+    DCHECK(iter->second.cached);
+    SbBlitterDestroySurface(iter->second.surface);
+  }
+}
+
+void CachedSoftwareRasterizer::OnStartNewFrame() {
+  for (CacheMap::iterator iter = surface_map_.begin();
+       iter != surface_map_.end();) {
+    CacheMap::iterator current = iter;
+    ++iter;
+
+    // If the surface wasn't referenced, destroy it.  If it was, mark it as
+    // being unreferenced.
+    if (!current->second.referenced) {
+      cache_memory_usage_ -= current->second.GetEstimatedMemoryUsage();
+      SbBlitterDestroySurface(current->second.surface);
+      surface_map_.erase(current);
+    } else {
+#if defined(ENABLE_DEBUG_CONSOLE)
+      current->second.created = false;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+      current->second.referenced = false;
+    }
+  }
+}
+
+CachedSoftwareRasterizer::Surface CachedSoftwareRasterizer::GetSurface(
+    render_tree::Node* node, const Transform& transform) {
+  CacheMap::iterator found = surface_map_.find(node);
+  if (found != surface_map_.end()) {
+#if SB_HAS(BILINEAR_FILTERING_SUPPORT)
+    if (found->second.scale.x() >= transform.scale().x() &&
+        found->second.scale.y() >= transform.scale().y()) {
+#else  // SB_HAS(BILINEAR_FILTERING_SUPPORT)
+    if (found->second.scale.x() == transform.scale().x() &&
+        found->second.scale.y() == transform.scale().y()) {
+#endif
+      found->second.referenced = true;
+      return found->second;
+    }
+  }
+
+  Surface software_surface;
+  software_surface.referenced = true;
+#if defined(ENABLE_DEBUG_CONSOLE)
+  software_surface.created = true;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+  software_surface.node = node;
+  software_surface.surface = kSbBlitterInvalidSurface;
+  software_surface.cached = false;
+
+  // We ensure that scale is at least 1. Since it is common to have animating
+  // scale between 0 and 1 (e.g. an item animating from 0 to 1 to "grow" into
+  // the scene), this ensures that rendered items will not look pixellated as
+  // they grow (e.g. if they were cached at scale 0.2, they would be stretched
+  // x5 if the scale were to animate to 1.0).
+  if (transform.scale().x() == 0.0f || transform.scale().y() == 0.0f) {
+    // There won't be anything to render if the transform squishes one dimension
+    // to 0.
+    return software_surface;
+  }
+  DCHECK_LT(0, transform.scale().x());
+  DCHECK_LT(0, transform.scale().y());
+  Transform scaled_transform(transform);
+// If bilinear filtering is not supported, do not rely on the rasterizer to
+// scale down for us, it results in too many artifacts.
+#if SB_HAS(BILINEAR_FILTERING_SUPPORT)
+  math::Vector2dF apply_scale(1.0f, 1.0f);
+  if (transform.scale().x() < 1.0f) {
+    apply_scale.set_x(1.0f / transform.scale().x());
+  }
+  if (transform.scale().y() < 1.0f) {
+    apply_scale.set_y(1.0f / transform.scale().y());
+  }
+  scaled_transform.ApplyScale(apply_scale);
+#endif  // SB_HAS(BILINEAR_FILTERING_SUPPORT)
+
+  software_surface.scale = scaled_transform.scale();
+
+  common::OffscreenRenderCoordinateMapping coord_mapping =
+      common::GetOffscreenRenderCoordinateMapping(node->GetBounds(),
+                                                  scaled_transform.ToMatrix(),
+                                                  base::optional<math::Rect>());
+
+  software_surface.coord_mapping = coord_mapping;
+
+  if (coord_mapping.output_bounds.IsEmpty()) {
+    // There's nothing to render if the bounds are 0.
+    return software_surface;
+  }
+
+  DCHECK_GE(0.001f, std::abs(1.0f -
+                             scaled_transform.scale().x() *
+                                 coord_mapping.output_post_scale.x()));
+  DCHECK_GE(0.001f, std::abs(1.0f -
+                             scaled_transform.scale().y() *
+                                 coord_mapping.output_post_scale.y()));
+
+  SkImageInfo output_image_info = SkImageInfo::MakeN32(
+      coord_mapping.output_bounds.width(), coord_mapping.output_bounds.height(),
+      kPremul_SkAlphaType);
+
+  // Allocate the pixels for the output image.
+  SbBlitterPixelDataFormat blitter_pixel_data_format =
+      SkiaToBlitterPixelFormat(output_image_info.colorType());
+  DCHECK(SbBlitterIsPixelFormatSupportedByPixelData(device_,
+                                                    blitter_pixel_data_format));
+  SbBlitterPixelData pixel_data = SbBlitterCreatePixelData(
+      device_, coord_mapping.output_bounds.width(),
+      coord_mapping.output_bounds.height(), blitter_pixel_data_format);
+  CHECK(SbBlitterIsPixelDataValid(pixel_data));
+
+  SkBitmap bitmap;
+  bitmap.installPixels(output_image_info,
+                       SbBlitterGetPixelDataPointer(pixel_data),
+                       SbBlitterGetPixelDataPitchInBytes(pixel_data));
+
+  // Setup our Skia canvas that we will be using as the target for all CPU Skia
+  // output.
+  SkCanvas canvas(bitmap);
+  canvas.clear(SkColorSetARGB(0, 0, 0, 0));
+
+  Transform sub_render_transform(coord_mapping.sub_render_transform);
+
+  // Now setup our canvas so that the render tree will be rendered to the top
+  // left corner instead of at node->GetBounds().origin().
+  canvas.translate(sub_render_transform.translate().x(),
+                   sub_render_transform.translate().y());
+  // And finally set the scale on our target canvas to match that of the current
+  // |transform|.
+  canvas.scale(sub_render_transform.scale().x(),
+               sub_render_transform.scale().y());
+
+  // Use the Skia software rasterizer to render our subtree.
+  software_rasterizer_.Submit(node, &canvas);
+
+  // Create a surface out of the now populated pixel data.
+  software_surface.surface =
+      SbBlitterCreateSurfaceFromPixelData(device_, pixel_data);
+
+  if (software_surface.GetEstimatedMemoryUsage() +
+          cache_memory_usage_.value() <=
+      cache_capacity_) {
+    software_surface.cached = true;
+    if (found != surface_map_.end()) {
+      cache_memory_usage_ -= found->second.GetEstimatedMemoryUsage();
+      found->second = software_surface;
+    } else {
+      std::pair<CacheMap::iterator, bool> inserted =
+          surface_map_.insert(std::make_pair(node, software_surface));
+    }
+    cache_memory_usage_ += software_surface.GetEstimatedMemoryUsage();
+  }
+
+  return software_surface;
+}
+
+}  // namespace blitter
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // #if SB_HAS(BLITTER)
diff --git a/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h
new file mode 100644
index 0000000..f71d49b
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_
+#define COBALT_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_
+
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "cobalt/base/c_val.h"
+#include "cobalt/render_tree/node.h"
+#include "cobalt/renderer/rasterizer/blitter/render_state.h"
+#include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h"
+#include "cobalt/renderer/rasterizer/skia/software_rasterizer.h"
+#include "starboard/blitter.h"
+
+#if SB_HAS(BLITTER)
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace blitter {
+
+// This class is responsible for creating SbBlitterSurfaces and software
+// rasterizing render tree nodes to them to be used by clients.  The class
+// manages a cache of software surfaces in order to avoid duplicate work.  Its
+// main public interface is for clients to construct
+// CachedSoftwareRasterizer::SurfaceReference objects from which they can
+// extract the software-rasterized surface.  Internally, software rasterization
+// is handled via a skia::SoftwareRasterizer object.
+class CachedSoftwareRasterizer {
+ public:
+  struct Surface {
+    // The actual surface containing rendered data.
+    SbBlitterSurface surface;
+
+    // True if this surface was referenced at least once since the last time
+    // OnStartNewFrame() was called.
+    bool referenced;
+
+#if defined(ENABLE_DEBUG_CONSOLE)
+    // True if OnStartNewFrame() has not been called since this surface was
+    // created.
+    bool created;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+
+    // Transform information detailing how the surface should be output to
+    // the display such that sub-pixel alignments are respected.
+    common::OffscreenRenderCoordinateMapping coord_mapping;
+
+    // A duplicate of the key, though as a smart pointer, to keep a reference
+    // to the node alive.
+    scoped_refptr<render_tree::Node> node;
+
+    // Is this surface cached or not.
+    bool cached;
+
+    // The scale used for rasterization when the surface was cached.
+    math::Vector2dF scale;
+
+    size_t GetEstimatedMemoryUsage() const;
+  };
+
+  CachedSoftwareRasterizer(SbBlitterDevice device, SbBlitterContext context,
+                           int cache_capacity);
+  ~CachedSoftwareRasterizer();
+
+  // Should be called once per frame.  This method will remove from the cache
+  // anything that hasn't been referenced in the last frame.
+  void OnStartNewFrame();
+
+  // A SurfaceReference is the mechanism through which clients can request
+  // renders from the CachedSoftwareRasterizer.  The main reason that we don't
+  // make GetSurface() the direct public interface is so that we can decide
+  // to rasterize something without caching it, in which case it should be
+  // cleaned up when the reference to it expires.
+  class SurfaceReference {
+   public:
+    // The |transform| parameter should be the transform that the node will
+    // ultimately have applied to it, and is used for ensuring that the rendered
+    // result is sub-pixel aligned properly.
+    SurfaceReference(CachedSoftwareRasterizer* rasterizer,
+                     render_tree::Node* node, const Transform& transform)
+        : surface_(rasterizer->GetSurface(node, transform)) {}
+    ~SurfaceReference() {
+      // If the surface returned to us was not actually cached, then destroy
+      // it here when the surface reference is destroyed.
+      if (!surface_.cached && SbBlitterIsSurfaceValid(surface_.surface)) {
+        SbBlitterDestroySurface(surface_.surface);
+      }
+    }
+    const Surface& surface() const { return surface_; }
+
+   private:
+    Surface surface_;
+
+    DISALLOW_COPY_AND_ASSIGN(SurfaceReference);
+  };
+
+  Surface GetSurface(render_tree::Node* node, const Transform& transform);
+
+  render_tree::ResourceProvider* GetResourceProvider() {
+    return software_rasterizer_.GetResourceProvider();
+  }
+
+ private:
+  // The cache, mapping input render_tree::Node references to cached surfaces.
+  typedef base::hash_map<render_tree::Node*, Surface> CacheMap;
+  CacheMap surface_map_;
+
+  const int cache_capacity_;
+
+  // The Blitter device/context that all image allocation/blitting operations
+  // will occur within.
+  SbBlitterDevice device_;
+  SbBlitterContext context_;
+
+  // The internal rasterizer used to actually render nodes.
+  skia::SoftwareRasterizer software_rasterizer_;
+
+  // The amount of memory currently consumed by the surfaces populating the
+  // cache.
+  base::CVal<base::cval::SizeInBytes> cache_memory_usage_;
+};
+
+}  // namespace blitter
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // #if SB_HAS(BLITTER)
+
+#endif  // COBALT_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
index 2868926..dee6372 100644
--- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
@@ -16,11 +16,17 @@
 
 #include "cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h"
 
+#include <string>
+
 #include "base/debug/trace_event.h"
 #include "base/threading/thread_checker.h"
+#if defined(ENABLE_DEBUG_CONSOLE)
+#include "cobalt/base/console_commands.h"
+#endif
 #include "cobalt/render_tree/resource_provider_stub.h"
 #include "cobalt/renderer/backend/blitter/graphics_context.h"
 #include "cobalt/renderer/backend/blitter/render_target.h"
+#include "cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h"
 #include "cobalt/renderer/rasterizer/blitter/render_state.h"
 #include "cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h"
 #include "cobalt/renderer/rasterizer/blitter/resource_provider.h"
@@ -39,7 +45,8 @@
  public:
   explicit Impl(backend::GraphicsContext* graphics_context,
                 int scratch_surface_size_in_bytes,
-                int surface_cache_size_in_bytes);
+                int surface_cache_size_in_bytes,
+                int software_surface_cache_size_in_bytes);
   ~Impl();
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
@@ -49,11 +56,14 @@
   render_tree::ResourceProvider* GetResourceProvider();
 
  private:
+#if defined(ENABLE_DEBUG_CONSOLE)
+  void OnToggleHighlightSoftwareDraws(const std::string& message);
+#endif
+
   base::ThreadChecker thread_checker_;
 
   backend::GraphicsContextBlitter* context_;
 
-  skia::SoftwareRasterizer software_rasterizer_;
   scoped_ptr<render_tree::ResourceProvider> resource_provider_;
 
   int64 submit_count_;
@@ -61,21 +71,47 @@
   ScratchSurfaceCache scratch_surface_cache_;
   base::optional<SurfaceCacheDelegate> surface_cache_delegate_;
   base::optional<common::SurfaceCache> surface_cache_;
+
+  CachedSoftwareRasterizer software_surface_cache_;
+
+#if defined(ENABLE_DEBUG_CONSOLE)
+  // Debug command to toggle cache highlights to help visualize which nodes
+  // are being cached.
+  bool toggle_highlight_software_draws_;
+  base::ConsoleCommandManager::CommandHandler
+      toggle_highlight_software_draws_command_handler_;
+#endif
 };
 
 HardwareRasterizer::Impl::Impl(backend::GraphicsContext* graphics_context,
                                int scratch_surface_size_in_bytes,
-                               int surface_cache_size_in_bytes)
+                               int surface_cache_size_in_bytes,
+                               int software_surface_cache_size_in_bytes)
     : context_(base::polymorphic_downcast<backend::GraphicsContextBlitter*>(
           graphics_context)),
-      software_rasterizer_(0),
       submit_count_(0),
       scratch_surface_cache_(context_->GetSbBlitterDevice(),
                              context_->GetSbBlitterContext(),
-                             scratch_surface_size_in_bytes) {
+                             scratch_surface_size_in_bytes),
+      software_surface_cache_(context_->GetSbBlitterDevice(),
+                              context_->GetSbBlitterContext(),
+                              software_surface_cache_size_in_bytes)
+#if defined(ENABLE_DEBUG_CONSOLE)
+      ,
+      toggle_highlight_software_draws_(false),
+      toggle_highlight_software_draws_command_handler_(
+          "toggle_highlight_software_draws",
+          base::Bind(&HardwareRasterizer::Impl::OnToggleHighlightSoftwareDraws,
+                     base::Unretained(this)),
+          "Highlights regions where software rasterization is occurring.",
+          "Toggles whether all software rasterized elements will appear as a "
+          "green rectangle or not.  This can be used to identify where in a "
+          "scene software rasterization is occurring.")
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+{
   resource_provider_ = scoped_ptr<render_tree::ResourceProvider>(
       new ResourceProvider(context_->GetSbBlitterDevice(),
-                           software_rasterizer_.GetResourceProvider()));
+                           software_surface_cache_.GetResourceProvider()));
 
   if (surface_cache_size_in_bytes > 0) {
     surface_cache_delegate_.emplace(context_->GetSbBlitterDevice(),
@@ -88,6 +124,14 @@
 
 HardwareRasterizer::Impl::~Impl() {}
 
+#if defined(ENABLE_DEBUG_CONSOLE)
+void HardwareRasterizer::Impl::OnToggleHighlightSoftwareDraws(
+    const std::string& message) {
+  UNREFERENCED_PARAMETER(message);
+  toggle_highlight_software_draws_ = !toggle_highlight_software_draws_;
+}
+#endif
+
 void HardwareRasterizer::Impl::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
     const scoped_refptr<backend::RenderTarget>& render_target, int options) {
@@ -112,6 +156,8 @@
     surface_cache_->Frame();
   }
 
+  software_surface_cache_.OnStartNewFrame();
+
   // Clear the background before proceeding if the clear option is set.
   // We also clear if this is one of the first 3 submits.  This is for security
   // purposes, so that despite the Blitter API implementation, we ensure that
@@ -127,14 +173,20 @@
     TRACE_EVENT0("cobalt::renderer", "VisitRenderTree");
 
     // Visit the render tree with our Blitter API visitor.
+    RenderState initial_render_state(
+        render_target_blitter->GetSbRenderTarget(), Transform(),
+        BoundsStack(context_->GetSbBlitterContext(),
+                    math::Rect(render_target_blitter->GetSize())));
+#if defined(ENABLE_DEBUG_CONSOLE)
+    initial_render_state.highlight_software_draws =
+        toggle_highlight_software_draws_;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
     RenderTreeNodeVisitor visitor(
         context_->GetSbBlitterDevice(), context_->GetSbBlitterContext(),
-        RenderState(render_target_blitter->GetSbRenderTarget(), Transform(),
-                    BoundsStack(context_->GetSbBlitterContext(),
-                                math::Rect(render_target_blitter->GetSize()))),
-        &software_rasterizer_, &scratch_surface_cache_,
+        initial_render_state, &scratch_surface_cache_,
         surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL,
-        surface_cache_ ? &surface_cache_.value() : NULL);
+        surface_cache_ ? &surface_cache_.value() : NULL,
+        &software_surface_cache_);
     render_tree->Accept(&visitor);
   }
 
@@ -151,9 +203,11 @@
 
 HardwareRasterizer::HardwareRasterizer(
     backend::GraphicsContext* graphics_context,
-    int scratch_surface_size_in_bytes, int surface_cache_size_in_bytes)
+    int scratch_surface_size_in_bytes, int surface_cache_size_in_bytes,
+    int software_surface_cache_size_in_bytes)
     : impl_(new Impl(graphics_context, scratch_surface_size_in_bytes,
-                     surface_cache_size_in_bytes)) {}
+                     surface_cache_size_in_bytes,
+                     software_surface_cache_size_in_bytes)) {}
 
 HardwareRasterizer::~HardwareRasterizer() {}
 
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
index abf4d4e..6a48124 100644
--- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
@@ -39,7 +39,8 @@
  public:
   explicit HardwareRasterizer(backend::GraphicsContext* graphics_context,
                               int scratch_surface_size_in_bytes,
-                              int surface_cache_size_in_bytes);
+                              int surface_cache_size_in_bytes,
+                              int software_surface_cache_size_in_bytes);
   virtual ~HardwareRasterizer();
 
   // Consume the render tree and output the results to the render target passed
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.cc b/src/cobalt/renderer/rasterizer/blitter/image.cc
index 5932ddd..bd82e11 100644
--- a/src/cobalt/renderer/rasterizer/blitter/image.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/image.cc
@@ -38,7 +38,8 @@
           RenderTreePixelFormatToBlitter(pixel_format))),
       descriptor_(size, pixel_format, alpha_format,
                   SbBlitterGetPixelDataPitchInBytes(pixel_data_)) {
-  CHECK_EQ(render_tree::kAlphaFormatPremultiplied, alpha_format);
+  CHECK(alpha_format == render_tree::kAlphaFormatPremultiplied ||
+        alpha_format == render_tree::kAlphaFormatOpaque);
   CHECK(SbBlitterIsPixelDataValid(pixel_data_));
 }
 
@@ -63,6 +64,9 @@
   surface_ = SbBlitterCreateSurfaceFromPixelData(image_data->device(),
                                                  image_data->TakePixelData());
   CHECK(SbBlitterIsSurfaceValid(surface_));
+
+  is_opaque_ = image_data->GetDescriptor().alpha_format ==
+               render_tree::kAlphaFormatOpaque;
 }
 
 bool SinglePlaneImage::EnsureInitialized() { return false; }
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.h b/src/cobalt/renderer/rasterizer/blitter/image.h
index 605cabe..949e3a2 100644
--- a/src/cobalt/renderer/rasterizer/blitter/image.h
+++ b/src/cobalt/renderer/rasterizer/blitter/image.h
@@ -84,12 +84,16 @@
   // surface into a SkBitmap.
   const SkBitmap& GetBitmap() const OVERRIDE;
 
+  bool IsOpaque() const OVERRIDE { return is_opaque_; }
+
  private:
   ~SinglePlaneImage() OVERRIDE;
 
   math::Size size_;
   SbBlitterSurface surface_;
 
+  bool is_opaque_;
+
   // This field is populated when GetBitmap() is called for the first time, and
   // after that is never modified.
   mutable base::optional<SkBitmap> bitmap_;
diff --git a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
index 8c75fd1..07f6947 100644
--- a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
@@ -38,6 +38,7 @@
       'type': 'static_library',
 
       'sources': [
+        'cached_software_rasterizer.cc',
         'cobalt_blitter_conversions.cc',
         'hardware_rasterizer.cc',
         'image.cc',
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_state.h b/src/cobalt/renderer/rasterizer/blitter/render_state.h
index 5b6f683..8b05887 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_state.h
+++ b/src/cobalt/renderer/rasterizer/blitter/render_state.h
@@ -118,11 +118,26 @@
               const BoundsStack& bounds_stack)
       : render_target(render_target),
         transform(transform),
-        bounds_stack(bounds_stack) {}
+        bounds_stack(bounds_stack),
+        opacity(1.0f)
+#if defined(ENABLE_DEBUG_CONSOLE)
+        ,
+        highlight_software_draws(false)
+#endif
+  {
+  }
 
   SbBlitterRenderTarget render_target;
   Transform transform;
   BoundsStack bounds_stack;
+  float opacity;
+
+#if defined(ENABLE_DEBUG_CONSOLE)
+  // If true, all software rasterization results are replaced with a green
+  // fill rectangle.  Thus, if the software cache is used, one will see a flash
+  // of green every time something is registered with the cache.
+  bool highlight_software_draws;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
 };
 
 }  // namespace blitter
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
index d0f5377..8726b2e 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
@@ -28,9 +28,6 @@
 #include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h"
 #include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h"
 #include "starboard/blitter.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkImageInfo.h"
 
 #if SB_HAS(BLITTER)
 
@@ -52,18 +49,17 @@
 
 RenderTreeNodeVisitor::RenderTreeNodeVisitor(
     SbBlitterDevice device, SbBlitterContext context,
-    const RenderState& render_state,
-    skia::SoftwareRasterizer* software_rasterizer,
-    ScratchSurfaceCache* scratch_surface_cache,
+    const RenderState& render_state, ScratchSurfaceCache* scratch_surface_cache,
     SurfaceCacheDelegate* surface_cache_delegate,
-    common::SurfaceCache* surface_cache)
-    : software_rasterizer_(software_rasterizer),
-      device_(device),
+    common::SurfaceCache* surface_cache,
+    CachedSoftwareRasterizer* software_surface_cache)
+    : device_(device),
       context_(context),
       render_state_(render_state),
       scratch_surface_cache_(scratch_surface_cache),
       surface_cache_delegate_(surface_cache_delegate),
-      surface_cache_(surface_cache) {
+      surface_cache_(surface_cache),
+      software_surface_cache_(software_surface_cache) {
   DCHECK_EQ(surface_cache_delegate_ == NULL, surface_cache_ == NULL);
   if (surface_cache_delegate_) {
     // Update our surface cache delegate to point to this render tree node
@@ -77,9 +73,6 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::CompositionNode* composition_node) {
-  common::SurfaceCache::Block cache_block(surface_cache_, composition_node);
-  if (cache_block.Cached()) return;
-
   const render_tree::CompositionNode::Children& children =
       composition_node->data().children();
 
@@ -99,6 +92,27 @@
   render_state_.transform.ApplyOffset(-composition_node->data().offset());
 }
 
+namespace {
+bool SourceCanRenderWithOpacity(render_tree::Node* source) {
+  if (source->GetTypeId() == base::GetTypeId<render_tree::ImageNode>() ||
+      source->GetTypeId() == base::GetTypeId<render_tree::RectNode>()) {
+    return true;
+  } else if (source->GetTypeId() ==
+             base::GetTypeId<render_tree::CompositionNode>()) {
+    // If we are a composition of valid sources, then we also allow
+    // rendering through a viewport here.
+    render_tree::CompositionNode* composition_node =
+        base::polymorphic_downcast<render_tree::CompositionNode*>(source);
+    typedef render_tree::CompositionNode::Children Children;
+    const Children& children = composition_node->data().children();
+    if (children.size() == 1 && SourceCanRenderWithOpacity(children[0].get())) {
+      return true;
+    }
+  }
+  return false;
+}
+}  // namespace
+
 void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) {
   if (filter_node->data().blur_filter) {
     // The Starboard Blitter API does not support blur filters, so we fallback
@@ -135,6 +149,16 @@
     // we know that opacity is in the range (0, 1), exclusive.
     float opacity = filter_node->data().opacity_filter->opacity();
 
+    if (SourceCanRenderWithOpacity(source)) {
+      float original_opacity = render_state_.opacity;
+      render_state_.opacity *= opacity;
+
+      source->Accept(this);
+
+      render_state_.opacity = original_opacity;
+      return;
+    }
+
     // Render our source subtree to an offscreen surface, and then we will
     // re-render it to our main render target with an alpha value applied to it.
     scoped_ptr<OffscreenRender> offscreen_render =
@@ -202,8 +226,18 @@
                 -local_matrix.Get(1, 2) * image_size.height()));
 
   // Render the image.
-  SbBlitterSetBlending(context_, true);
-  SbBlitterSetModulateBlitsWithColor(context_, false);
+  if (render_state_.opacity < 1.0f) {
+    SbBlitterSetBlending(context_, true);
+    SbBlitterSetModulateBlitsWithColor(context_, true);
+    SbBlitterSetColor(
+        context_,
+        SbBlitterColorFromRGBA(255, 255, 255,
+                               static_cast<int>(255 * render_state_.opacity)));
+  } else {
+    SbBlitterSetBlending(context_, !skia_image->IsOpaque());
+    SbBlitterSetModulateBlitsWithColor(context_, false);
+  }
+
   SbBlitterBlitRectToRectTiled(
       context_, blitter_image->surface(),
       RectFToBlitterRect(local_transform.TransformRect(RectF(image_size))),
@@ -213,15 +247,12 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::MatrixTransformNode* matrix_transform_node) {
-  common::SurfaceCache::Block cache_block(surface_cache_,
-                                          matrix_transform_node);
-  if (cache_block.Cached()) return;
-
   const Matrix3F& transform = matrix_transform_node->data().transform;
 
-  if (transform.Get(1, 0) != 0 || transform.Get(0, 1) != 0) {
-    // The Starboard Blitter API does not support rotations/shears, so we must
-    // fallback to software in order to render the entire subtree.
+  if (transform.Get(1, 0) != 0 || transform.Get(0, 1) != 0 ||
+      transform.Get(0, 0) < 0 || transform.Get(1, 1) < 0) {
+    // The Starboard Blitter API does not support rotations/shears/flips, so we
+    // must fallback to software in order to render the entire subtree.
     RenderWithSoftwareRenderer(matrix_transform_node);
     return;
   }
@@ -277,8 +308,14 @@
 void RenderRectNodeBorder(SbBlitterContext context, ColorRGBA color, float left,
                           float right, float top, float bottom,
                           const RectF& rect) {
-  SbBlitterSetColor(context, RenderTreeToBlitterColor(color));
-  SbBlitterSetBlending(context, true);
+  SbBlitterColor blitter_color = RenderTreeToBlitterColor(color);
+  SbBlitterSetColor(context, blitter_color);
+
+  if (SbBlitterAFromColor(blitter_color) < 255) {
+    SbBlitterSetBlending(context, true);
+  } else {
+    SbBlitterSetBlending(context, false);
+  }
 
   // We draw four rectangles, one for each border edge.  They have the following
   // layout:
@@ -342,13 +379,16 @@
 
   // Render the solid color fill, if a brush exists.
   if (rect_node->data().background_brush) {
-    SbBlitterSetBlending(context_, true);
-
     SolidColorBrush* solid_color_brush =
         base::polymorphic_downcast<SolidColorBrush*>(
             rect_node->data().background_brush.get());
     ColorRGBA color = solid_color_brush->color();
 
+    if (render_state_.opacity < 1.0f) {
+      color.set_a(color.a() * render_state_.opacity);
+    }
+
+    SbBlitterSetBlending(context_, color.a() < 1.0f);
     SbBlitterSetColor(context_, RenderTreeToBlitterColor(color));
 
     SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect));
@@ -370,8 +410,12 @@
       float bottom_width =
           border.bottom.width * render_state_.transform.scale().y();
 
-      RenderRectNodeBorder(context_, border.left.color, left_width, right_width,
-                           top_width, bottom_width, transformed_rect);
+      ColorRGBA color = border.left.color;
+      if (render_state_.opacity < 1.0f) {
+        color.set_a(color.a() * render_state_.opacity);
+      }
+      RenderRectNodeBorder(context_, color, left_width, right_width, top_width,
+                           bottom_width, transformed_rect);
     }
   }
 }
@@ -387,82 +431,51 @@
 
 void RenderTreeNodeVisitor::RenderWithSoftwareRenderer(
     render_tree::Node* node) {
-  common::SurfaceCache::Block cache_block(surface_cache_, node);
-  if (cache_block.Cached()) return;
-
-  common::OffscreenRenderCoordinateMapping coord_mapping =
-      common::GetOffscreenRenderCoordinateMapping(
-          node->GetBounds(), render_state_.transform.ToMatrix(),
-          render_state_.bounds_stack.Top());
-  if (coord_mapping.output_bounds.IsEmpty()) {
-    // There's nothing to render if the bounds are 0.
+  CachedSoftwareRasterizer::SurfaceReference software_surface_reference(
+      software_surface_cache_, node, render_state_.transform);
+  CachedSoftwareRasterizer::Surface software_surface =
+      software_surface_reference.surface();
+  if (!SbBlitterIsSurfaceValid(software_surface.surface)) {
     return;
   }
 
-  DCHECK_GE(0.001f, std::abs(1.0f -
-                             render_state_.transform.scale().x() *
-                                 coord_mapping.output_post_scale.x()));
-  DCHECK_GE(0.001f, std::abs(1.0f -
-                             render_state_.transform.scale().y() *
-                                 coord_mapping.output_post_scale.y()));
+  Transform apply_transform(render_state_.transform);
+  apply_transform.ApplyScale(software_surface.coord_mapping.output_post_scale);
+  math::RectF output_rectf = apply_transform.TransformRect(
+      software_surface.coord_mapping.output_bounds);
+  // We can simulate a "pre-multiply" by translation by offsetting the final
+  // output rectangle by the pre-translate, effectively resulting in the
+  // translation being applied last, as intended.
+  output_rectf.Offset(software_surface.coord_mapping.output_pre_translate);
+  SbBlitterRect output_blitter_rect = RectFToBlitterRect(output_rectf);
 
-  SkImageInfo output_image_info = SkImageInfo::MakeN32(
-      coord_mapping.output_bounds.width(), coord_mapping.output_bounds.height(),
-      kPremul_SkAlphaType);
-
-  // Allocate the pixels for the output image.
-  SbBlitterPixelDataFormat blitter_pixel_data_format =
-      SkiaToBlitterPixelFormat(output_image_info.colorType());
-  DCHECK(SbBlitterIsPixelFormatSupportedByPixelData(device_,
-                                                    blitter_pixel_data_format));
-  SbBlitterPixelData pixel_data = SbBlitterCreatePixelData(
-      device_, coord_mapping.output_bounds.width(),
-      coord_mapping.output_bounds.height(), blitter_pixel_data_format);
-  CHECK(SbBlitterIsPixelDataValid(pixel_data));
-
-  SkBitmap bitmap;
-  bitmap.installPixels(output_image_info,
-                       SbBlitterGetPixelDataPointer(pixel_data),
-                       SbBlitterGetPixelDataPitchInBytes(pixel_data));
-
-  // Setup our Skia canvas that we will be using as the target for all CPU Skia
-  // output.
-  SkCanvas canvas(bitmap);
-  canvas.clear(SkColorSetARGB(0, 0, 0, 0));
-
-  Transform sub_render_transform(coord_mapping.sub_render_transform);
-
-  // Now setup our canvas so that the render tree will be rendered to the top
-  // left corner instead of at node->GetBounds().origin().
-  canvas.translate(sub_render_transform.translate().x(),
-                   sub_render_transform.translate().y());
-  // And finally set the scale on our target canvas to match that of the current
-  // |render_state_.transform|.
-  canvas.scale(sub_render_transform.scale().x(),
-               sub_render_transform.scale().y());
-
-  // Use the Skia software rasterizer to render our subtree.
-  software_rasterizer_->Submit(node, &canvas);
-
-  // Create a surface out of the now populated pixel data.
-  SbBlitterSurface surface =
-      SbBlitterCreateSurfaceFromPixelData(device_, pixel_data);
-
-  math::RectF output_rect = coord_mapping.output_bounds;
-  output_rect.Offset(coord_mapping.output_pre_translate);
-  output_rect.Offset(render_state_.transform.translate());
-
-  // Finally blit the resulting surface to our actual render target.
   SbBlitterSetBlending(context_, true);
-  SbBlitterSetModulateBlitsWithColor(context_, false);
-  SbBlitterBlitRectToRect(
-      context_, surface,
-      SbBlitterMakeRect(0, 0, coord_mapping.output_bounds.width(),
-                        coord_mapping.output_bounds.height()),
-      RectFToBlitterRect(output_rect));
 
-  // Clean up our temporary surface.
-  SbBlitterDestroySurface(surface);
+  if (render_state_.opacity < 1.0f) {
+    SbBlitterSetModulateBlitsWithColor(context_, true);
+    SbBlitterSetColor(
+        context_,
+        SbBlitterColorFromRGBA(255, 255, 255,
+                               static_cast<int>(255 * render_state_.opacity)));
+  } else {
+    SbBlitterSetModulateBlitsWithColor(context_, false);
+  }
+
+// Blit the software rasterized surface to our actual render target.
+#if defined(ENABLE_DEBUG_CONSOLE)
+  if (render_state_.highlight_software_draws && software_surface.created) {
+    SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 255, 0, 255));
+    SbBlitterFillRect(context_, output_blitter_rect);
+  } else  // NOLINT(readability/braces)
+#endif    // defined(ENABLE_DEBUG_CONSOLE)
+  {
+    SbBlitterBlitRectToRect(
+        context_, software_surface.surface,
+        SbBlitterMakeRect(
+            0, 0, software_surface.coord_mapping.output_bounds.width(),
+            software_surface.coord_mapping.output_bounds.height()),
+        output_blitter_rect);
+  }
 }
 
 scoped_ptr<RenderTreeNodeVisitor::OffscreenRender>
@@ -496,8 +509,8 @@
       RenderState(
           render_target, Transform(coord_mapping.sub_render_transform),
           BoundsStack(context_, Rect(coord_mapping.output_bounds.size()))),
-      software_rasterizer_, scratch_surface_cache_, surface_cache_delegate_,
-      surface_cache_);
+      scratch_surface_cache_, surface_cache_delegate_, surface_cache_,
+      software_surface_cache_);
   node->Accept(&sub_visitor);
 
   // Restore our original render target.
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
index 4677308..4d9e0ee 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
@@ -32,11 +32,11 @@
 #include "cobalt/render_tree/rect_node.h"
 #include "cobalt/render_tree/rect_shadow_node.h"
 #include "cobalt/render_tree/text_node.h"
+#include "cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h"
 #include "cobalt/renderer/rasterizer/blitter/render_state.h"
 #include "cobalt/renderer/rasterizer/blitter/scratch_surface_cache.h"
 #include "cobalt/renderer/rasterizer/blitter/surface_cache_delegate.h"
 #include "cobalt/renderer/rasterizer/common/surface_cache.h"
-#include "cobalt/renderer/rasterizer/skia/software_rasterizer.h"
 
 #include "starboard/blitter.h"
 
@@ -62,10 +62,10 @@
  public:
   RenderTreeNodeVisitor(SbBlitterDevice device, SbBlitterContext context,
                         const RenderState& render_state,
-                        skia::SoftwareRasterizer* software_rasterizer,
                         ScratchSurfaceCache* scratch_surface_cache,
                         SurfaceCacheDelegate* surface_cache_delegate,
-                        common::SurfaceCache* surface_cache);
+                        common::SurfaceCache* surface_cache,
+                        CachedSoftwareRasterizer* software_surface_cache);
 
   void Visit(render_tree::animations::AnimateNode* animate_node) OVERRIDE {
     NOTREACHED();
@@ -99,10 +99,6 @@
   };
   scoped_ptr<OffscreenRender> RenderToOffscreenSurface(render_tree::Node* node);
 
-  // We maintain an instance of a software skia rasterizer which is used to
-  // render anything that we cannot render via the Blitter API directly.
-  skia::SoftwareRasterizer* software_rasterizer_;
-
   SbBlitterDevice device_;
   SbBlitterContext context_;
 
@@ -118,6 +114,10 @@
   base::optional<SurfaceCacheDelegate::ScopedContext>
       surface_cache_scoped_context_;
 
+  // We fallback to software rasterization in order to render anything that we
+  // cannot render via the Blitter API directly.  We cache the results.
+  CachedSoftwareRasterizer* software_surface_cache_;
+
   DISALLOW_COPY_AND_ASSIGN(RenderTreeNodeVisitor);
 };
 
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
index ae7cbd9..f518c52 100644
--- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
@@ -48,7 +48,8 @@
 
 bool ResourceProvider::AlphaFormatSupported(
     render_tree::AlphaFormat alpha_format) {
-  return alpha_format == render_tree::kAlphaFormatPremultiplied;
+  return alpha_format == render_tree::kAlphaFormatPremultiplied ||
+         alpha_format == render_tree::kAlphaFormatOpaque;
 }
 
 scoped_ptr<render_tree::ImageData> ResourceProvider::AllocateImageData(
diff --git a/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h b/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h
index 4c114b1..c55f7cc 100644
--- a/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h
+++ b/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h
@@ -35,6 +35,9 @@
 namespace common {
 
 struct OffscreenRenderCoordinateMapping {
+  OffscreenRenderCoordinateMapping()
+      : sub_render_transform(math::Matrix3F::Zeros()) {}
+
   OffscreenRenderCoordinateMapping(const math::Rect& output_bounds,
                                    const math::Vector2dF& output_pre_translate,
                                    const math::Vector2dF& output_post_scale,
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index 6f821ed..aaadf4b 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -436,15 +436,21 @@
   return resource_provider->CreateImage(image_data.Pass());
 }
 
-scoped_refptr<Image> CreateColoredCheckersImage(
-    ResourceProvider* resource_provider, const SizeF& dimensions) {
+scoped_refptr<Image> CreateColoredCheckersImageForAlphaFormat(
+    ResourceProvider* resource_provider, const SizeF& dimensions,
+    render_tree::AlphaFormat alpha_format) {
   std::vector<RGBAWord> row_colors;
   row_colors.push_back(RGBAWord(255, 0, 0, 255));
   row_colors.push_back(RGBAWord(0, 255, 0, 255));
   row_colors.push_back(RGBAWord(0, 0, 255, 255));
   return CreateAdditiveColorGridImage(resource_provider, dimensions, row_colors,
-                                      row_colors,
-                                      render_tree::kAlphaFormatPremultiplied);
+                                      row_colors, alpha_format);
+}
+
+scoped_refptr<Image> CreateColoredCheckersImage(
+    ResourceProvider* resource_provider, const SizeF& dimensions) {
+  return CreateColoredCheckersImageForAlphaFormat(
+      resource_provider, dimensions, render_tree::kAlphaFormatPremultiplied);
 }
 
 }  // namespace
@@ -457,6 +463,24 @@
   TestTree(new ImageNode(image));
 }
 
+TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatNone) {
+  scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat(
+      GetResourceProvider(), output_surface_size(),
+      render_tree::kAlphaFormatOpaque);
+
+  TestTree(new ImageNode(image));
+}
+
+TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatNoneAndRoundedCorners) {
+  scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat(
+      GetResourceProvider(), output_surface_size(),
+      render_tree::kAlphaFormatOpaque);
+
+  TestTree(new FilterNode(
+      ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)),
+      new ImageNode(image)));
+}
+
 TEST_F(PixelTest, SingleRGBAImageLargerThanRenderTarget) {
   // Tests that rasterizing images that are larger than the render target
   // work as expected (e.g. they are cropped).
diff --git a/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc b/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc
index 1ca29c1..4b6e10c 100644
--- a/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc
+++ b/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc
@@ -49,6 +49,8 @@
       return kPremul_SkAlphaType;
     case render_tree::kAlphaFormatUnpremultiplied:
       return kUnpremul_SkAlphaType;
+    case render_tree::kAlphaFormatOpaque:
+      return kOpaque_SkAlphaType;
     default: DLOG(FATAL) << "Unknown render tree alpha format!";
   }
   return kUnpremul_SkAlphaType;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
index 0300dd6..2c8db59 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
@@ -164,7 +164,9 @@
     scoped_ptr<HardwareImageData> image_data,
     backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context,
     MessageLoop* rasterizer_message_loop)
-    : size_(image_data->GetDescriptor().size),
+    : is_opaque_(image_data->GetDescriptor().alpha_format ==
+                 render_tree::kAlphaFormatOpaque),
+      size_(image_data->GetDescriptor().size),
       rasterizer_message_loop_(rasterizer_message_loop) {
   TRACE_EVENT0("cobalt::renderer",
                "HardwareFrontendImage::HardwareFrontendImage()");
@@ -180,7 +182,8 @@
     intptr_t offset, const render_tree::ImageDataDescriptor& descriptor,
     backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context,
     MessageLoop* rasterizer_message_loop)
-    : size_(descriptor.size),
+    : is_opaque_(descriptor.alpha_format == render_tree::kAlphaFormatOpaque),
+      size_(descriptor.size),
       rasterizer_message_loop_(rasterizer_message_loop) {
   TRACE_EVENT0("cobalt::renderer",
                "HardwareFrontendImage::HardwareFrontendImage()");
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.h b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
index 1590567..bb44b1a 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
@@ -102,6 +102,8 @@
 
   bool EnsureInitialized() OVERRIDE;
 
+  bool IsOpaque() const OVERRIDE { return is_opaque_; }
+
  private:
   ~HardwareFrontendImage() OVERRIDE;
 
@@ -116,6 +118,10 @@
       intptr_t offset, const render_tree::ImageDataDescriptor& descriptor,
       backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context);
 
+  // Track if we have any alpha or not, which can enable optimizations in the
+  // case that alpha is not present.
+  bool is_opaque_;
+
   // We shadow the image dimensions so they can be quickly looked up from just
   // the frontend image object.
   const math::Size size_;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 1f97820..a17b893 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -68,7 +68,8 @@
 
 bool HardwareResourceProvider::AlphaFormatSupported(
     render_tree::AlphaFormat alpha_format) {
-  return alpha_format == render_tree::kAlphaFormatPremultiplied;
+  return alpha_format == render_tree::kAlphaFormatPremultiplied ||
+         alpha_format == render_tree::kAlphaFormatOpaque;
 }
 
 scoped_ptr<ImageData> HardwareResourceProvider::AllocateImageData(
@@ -96,7 +97,8 @@
   const render_tree::ImageDataDescriptor& descriptor =
       skia_hardware_source_data->GetDescriptor();
 
-  DCHECK_EQ(render_tree::kAlphaFormatPremultiplied, descriptor.alpha_format);
+  DCHECK(descriptor.alpha_format == render_tree::kAlphaFormatPremultiplied ||
+         descriptor.alpha_format == render_tree::kAlphaFormatOpaque);
 #if defined(COBALT_BUILD_TYPE_DEBUG)
   Image::DCheckForPremultipliedAlpha(descriptor.size, descriptor.pitch_in_bytes,
                                      descriptor.pixel_format,
diff --git a/src/cobalt/renderer/rasterizer/skia/image.h b/src/cobalt/renderer/rasterizer/skia/image.h
index bb696aa..da4a5dc 100644
--- a/src/cobalt/renderer/rasterizer/skia/image.h
+++ b/src/cobalt/renderer/rasterizer/skia/image.h
@@ -79,6 +79,9 @@
   base::TypeId GetTypeId() const OVERRIDE {
     return base::GetTypeId<MultiPlaneImage>();
   }
+
+  // All currently supported MultiPlaneImage formats do not feature alpha.
+  bool IsOpaque() const OVERRIDE { return true; }
 };
 
 }  // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
index e7225dc..06ed04b 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -505,12 +505,14 @@
 }
 
 SkPaint CreateSkPaintForImageRendering(
-    const RenderTreeNodeVisitorDrawState& draw_state) {
+    const RenderTreeNodeVisitorDrawState& draw_state, bool is_opaque) {
   SkPaint paint;
   paint.setFilterLevel(SkPaint::kLow_FilterLevel);
 
   if (draw_state.opacity < 1.0f) {
     paint.setAlpha(draw_state.opacity * 255);
+  } else if (is_opaque) {
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   }
 
   return paint;
@@ -520,7 +522,8 @@
                             RenderTreeNodeVisitorDrawState* draw_state,
                             const math::RectF& destination_rect,
                             const math::Matrix3F* local_transform) {
-  SkPaint paint = CreateSkPaintForImageRendering(*draw_state);
+  SkPaint paint = CreateSkPaintForImageRendering(
+      *draw_state, single_plane_image->IsOpaque());
 
   // In the most frequent by far case where the normalized transformed image
   // texture coordinates lie within the unit square, then we must ensure NOT
@@ -608,7 +611,8 @@
     }
   }
 
-  SkPaint paint = CreateSkPaintForImageRendering(*draw_state);
+  SkPaint paint = CreateSkPaintForImageRendering(*draw_state,
+                                                 multi_plane_image->IsOpaque());
   paint.setShader(yuv2rgb_shader);
   draw_state->render_target->drawRect(CobaltRectFToSkiaRect(destination_rect),
                                       paint);
diff --git a/src/cobalt/renderer/rasterizer/skia/software_image.cc b/src/cobalt/renderer/rasterizer/skia/software_image.cc
index 5f68cde..9c05a86 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_image.cc
+++ b/src/cobalt/renderer/rasterizer/skia/software_image.cc
@@ -79,6 +79,7 @@
 
 void SoftwareImage::Initialize(
     uint8_t* source_data, const render_tree::ImageDataDescriptor& descriptor) {
+  is_opaque_ = (descriptor.alpha_format == render_tree::kAlphaFormatOpaque);
   SkAlphaType skia_alpha_format =
       RenderTreeAlphaFormatToSkia(descriptor.alpha_format);
   DCHECK_EQ(kPremul_SkAlphaType, skia_alpha_format);
diff --git a/src/cobalt/renderer/rasterizer/skia/software_image.h b/src/cobalt/renderer/rasterizer/skia/software_image.h
index 63afe6a..d01f212 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/software_image.h
@@ -57,6 +57,8 @@
 
   bool EnsureInitialized() OVERRIDE { return false; }
 
+  bool IsOpaque() const OVERRIDE { return is_opaque_; }
+
  private:
   void Initialize(uint8_t* source_data,
                   const render_tree::ImageDataDescriptor& descriptor);
@@ -64,6 +66,7 @@
   scoped_array<uint8_t> owned_pixel_data_;
   SkBitmap bitmap_;
   math::Size size_;
+  bool is_opaque_;
 };
 
 class SoftwareRawImageMemory : public render_tree::RawImageMemory {
@@ -97,6 +100,10 @@
 
   bool EnsureInitialized() OVERRIDE { return false; }
 
+  // Currently, all supported multiplane images (e.g. mostly YUV) do not
+  // support alpha, so multiplane images will always be opaque.
+  bool IsOpaque() const OVERRIDE { return true; }
+
  private:
   math::Size size_;
   render_tree::MultiPlaneImageFormat format_;
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
index be05ccf..d3af607 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
@@ -47,7 +47,8 @@
 
 bool SoftwareResourceProvider::AlphaFormatSupported(
     render_tree::AlphaFormat alpha_format) {
-  return alpha_format == render_tree::kAlphaFormatPremultiplied;
+  return alpha_format == render_tree::kAlphaFormatPremultiplied ||
+         alpha_format == render_tree::kAlphaFormatOpaque;
 }
 
 scoped_ptr<ImageData> SoftwareResourceProvider::AllocateImageData(
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNone-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNone-expected.png
new file mode 100644
index 0000000..b8638eb
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNone-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNoneAndRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNoneAndRoundedCorners-expected.png
new file mode 100644
index 0000000..2076087
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatNoneAndRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/renderer_module.h b/src/cobalt/renderer/renderer_module.h
index 44099d6..96226ca 100644
--- a/src/cobalt/renderer/renderer_module.h
+++ b/src/cobalt/renderer/renderer_module.h
@@ -55,6 +55,12 @@
     // using the hardware-accelerated Skia rasterizer.
     int skia_cache_size_in_bytes;
 
+    // Only relevant if you are using the Blitter API.
+    // Determines the capacity of the software surface cache, which is used to
+    // cache all surfaces that are rendered via a software rasterizer to avoid
+    // re-rendering them.
+    int software_surface_cache_size_in_bytes;
+
     // Determines the capacity of the surface cache.  The surface cache tracks
     // which render tree nodes are being re-used across frames and stores the
     // nodes that are most CPU-expensive to render into surfaces.
diff --git a/src/cobalt/renderer/renderer_module_default_options_starboard.cc b/src/cobalt/renderer/renderer_module_default_options_starboard.cc
index 7a00bd5..7ee0e3e 100644
--- a/src/cobalt/renderer/renderer_module_default_options_starboard.cc
+++ b/src/cobalt/renderer/renderer_module_default_options_starboard.cc
@@ -54,7 +54,8 @@
   return scoped_ptr<rasterizer::Rasterizer>(
       new rasterizer::blitter::HardwareRasterizer(
           graphics_context, options.scratch_surface_cache_size_in_bytes,
-          options.surface_cache_size_in_bytes));
+          options.surface_cache_size_in_bytes,
+          options.software_surface_cache_size_in_bytes));
 #endif  // COBALT_FORCE_SOFTWARE_RASTERIZER
 #else
 #error "Either GLES2 or the Starboard Blitter API must be available."
@@ -70,6 +71,8 @@
   skia_cache_size_in_bytes = COBALT_SKIA_CACHE_SIZE_IN_BYTES;
   scratch_surface_cache_size_in_bytes =
       COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES;
+  software_surface_cache_size_in_bytes =
+      COBALT_SOFTWARE_SURFACE_CACHE_SIZE_IN_BYTES;
 
   // If there is no need to frequently flip the display buffer, then enable
   // support for an optimization where the scene is not re-rasterized each frame
diff --git a/src/cobalt/renderer/renderer_parameters_setup.gypi b/src/cobalt/renderer/renderer_parameters_setup.gypi
index 95724f6..ed0313b 100644
--- a/src/cobalt/renderer/renderer_parameters_setup.gypi
+++ b/src/cobalt/renderer/renderer_parameters_setup.gypi
@@ -17,6 +17,7 @@
     'COBALT_SKIA_CACHE_SIZE_IN_BYTES=<(skia_cache_size_in_bytes)',
     'COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES=<(scratch_surface_cache_size_in_bytes)',
     'COBALT_SURFACE_CACHE_SIZE_IN_BYTES=<(surface_cache_size_in_bytes)',
+    'COBALT_SOFTWARE_SURFACE_CACHE_SIZE_IN_BYTES=<(software_surface_cache_size_in_bytes)',
   ],
   'conditions': [
     ['rasterizer_type == "software"', {
diff --git a/src/cobalt/renderer/test/png_utils/png_decode.cc b/src/cobalt/renderer/test/png_utils/png_decode.cc
index 4bf217b..b65618b 100644
--- a/src/cobalt/renderer/test/png_utils/png_decode.cc
+++ b/src/cobalt/renderer/test/png_utils/png_decode.cc
@@ -209,6 +209,9 @@
         }
       }
     } break;
+    case render_tree::kAlphaFormatOpaque: {
+      NOTREACHED() << "kAlphaFormatOpaque not supported.";
+    } break;
   }
 
   // End transformations. Get the updated info, and then verify.
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.cc b/src/cobalt/script/mozjs/mozjs_global_environment.cc
index 1cf31fd..89ddeb2 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.cc
@@ -230,10 +230,8 @@
   JSAutoCompartment auto_compartment(context_, global_object_proxy_);
   JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_);
   JS::RootedValue result_value(context_);
-  if (!EvaluateScriptInternal(source_code, &result_value)) {
-    return false;
-  }
-  if (out_opaque_handle) {
+  bool success = EvaluateScriptInternal(source_code, &result_value);
+  if (success && out_opaque_handle) {
     JS::RootedObject js_object(context_);
     JS_ValueToObject(context_, result_value, js_object.address());
     MozjsObjectHandleHolder mozjs_object_holder(js_object, context_,
@@ -241,7 +239,7 @@
     out_opaque_handle->emplace(owning_object.get(), mozjs_object_holder);
   }
   JS_RestoreExceptionState(context_, previous_exception_state);
-  return true;
+  return success;
 }
 
 bool MozjsGlobalEnvironment::EvaluateScriptInternal(
diff --git a/src/cobalt/speech/endpointer/endpointer.cc b/src/cobalt/speech/endpointer/endpointer.cc
index b0c09e9..0b19a22 100644
--- a/src/cobalt/speech/endpointer/endpointer.cc
+++ b/src/cobalt/speech/endpointer/endpointer.cc
@@ -9,7 +9,10 @@
 using base::Time;
 
 namespace {
-const int kFrameRate = 50;  // 1 frame = 20ms of audio.
+// Only send |kFrameSize| of audio data to energy endpointer each time.
+// It should be smaller than the samples of audio bus which is passed in
+// |ProcessAudio|.
+const int kFrameSize = 160;
 }
 
 namespace cobalt {
@@ -20,11 +23,9 @@
       speech_input_complete_silence_length_us_(-1),
       audio_frame_time_us_(0),
       sample_rate_(sample_rate),
-      frame_size_(0) {
+      frame_rate_(sample_rate / kFrameSize) {
   Reset();
 
-  frame_size_ = static_cast<int>(sample_rate / static_cast<float>(kFrameRate));
-
   speech_input_minimum_length_us_ =
       static_cast<int64_t>(1.7 * Time::kMicrosecondsPerSecond);
   speech_input_complete_silence_length_us_ =
@@ -36,8 +37,8 @@
 
   // Set the default configuration for Push To Talk mode.
   EnergyEndpointerParams ep_config;
-  ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate));
-  ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate));
+  ep_config.set_frame_period(1.0f / static_cast<float>(frame_rate_));
+  ep_config.set_frame_duration(1.0f / static_cast<float>(frame_rate_));
   ep_config.set_endpoint_margin(0.2f);
   ep_config.set_onset_window(0.15f);
   ep_config.set_speech_on_window(0.4f);
@@ -92,6 +93,8 @@
 EpStatus Endpointer::ProcessAudio(
     const ShellAudioBus& audio_bus, float* rms_out) {
   DCHECK_EQ(audio_bus.channels(), 1);
+  DCHECK_LE(kFrameSize, static_cast<int>(audio_bus.frames()));
+
   const size_t num_samples = audio_bus.frames();
   const int16_t* audio_data = NULL;
 
@@ -111,19 +114,17 @@
 
   EpStatus ep_status = EP_PRE_SPEECH;
 
-  // Process the input data in blocks of frame_size_, dropping any incomplete
+  // Process the input data in blocks of kFrameSize, dropping any incomplete
   // frames at the end (which is ok since typically the caller will be recording
   // audio in multiples of our frame size).
   int sample_index = 0;
-  while (static_cast<size_t>(sample_index + frame_size_) <= num_samples) {
+  while (static_cast<size_t>(sample_index + kFrameSize) <= num_samples) {
     // Have the endpointer process the frame.
-    energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_,
-                                         audio_data + sample_index,
-                                         frame_size_,
-                                         rms_out);
-    sample_index += frame_size_;
-    audio_frame_time_us_ += (frame_size_ * Time::kMicrosecondsPerSecond) /
-                         sample_rate_;
+    energy_endpointer_.ProcessAudioFrame(
+        audio_frame_time_us_, audio_data + sample_index, kFrameSize, rms_out);
+    sample_index += kFrameSize;
+    audio_frame_time_us_ +=
+        (kFrameSize * Time::kMicrosecondsPerSecond) / sample_rate_;
 
     // Get the status of the endpointer.
     int64_t ep_time;
diff --git a/src/cobalt/speech/endpointer/endpointer.h b/src/cobalt/speech/endpointer/endpointer.h
index b8d46f5..f6c818f 100644
--- a/src/cobalt/speech/endpointer/endpointer.h
+++ b/src/cobalt/speech/endpointer/endpointer.h
@@ -100,6 +100,8 @@
     return speech_input_complete_;
   }
 
+  int sample_rate() const { return sample_rate_; }
+
   // RMS background noise level in dB.
   float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); }
 
@@ -147,7 +149,8 @@
   bool speech_input_complete_;
   EnergyEndpointer energy_endpointer_;
   int sample_rate_;
-  int32_t frame_size_;
+  // 1 frame = (1 / frame_rate_) second of audio.
+  int frame_rate_;
 };
 
 }  // namespace speech
diff --git a/src/cobalt/speech/endpointer_delegate.cc b/src/cobalt/speech/endpointer_delegate.cc
new file mode 100644
index 0000000..a3d0444
--- /dev/null
+++ b/src/cobalt/speech/endpointer_delegate.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/speech/endpointer_delegate.h"
+
+#include "base/time.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+const int kEndPointerEstimationTimeInMillisecond = 300;
+// If used in noisy conditions, the endpointer should be started and run in the
+// EnvironmentEstimation mode, for at least 200ms, before switching to
+// UserInputMode.
+COMPILE_ASSERT(kEndPointerEstimationTimeInMillisecond >= 200,
+               in_environment_estimation_mode_for_at_least_200ms);
+}  // namespace
+
+EndPointerDelegate::EndPointerDelegate(int sample_rate)
+    : endpointer_(sample_rate),
+      num_samples_recorded_(0),
+      is_first_time_sound_started_(false) {}
+
+EndPointerDelegate::~EndPointerDelegate() {}
+
+void EndPointerDelegate::Start() {
+  num_samples_recorded_ = 0;
+  is_first_time_sound_started_ = false;
+
+  endpointer_.StartSession();
+  endpointer_.SetEnvironmentEstimationMode();
+}
+
+void EndPointerDelegate::Stop() { endpointer_.EndSession(); }
+
+bool EndPointerDelegate::IsFirstTimeSoundStarted(
+    const ShellAudioBus& audio_bus) {
+  if (is_first_time_sound_started_) {
+    return false;
+  }
+
+  num_samples_recorded_ += static_cast<int>(audio_bus.frames());
+  if (endpointer_.IsEstimatingEnvironment() &&
+      num_samples_recorded_ * 1000 / endpointer_.sample_rate() >
+          kEndPointerEstimationTimeInMillisecond) {
+    // Switch to user input mode.
+    endpointer_.SetUserInputMode();
+  }
+
+  endpointer_.ProcessAudio(audio_bus, NULL);
+  int64_t ep_time;
+  EpStatus status = endpointer_.Status(&ep_time);
+  if (status == EP_POSSIBLE_ONSET) {
+    is_first_time_sound_started_ = true;
+  }
+
+  return is_first_time_sound_started_;
+}
+
+}  // namespace speech
+}  // namespace cobalt
diff --git a/src/cobalt/speech/endpointer_delegate.h b/src/cobalt/speech/endpointer_delegate.h
new file mode 100644
index 0000000..8a3aafa
--- /dev/null
+++ b/src/cobalt/speech/endpointer_delegate.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
+#define COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
+
+#include "cobalt/speech/endpointer/endpointer.h"
+#include "media/base/audio_bus.h"
+
+namespace cobalt {
+namespace speech {
+
+// Delegate of endpointer for detecting the first time sound started in one
+// speech session (from start speaking to end of speaking).
+class EndPointerDelegate {
+ public:
+  typedef ::media::ShellAudioBus ShellAudioBus;
+
+  explicit EndPointerDelegate(int sample_rate);
+  ~EndPointerDelegate();
+
+  // Start endpointer session for sound processing.
+  void Start();
+  // Stop endpointer session.
+  void Stop();
+
+  // Return true if it is the first time that the sound started.
+  bool IsFirstTimeSoundStarted(const ShellAudioBus& audio_bus);
+
+ private:
+  // Used for detecting sound start event.
+  Endpointer endpointer_;
+  // Used for recording the number of samples before notifying that it is the
+  // first time the sound started. The |endpointer_| should be started and run
+  // in the EnvironmentEstimation mode if used in noisy conditions for at least
+  // 200ms before switching to UserInputMode.
+  int num_samples_recorded_;
+
+  // Record if it is the first time that the sound started.
+  bool is_first_time_sound_started_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
diff --git a/src/cobalt/speech/mic.h b/src/cobalt/speech/mic.h
deleted file mode 100644
index 506632a..0000000
--- a/src/cobalt/speech/mic.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef COBALT_SPEECH_MIC_H_
-#define COBALT_SPEECH_MIC_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "media/base/shell_audio_bus.h"
-
-namespace cobalt {
-namespace speech {
-
-// An abstract class is used for interacting platform specific microphone.
-class Mic {
- public:
-  typedef ::media::ShellAudioBus ShellAudioBus;
-  typedef base::Callback<void(scoped_ptr<ShellAudioBus>)> DataReceivedCallback;
-  typedef base::Callback<void(void)> CompletionCallback;
-  typedef base::Callback<void(void)> ErrorCallback;
-
-  virtual ~Mic() {}
-
-  static scoped_ptr<Mic> Create(int sample_rate,
-                                const DataReceivedCallback& data_received,
-                                const CompletionCallback& completion,
-                                const ErrorCallback& error);
-
-  // Multiple calls to Start/Stop are allowed, the implementation should take
-  // care of multiple calls. The Start/Stop methods are required to be called in
-  // the same thread.
-  // Start microphone to receive voice.
-  virtual void Start() = 0;
-  // Stop microphone from receiving voice.
-  virtual void Stop() = 0;
-
- protected:
-  Mic(int sample_rate, const DataReceivedCallback& data_received,
-      const CompletionCallback& completion, const ErrorCallback& error)
-      : sample_rate_(sample_rate),
-        data_received_callback_(data_received),
-        completion_callback_(completion),
-        error_callback_(error) {}
-
-  int sample_rate_;
-  const DataReceivedCallback data_received_callback_;
-  const CompletionCallback completion_callback_;
-  const ErrorCallback error_callback_;
-};
-
-std::string GetSpeechAPIKey();
-
-}  // namespace speech
-}  // namespace cobalt
-
-#endif  // COBALT_SPEECH_MIC_H_
diff --git a/src/cobalt/speech/mic_linux.cc b/src/cobalt/speech/mic_linux.cc
deleted file mode 100644
index d7f6809..0000000
--- a/src/cobalt/speech/mic_linux.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "cobalt/speech/mic.h"
-
-namespace cobalt {
-namespace speech {
-
-class MicLinux : public Mic {
- public:
-  MicLinux(int sample_rate, const DataReceivedCallback& data_received,
-           const CompletionCallback& completion, const ErrorCallback& error)
-      : Mic(sample_rate, data_received, completion, error) {}
-
-  void Start() OVERRIDE { NOTIMPLEMENTED(); }
-  void Stop() OVERRIDE { NOTIMPLEMENTED(); }
-};
-
-// static
-scoped_ptr<Mic> Mic::Create(int sample_rate,
-                            const DataReceivedCallback& data_received,
-                            const CompletionCallback& completion,
-                            const ErrorCallback& error) {
-  return make_scoped_ptr<Mic>(
-      new MicLinux(sample_rate, data_received, completion, error));
-}
-
-std::string GetSpeechAPIKey() { return ""; }
-
-}  // namespace speech
-}  // namespace cobalt
diff --git a/src/cobalt/speech/mic_starboard.cc b/src/cobalt/speech/mic_starboard.cc
deleted file mode 100644
index c818dab..0000000
--- a/src/cobalt/speech/mic_starboard.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "cobalt/speech/mic.h"
-
-namespace cobalt {
-namespace speech {
-
-class MicStarboard : public Mic {
- public:
-  MicStarboard(int sample_rate, const DataReceivedCallback& data_received,
-               const CompletionCallback& completion, const ErrorCallback& error)
-      : Mic(sample_rate, data_received, completion, error) {}
-
-  void Start() OVERRIDE { NOTIMPLEMENTED(); }
-  void Stop() OVERRIDE { NOTIMPLEMENTED(); }
-};
-
-// static
-scoped_ptr<Mic> Mic::Create(int sample_rate,
-                            const DataReceivedCallback& data_received,
-                            const CompletionCallback& completion,
-                            const ErrorCallback& error) {
-  return make_scoped_ptr<Mic>(
-      new MicStarboard(sample_rate, data_received, completion, error));
-}
-
-std::string GetSpeechAPIKey() { return ""; }
-
-}  // namespace speech
-}  // namespace cobalt
diff --git a/src/cobalt/speech/mic_win.cc b/src/cobalt/speech/mic_win.cc
deleted file mode 100644
index 80c49fc..0000000
--- a/src/cobalt/speech/mic_win.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "cobalt/speech/mic.h"
-
-namespace cobalt {
-namespace speech {
-
-class MicWin : public Mic {
- public:
-  MicWin(int sample_rate, const DataReceivedCallback& data_received,
-         const CompletionCallback& completion, const ErrorCallback& error)
-      : Mic(sample_rate, data_received, completion, error) {}
-
-  void Start() OVERRIDE { NOTIMPLEMENTED(); }
-  void Stop() OVERRIDE { NOTIMPLEMENTED(); }
-};
-
-// static
-scoped_ptr<Mic> Mic::Create(int sample_rate,
-                            const DataReceivedCallback& data_received,
-                            const CompletionCallback& completion,
-                            const ErrorCallback& error) {
-  return make_scoped_ptr<Mic>(
-      new MicWin(sample_rate, data_received, completion, error));
-}
-
-std::string GetSpeechAPIKey() { return ""; }
-
-}  // namespace speech
-}  // namespace cobalt
diff --git a/src/cobalt/speech/microphone.h b/src/cobalt/speech/microphone.h
new file mode 100644
index 0000000..3b9e6ae
--- /dev/null
+++ b/src/cobalt/speech/microphone.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_MICROPHONE_H_
+#define COBALT_SPEECH_MICROPHONE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace cobalt {
+namespace speech {
+
+// An abstract class is used for interacting platform specific microphone.
+class Microphone {
+ public:
+  virtual ~Microphone() {}
+
+  // Opens microphone port and starts recording audio.
+  virtual bool Open() = 0;
+  // Reads audio data from microphone. The return value is the bytes that were
+  // read from microphone. Negative value indicates a read error. |data_size| is
+  // the requested read bytes.
+  virtual int Read(char* out_data, int data_size) = 0;
+  // Closes microphone port and stops recording audio.
+  virtual bool Close() = 0;
+  // Returns the minimum requested bytes per microphone read.
+  virtual int MinMicrophoneReadInBytes() = 0;
+  // Returns true if the microphone is valid.
+  virtual bool IsValid() = 0;
+
+ protected:
+  Microphone() {}
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // COBALT_SPEECH_MICROPHONE_H_
diff --git a/src/cobalt/speech/microphone_fake.cc b/src/cobalt/speech/microphone_fake.cc
new file mode 100644
index 0000000..7ee601c
--- /dev/null
+++ b/src/cobalt/speech/microphone_fake.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/speech/microphone_fake.h"
+
+#if defined(ENABLE_FAKE_MICROPHONE)
+
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/rand_util.h"
+#include "starboard/file.h"
+#include "starboard/memory.h"
+#include "starboard/time.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+const int kMaxBufferSize = 512 * 1024;
+const int kMinMicrophoneReadInBytes = 1024;
+// The possiblity of microphone creation failed is 1/20.
+const int kCreationRange = 20;
+// The possiblity of microphone open failed is 1/20.
+const int kOpenRange = 20;
+// The possiblity of microphone read failed is 1/200.
+const int kReadRange = 200;
+// The possiblity of microphone close failed is 1/20.
+const int kCloseRange = 20;
+const int kFailureNumber = 5;
+
+bool ShouldFail(int range) {
+  return base::RandGenerator(range) == kFailureNumber;
+}
+
+}  // namespace
+
+MicrophoneFake::MicrophoneFake()
+    : Microphone(),
+      file_length_(-1),
+      read_index_(0),
+      is_valid_(!ShouldFail(kCreationRange)) {
+  if (!is_valid_) {
+    SB_DLOG(WARNING) << "Mocking microphone creation failed.";
+    return;
+  }
+
+  FilePath audio_files_path;
+  SB_CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &audio_files_path));
+  audio_files_path = audio_files_path.Append(FILE_PATH_LITERAL("cobalt"))
+                         .Append(FILE_PATH_LITERAL("speech"))
+                         .Append(FILE_PATH_LITERAL("testdata"));
+
+  file_util::FileEnumerator file_enumerator(audio_files_path,
+                                            false /* Not recursive */,
+                                            file_util::FileEnumerator::FILES);
+  for (FilePath next = file_enumerator.Next(); !next.empty();
+       next = file_enumerator.Next()) {
+    file_paths_.push_back(next);
+  }
+}
+
+bool MicrophoneFake::Open() {
+  if (ShouldFail(kOpenRange)) {
+    SB_DLOG(WARNING) << "Mocking microphone open failed.";
+    return false;
+  }
+
+  SB_DCHECK(file_paths_.size() != 0);
+  uint64 random_index = base::RandGenerator(file_paths_.size());
+  starboard::ScopedFile file(file_paths_[random_index].value().c_str(),
+                             kSbFileOpenOnly | kSbFileRead, NULL, NULL);
+  SB_DCHECK(file.IsValid());
+  int file_buffer_size =
+      std::min(static_cast<int>(file.GetSize()), kMaxBufferSize);
+  SB_DCHECK(file_buffer_size > 0);
+  file_buffer_.reset(new char[file_buffer_size]);
+  int read_bytes = file.ReadAll(file_buffer_.get(), file_buffer_size);
+  if (read_bytes < 0) {
+    return false;
+  }
+
+  file_length_ = read_bytes;
+  return true;
+}
+
+int MicrophoneFake::Read(char* out_data, int data_size) {
+  if (ShouldFail(kReadRange)) {
+    SB_DLOG(WARNING) << "Mocking microphone read failed.";
+    return -1;
+  }
+
+  int copy_bytes = std::min(file_length_ - read_index_, data_size);
+  SbMemoryCopy(out_data, file_buffer_.get() + read_index_, copy_bytes);
+  read_index_ += copy_bytes;
+  if (read_index_ == file_length_) {
+    read_index_ = 0;
+  }
+
+  return copy_bytes;
+}
+
+bool MicrophoneFake::Close() {
+  file_buffer_.reset();
+  file_length_ = -1;
+  read_index_ = 0;
+
+  if (ShouldFail(kCloseRange)) {
+    SB_DLOG(WARNING) << "Mocking microphone close failed.";
+    return false;
+  }
+
+  return true;
+}
+
+int MicrophoneFake::MinMicrophoneReadInBytes() {
+  return kMinMicrophoneReadInBytes;
+}
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
diff --git a/src/cobalt/speech/microphone_fake.h b/src/cobalt/speech/microphone_fake.h
new file mode 100644
index 0000000..dd17c1c
--- /dev/null
+++ b/src/cobalt/speech/microphone_fake.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_MICROPHONE_FAKE_H_
+#define COBALT_SPEECH_MICROPHONE_FAKE_H_
+
+#include "cobalt/speech/speech_configuration.h"
+
+#if defined(ENABLE_FAKE_MICROPHONE)
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/speech/microphone.h"
+
+namespace cobalt {
+namespace speech {
+
+// Fake microphone to mock the speech input by reading from the pre-recorded
+// audio.
+class MicrophoneFake : public Microphone {
+ public:
+  MicrophoneFake();
+  ~MicrophoneFake() SB_OVERRIDE {}
+
+  bool Open() SB_OVERRIDE;
+  int Read(char* out_data, int data_size) SB_OVERRIDE;
+  bool Close() SB_OVERRIDE;
+  int MinMicrophoneReadInBytes() SB_OVERRIDE;
+  bool IsValid() SB_OVERRIDE { return is_valid_; }
+
+ private:
+  std::vector<FilePath> file_paths_;
+  scoped_array<char> file_buffer_;
+  int file_length_;
+  int read_index_;
+  bool is_valid_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#endif  // COBALT_SPEECH_MICROPHONE_FAKE_H_
diff --git a/src/cobalt/speech/microphone_manager.cc b/src/cobalt/speech/microphone_manager.cc
new file mode 100644
index 0000000..7b491d9
--- /dev/null
+++ b/src/cobalt/speech/microphone_manager.cc
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/speech/microphone_manager.h"
+
+#if defined(ENABLE_FAKE_MICROPHONE)
+#include "cobalt/speech/microphone_fake.h"
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#if defined(SB_USE_SB_MICROPHONE)
+#include "cobalt/speech/microphone_starboard.h"
+#endif  // defined(SB_USE_SB_MICROPHONE)
+#include "cobalt/speech/speech_recognition_error.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+// Size of an audio buffer.
+const int kBufferSizeInBytes = 8 * 1024;
+// The frequency which we read the data from devices.
+const float kMicReadRateInHertz = 60.0f;
+}  // namespace
+
+MicrophoneManager::MicrophoneManager(int sample_rate,
+                                     const DataReceivedCallback& data_received,
+                                     const CompletionCallback& completion,
+                                     const ErrorCallback& error,
+                                     bool enable_fake_microphone)
+    : sample_rate_(sample_rate),
+      data_received_callback_(data_received),
+      completion_callback_(completion),
+      error_callback_(error),
+#if defined(ENABLE_FAKE_MICROPHONE)
+      enable_fake_microphone_(enable_fake_microphone),
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+      state_(kStopped),
+      thread_("microphone_thread") {
+  UNREFERENCED_PARAMETER(sample_rate_);
+#if defined(ENABLE_FAKE_MICROPHONE)
+  UNREFERENCED_PARAMETER(enable_fake_microphone_);
+#else
+  UNREFERENCED_PARAMETER(enable_fake_microphone);
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+  thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0));
+}
+
+MicrophoneManager::~MicrophoneManager() {
+  thread_.message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&MicrophoneManager::DestroyInternal, base::Unretained(this)));
+}
+
+void MicrophoneManager::Open() {
+  thread_.message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&MicrophoneManager::OpenInternal, base::Unretained(this)));
+}
+
+void MicrophoneManager::Close() {
+  thread_.message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&MicrophoneManager::CloseInternal, base::Unretained(this)));
+}
+
+bool MicrophoneManager::CreateIfNecessary() {
+  DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread());
+
+  if (microphone_) {
+    return true;
+  }
+
+#if defined(SB_USE_SB_MICROPHONE)
+#if defined(ENABLE_FAKE_MICROPHONE)
+  if (enable_fake_microphone_) {
+    microphone_.reset(new MicrophoneFake());
+  } else {
+    microphone_.reset(
+        new MicrophoneStarboard(sample_rate_, kBufferSizeInBytes));
+  }
+#else
+  microphone_.reset(new MicrophoneStarboard(sample_rate_, kBufferSizeInBytes));
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#endif  // defined(SB_USE_SB_MICROPHONE)
+
+  if (microphone_ && microphone_->IsValid()) {
+    state_ = kStopped;
+    return true;
+  } else {
+    DLOG(WARNING) << "Microphone creation failed.";
+    microphone_.reset();
+    state_ = kError;
+    error_callback_.Run(new SpeechRecognitionError(
+        SpeechRecognitionError::kAudioCapture, "No microphone available."));
+    return false;
+  }
+}
+
+void MicrophoneManager::OpenInternal() {
+  DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread());
+
+  // Try to create a valid microphone if necessary.
+  if (state_ == kStarted || !CreateIfNecessary()) {
+    return;
+  }
+
+  DCHECK(microphone_);
+  if (!microphone_->Open()) {
+    state_ = kError;
+    error_callback_.Run(new SpeechRecognitionError(
+        SpeechRecognitionError::kAborted, "Microphone open failed."));
+    return;
+  }
+
+  poll_mic_events_timer_.emplace();
+  // Setup a timer to poll for input events.
+  poll_mic_events_timer_->Start(
+      FROM_HERE, base::TimeDelta::FromMicroseconds(static_cast<int64>(
+                     base::Time::kMicrosecondsPerSecond / kMicReadRateInHertz)),
+      this, &MicrophoneManager::Read);
+  state_ = kStarted;
+}
+
+void MicrophoneManager::CloseInternal() {
+  DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread());
+
+  if (state_ == kStopped) {
+    return;
+  }
+
+  if (poll_mic_events_timer_) {
+    poll_mic_events_timer_->Stop();
+  }
+
+  if (microphone_) {
+    if (!microphone_->Close()) {
+      state_ = kError;
+      error_callback_.Run(new SpeechRecognitionError(
+          SpeechRecognitionError::kAborted, "Microphone close failed."));
+      return;
+    }
+    completion_callback_.Run();
+    state_ = kStopped;
+  }
+}
+
+void MicrophoneManager::Read() {
+  DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread());
+
+  DCHECK(state_ == kStarted);
+  DCHECK(microphone_);
+  DCHECK(microphone_->MinMicrophoneReadInBytes() <= kBufferSizeInBytes);
+
+  static int16_t samples[kBufferSizeInBytes / sizeof(int16_t)];
+  int read_bytes =
+      microphone_->Read(reinterpret_cast<char*>(samples), kBufferSizeInBytes);
+  if (read_bytes > 0) {
+    size_t frames = read_bytes / sizeof(int16_t);
+    scoped_ptr<ShellAudioBus> output_audio_bus(new ShellAudioBus(
+        1, frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+    output_audio_bus->Assign(ShellAudioBus(1, frames, samples));
+    data_received_callback_.Run(output_audio_bus.Pass());
+  } else if (read_bytes < 0) {
+    state_ = kError;
+    error_callback_.Run(new SpeechRecognitionError(
+        SpeechRecognitionError::kAborted, "Microphone read failed."));
+    poll_mic_events_timer_->Stop();
+  }
+}
+
+void MicrophoneManager::DestroyInternal() {
+  DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread());
+
+  microphone_.reset();
+  state_ = kStopped;
+}
+
+}  // namespace speech
+}  // namespace cobalt
diff --git a/src/cobalt/speech/microphone_manager.h b/src/cobalt/speech/microphone_manager.h
new file mode 100644
index 0000000..e91ca8c
--- /dev/null
+++ b/src/cobalt/speech/microphone_manager.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_MICROPHONE_MANAGER_H_
+#define COBALT_SPEECH_MICROPHONE_MANAGER_H_
+
+#include "cobalt/speech/speech_configuration.h"
+
+#include "base/callback.h"
+#include "base/optional.h"
+#include "base/threading/thread.h"
+#include "base/timer.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/speech/microphone.h"
+#include "media/base/shell_audio_bus.h"
+
+namespace cobalt {
+namespace speech {
+
+// This class is used for microphone creation, control and destruction. It has
+// a self-managed poller to fetch audio data from microphone.
+class MicrophoneManager {
+ public:
+  typedef ::media::ShellAudioBus ShellAudioBus;
+  typedef base::Callback<void(scoped_ptr<ShellAudioBus>)> DataReceivedCallback;
+  typedef base::Callback<void(void)> CompletionCallback;
+  typedef base::Callback<void(const scoped_refptr<dom::Event>&)> ErrorCallback;
+
+  MicrophoneManager(int sample_rate, const DataReceivedCallback& data_received,
+                    const CompletionCallback& completion,
+                    const ErrorCallback& error, bool enable_fake_microphone);
+
+  ~MicrophoneManager();
+
+  // Open microphone to receive voice and start a timer to fetch audio data from
+  // microphone.
+  void Open();
+  // Close microphone and stop the timer from receiving audio data.
+  void Close();
+
+ private:
+  enum State { kStarted, kStopped, kError };
+
+  // Returns true if the creation succeeded or the microphone is already a valid
+  // one.
+  bool CreateIfNecessary();
+  void OpenInternal();
+  void CloseInternal();
+  void DestroyInternal();
+
+  // Timer callback for fetching audio data.
+  void Read();
+
+  int sample_rate_;
+  const DataReceivedCallback data_received_callback_;
+  const CompletionCallback completion_callback_;
+  const ErrorCallback error_callback_;
+
+  scoped_ptr<Microphone> microphone_;
+#if defined(ENABLE_FAKE_MICROPHONE)
+  bool enable_fake_microphone_;
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+
+  // Microphone state.
+  State state_;
+  // Repeat timer to poll mic events.
+  base::optional<base::RepeatingTimer<MicrophoneManager> >
+      poll_mic_events_timer_;
+  // Microphone thread.
+  base::Thread thread_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  //  COBALT_SPEECH_MICROPHONE_MANAGER_H_
diff --git a/src/cobalt/speech/microphone_starboard.cc b/src/cobalt/speech/microphone_starboard.cc
new file mode 100644
index 0000000..676c0aa
--- /dev/null
+++ b/src/cobalt/speech/microphone_starboard.cc
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/speech/microphone_starboard.h"
+
+#if defined(SB_USE_SB_MICROPHONE)
+
+#include "starboard/log.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+// The maximum of microphones which can be supported. Currently only supports
+// one microphone.
+const int kNumberOfMicrophones = 1;
+}  // namespace
+
+MicrophoneStarboard::MicrophoneStarboard(int sample_rate, int buffer_size_bytes)
+    : Microphone(),
+      min_microphone_read_in_bytes_(-1),
+      microphone_(kSbMicrophoneInvalid) {
+  SbMicrophoneInfo info[kNumberOfMicrophones];
+  int microphone_num = SbMicrophoneGetAvailable(info, kNumberOfMicrophones);
+
+  // Loop all the available microphones and create a valid one.
+  for (int index = 0; index < microphone_num; ++index) {
+    if (!SbMicrophoneIsSampleRateSupported(info[index].id, sample_rate)) {
+      continue;
+    }
+
+    microphone_ =
+        SbMicrophoneCreate(info[index].id, sample_rate, buffer_size_bytes);
+    if (!SbMicrophoneIsValid(microphone_)) {
+      continue;
+    }
+
+    // Created a microphone successfully.
+    min_microphone_read_in_bytes_ = info[index].min_read_size;
+    return;
+  }
+}
+
+MicrophoneStarboard::~MicrophoneStarboard() {
+  SbMicrophoneDestroy(microphone_);
+}
+
+bool MicrophoneStarboard::Open() {
+  SB_DCHECK(SbMicrophoneIsValid(microphone_));
+  return SbMicrophoneOpen(microphone_);
+}
+
+int MicrophoneStarboard::Read(char* out_data, int data_size) {
+  SB_DCHECK(SbMicrophoneIsValid(microphone_));
+  return SbMicrophoneRead(microphone_, out_data, data_size);
+}
+
+bool MicrophoneStarboard::Close() {
+  SB_DCHECK(SbMicrophoneIsValid(microphone_));
+  return SbMicrophoneClose(microphone_);
+}
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(SB_USE_SB_MICROPHONE)
diff --git a/src/cobalt/speech/microphone_starboard.h b/src/cobalt/speech/microphone_starboard.h
new file mode 100644
index 0000000..9df841a
--- /dev/null
+++ b/src/cobalt/speech/microphone_starboard.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_MICROPHONE_STARBOARD_H_
+#define COBALT_SPEECH_MICROPHONE_STARBOARD_H_
+
+#include "cobalt/speech/speech_configuration.h"
+
+#if defined(SB_USE_SB_MICROPHONE)
+
+#include "cobalt/speech/microphone.h"
+#include "starboard/microphone.h"
+
+namespace cobalt {
+namespace speech {
+
+class MicrophoneStarboard : public Microphone {
+ public:
+  MicrophoneStarboard(int sample_rate, int buffer_size_bytes);
+  ~MicrophoneStarboard() OVERRIDE;
+
+  bool Open() OVERRIDE;
+  int Read(char* out_data, int data_size) OVERRIDE;
+  bool Close() OVERRIDE;
+  int MinMicrophoneReadInBytes() OVERRIDE {
+    return min_microphone_read_in_bytes_;
+  }
+  bool IsValid() OVERRIDE { return SbMicrophoneIsValid(microphone_); }
+
+ private:
+  // Minimum requested bytes per microphone read.
+  int min_microphone_read_in_bytes_;
+  SbMicrophone microphone_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(SB_USE_SB_MICROPHONE)
+#endif  // COBALT_SPEECH_MICROPHONE_STARBOARD_H_
diff --git a/src/cobalt/speech/speech.gyp b/src/cobalt/speech/speech.gyp
index eb0ffcb..aa933d3 100644
--- a/src/cobalt/speech/speech.gyp
+++ b/src/cobalt/speech/speech.gyp
@@ -30,6 +30,8 @@
         'audio_encoder_flac.h',
         'chunked_byte_buffer.cc',
         'chunked_byte_buffer.h',
+        'endpointer_delegate.cc',
+        'endpointer_delegate.h',
 
         'endpointer/endpointer.cc',
         'endpointer/endpointer.h',
@@ -41,7 +43,10 @@
         'google_streaming_api.pb.cc',
         'google_streaming_api.pb.h',
         'google_streaming_api.pb.proto',
-        'mic.h',
+        'microphone.h',
+        'microphone_manager.cc',
+        'microphone_manager.h',
+        'speech_configuration.h',
         'speech_recognition.cc',
         'speech_recognition.h',
         'speech_recognition_alternative.cc',
@@ -73,14 +78,38 @@
       'conditions': [
         ['OS=="starboard"', {
           'sources': [
-            'mic_starboard.cc',
+            'microphone_starboard.cc',
+            'microphone_starboard.h',
           ],
         }],
-        ['OS!="starboard" and actual_target_arch in ["linux", "ps3", "win"]', {
+        ['OS=="starboard" and enable_fake_microphone == 1', {
           'sources': [
-            'mic_<(actual_target_arch).cc',
+            'microphone_fake.cc',
+            'microphone_fake.h',
           ],
+          'defines': [
+            'ENABLE_FAKE_MICROPHONE',
+          ],
+          'direct_dependent_settings': {
+            'defines': [ 'ENABLE_FAKE_MICROPHONE', ],
+          },
         }],
+        ['cobalt_copy_test_data == 1', {
+          'actions': [
+            {
+              'action_name': 'speech_copy_test_data',
+              'variables': {
+                'input_files': [
+                  '<(DEPTH)/cobalt/speech/testdata/',
+                ],
+                'output_dir': 'cobalt/speech/testdata/',
+              },
+              'includes': [ '../build/copy_test_data.gypi' ],
+            },
+          ],
+        }
+
+        ],
       ],
     },
     {
diff --git a/src/cobalt/speech/speech_configuration.h b/src/cobalt/speech/speech_configuration.h
new file mode 100644
index 0000000..97ab76f
--- /dev/null
+++ b/src/cobalt/speech/speech_configuration.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_SPEECH_SPEECH_CONFIGURATION_H_
+#define COBALT_SPEECH_SPEECH_CONFIGURATION_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#define SB_USE_SB_MICROPHONE 1
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // defined(OS_STARBOARD)
+
+#endif  // COBALT_SPEECH_SPEECH_CONFIGURATION_H_
diff --git a/src/cobalt/speech/speech_recognition.cc b/src/cobalt/speech/speech_recognition.cc
index 761b2e5..8ea7c49 100644
--- a/src/cobalt/speech/speech_recognition.cc
+++ b/src/cobalt/speech/speech_recognition.cc
@@ -31,7 +31,9 @@
                        ->fetcher_factory()
                        ->network_module(),
                    base::Bind(&SpeechRecognition::OnEventAvailable,
-                              base::Unretained(this)))),
+                              base::Unretained(this)),
+                   base::polymorphic_downcast<dom::DOMSettings*>(settings)
+                       ->enable_fake_microphone())),
       config_("" /*lang*/, false /*continuous*/, false /*interim_results*/,
               1 /*max alternatives*/) {}
 
diff --git a/src/cobalt/speech/speech_recognition_manager.cc b/src/cobalt/speech/speech_recognition_manager.cc
index e531a77..114d374 100644
--- a/src/cobalt/speech/speech_recognition_manager.cc
+++ b/src/cobalt/speech/speech_recognition_manager.cc
@@ -17,6 +17,7 @@
 #include "cobalt/speech/speech_recognition_manager.h"
 
 #include "base/bind.h"
+#include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
 
 namespace cobalt {
@@ -28,7 +29,8 @@
 }  // namespace
 
 SpeechRecognitionManager::SpeechRecognitionManager(
-    network::NetworkModule* network_module, const EventCallback& event_callback)
+    network::NetworkModule* network_module, const EventCallback& event_callback,
+    bool enable_fake_microphone)
     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       weak_this_(weak_ptr_factory_.GetWeakPtr()),
       main_message_loop_(base::MessageLoopProxy::current()),
@@ -37,13 +39,15 @@
           recognizer_(network_module,
                       base::Bind(&SpeechRecognitionManager::OnRecognizerEvent,
                                  base::Unretained(this)))),
-      ALLOW_THIS_IN_INITIALIZER_LIST(mic_(Mic::Create(
+      ALLOW_THIS_IN_INITIALIZER_LIST(microphone_manager_(
           kSampleRate, base::Bind(&SpeechRecognitionManager::OnDataReceived,
                                   base::Unretained(this)),
           base::Bind(&SpeechRecognitionManager::OnDataCompletion,
                      base::Unretained(this)),
           base::Bind(&SpeechRecognitionManager::OnMicError,
-                     base::Unretained(this))))),
+                     base::Unretained(this)),
+          enable_fake_microphone)),
+      endpointer_delegate_(kSampleRate),
       state_(kStopped) {}
 
 SpeechRecognitionManager::~SpeechRecognitionManager() { Stop(); }
@@ -61,7 +65,8 @@
   }
 
   recognizer_.Start(config, kSampleRate);
-  mic_->Start();
+  microphone_manager_.Open();
+  endpointer_delegate_.Start();
   state_ = kStarted;
 }
 
@@ -74,9 +79,11 @@
     return;
   }
 
-  mic_->Stop();
+  endpointer_delegate_.Stop();
+  microphone_manager_.Close();
   recognizer_.Stop();
   state_ = kStopped;
+  event_callback_.Run(new dom::Event(base::Tokens::soundend()));
 }
 
 void SpeechRecognitionManager::Abort() {
@@ -88,9 +95,11 @@
     return;
   }
 
-  mic_->Stop();
+  endpointer_delegate_.Stop();
+  microphone_manager_.Close();
   recognizer_.Stop();
   state_ = kAborted;
+  event_callback_.Run(new dom::Event(base::Tokens::soundend()));
 }
 
 void SpeechRecognitionManager::OnDataReceived(
@@ -105,6 +114,9 @@
 
   // Stop recognizing if in the abort state.
   if (state_ != kAborted) {
+    if (endpointer_delegate_.IsFirstTimeSoundStarted(*audio_bus)) {
+      event_callback_.Run(new dom::Event(base::Tokens::soundstart()));
+    }
     recognizer_.RecognizeAudio(audio_bus.Pass(), false);
   }
 }
@@ -125,7 +137,7 @@
     size_t dummy_frames =
         static_cast<size_t>(kSampleRate * kAudioPacketDurationInSeconds);
     scoped_ptr<ShellAudioBus> dummy_audio_bus(new ShellAudioBus(
-        1, dummy_frames, ShellAudioBus::kInt16, ShellAudioBus::kPlanar));
+        1, dummy_frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
     dummy_audio_bus->ZeroAllFrames();
     recognizer_.RecognizeAudio(dummy_audio_bus.Pass(), true);
   }
@@ -147,22 +159,23 @@
   }
 }
 
-void SpeechRecognitionManager::OnMicError() {
+void SpeechRecognitionManager::OnMicError(
+    const scoped_refptr<dom::Event>& event) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
     // Called from mic thread.
     main_message_loop_->PostTask(
         FROM_HERE,
-        base::Bind(&SpeechRecognitionManager::OnMicError, weak_this_));
+        base::Bind(&SpeechRecognitionManager::OnMicError, weak_this_, event));
     return;
   }
 
-  event_callback_.Run(
-      scoped_refptr<SpeechRecognitionError>(new SpeechRecognitionError(
-          SpeechRecognitionError::kAborted, "Mic Disconnected.")));
+  event_callback_.Run(event);
 
-  // An error is occured in Mic, so stopping the recognizer.
+  // An error is occured in Mic, so stop the energy endpointer and recognizer.
+  endpointer_delegate_.Stop();
   recognizer_.Stop();
   state_ = kAborted;
+  event_callback_.Run(new dom::Event(base::Tokens::soundend()));
 }
 
 }  // namespace speech
diff --git a/src/cobalt/speech/speech_recognition_manager.h b/src/cobalt/speech/speech_recognition_manager.h
index 852ede2..9367a10 100644
--- a/src/cobalt/speech/speech_recognition_manager.h
+++ b/src/cobalt/speech/speech_recognition_manager.h
@@ -21,7 +21,9 @@
 
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/exception_state.h"
-#include "cobalt/speech/mic.h"
+#include "cobalt/speech/endpointer_delegate.h"
+#include "cobalt/speech/microphone_manager.h"
+#include "cobalt/speech/speech_configuration.h"
 #include "cobalt/speech/speech_recognition_config.h"
 #include "cobalt/speech/speech_recognition_error.h"
 #include "cobalt/speech/speech_recognition_event.h"
@@ -42,7 +44,8 @@
   typedef base::Callback<bool(const scoped_refptr<dom::Event>&)> EventCallback;
 
   SpeechRecognitionManager(network::NetworkModule* network_module,
-                           const EventCallback& event_callback);
+                           const EventCallback& event_callback,
+                           bool enable_fake_microphone);
   ~SpeechRecognitionManager();
 
   // Start/Stop speech recognizer and microphone. Multiple calls would be
@@ -62,7 +65,7 @@
   // Callbacks from mic.
   void OnDataReceived(scoped_ptr<ShellAudioBus> audio_bus);
   void OnDataCompletion();
-  void OnMicError();
+  void OnMicError(const scoped_refptr<dom::Event>& event);
 
   // Callbacks from recognizer.
   void OnRecognizerEvent(const scoped_refptr<dom::Event>& event);
@@ -76,7 +79,12 @@
   // Callback for sending dom events if available.
   EventCallback event_callback_;
   SpeechRecognizer recognizer_;
-  scoped_ptr<Mic> mic_;
+
+  MicrophoneManager microphone_manager_;
+
+  // Delegate of endpointer which is used for detecting sound energy.
+  EndPointerDelegate endpointer_delegate_;
+
   State state_;
 };
 
diff --git a/src/cobalt/speech/speech_recognizer.cc b/src/cobalt/speech/speech_recognizer.cc
index d5c1ce1..d7ad5dd 100644
--- a/src/cobalt/speech/speech_recognizer.cc
+++ b/src/cobalt/speech/speech_recognizer.cc
@@ -25,11 +25,16 @@
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/speech/google_streaming_api.pb.h"
-#include "cobalt/speech/mic.h"
+#include "cobalt/speech/microphone.h"
+#include "cobalt/speech/speech_configuration.h"
 #include "cobalt/speech/speech_recognition_error.h"
 #include "net/base/escape.h"
 #include "net/url_request/url_fetcher.h"
 
+#if defined(SB_USE_SB_MICROPHONE)
+#include "starboard/microphone.h"
+#endif  // defined(SB_USE_SB_MICROPHONE)
+
 namespace cobalt {
 namespace speech {
 
@@ -75,16 +80,23 @@
   SpeechRecognitionResultList::SpeechRecognitionResults results;
   for (int i = 0; i < event.result_size(); ++i) {
     SpeechRecognitionResult::SpeechRecognitionAlternatives alternatives;
-    const proto::SpeechRecognitionResult& kProtoResult = event.result(i);
-    for (int j = 0; j < kProtoResult.alternative_size(); ++j) {
-      const proto::SpeechRecognitionAlternative& kAlternative =
-          kProtoResult.alternative(j);
+    const proto::SpeechRecognitionResult& proto_result = event.result(i);
+    for (int j = 0; j < proto_result.alternative_size(); ++j) {
+      const proto::SpeechRecognitionAlternative& proto_alternative =
+          proto_result.alternative(j);
+      float confidence = 0.0f;
+      if (proto_alternative.has_confidence()) {
+        confidence = proto_alternative.confidence();
+      } else if (proto_result.has_stability()) {
+        confidence = proto_result.stability();
+      }
       scoped_refptr<SpeechRecognitionAlternative> alternative(
-          new SpeechRecognitionAlternative(kAlternative.transcript(),
-                                           kAlternative.confidence()));
+          new SpeechRecognitionAlternative(proto_alternative.transcript(),
+                                           confidence));
       alternatives.push_back(alternative);
     }
-    bool final = kProtoResult.has_final() && kProtoResult.final();
+
+    bool final = proto_result.has_final() && proto_result.final();
     scoped_refptr<SpeechRecognitionResult> recognition_result(
         new SpeechRecognitionResult(alternatives, final));
     results.push_back(recognition_result);
@@ -139,6 +151,16 @@
   event_callback.Run(error_event);
 }
 
+bool IsResponseCodeSuccess(int response_code) {
+  // NetFetcher only considers success to be if the network request
+  // was successful *and* we get a 2xx response back.
+  // TODO: 304s are unexpected since we don't enable the HTTP cache,
+  // meaning we don't add the If-Modified-Since header to our request.
+  // However, it's unclear what would happen if we did, so DCHECK.
+  DCHECK_NE(response_code, 304) << "Unsupported status code";
+  return response_code / 100 == 2;
+}
+
 }  // namespace
 
 SpeechRecognizer::SpeechRecognizer(network::NetworkModule* network_module,
@@ -182,23 +204,31 @@
     const net::URLFetcher* source, scoped_ptr<std::string> download_data) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
 
+  const net::URLRequestStatus& status = source->GetStatus();
+  const int response_code = source->GetResponseCode();
+
   if (source == downstream_fetcher_.get()) {
-    chunked_byte_buffer_.Append(*download_data);
-    while (chunked_byte_buffer_.HasChunks()) {
-      scoped_ptr<std::vector<uint8_t> > chunk =
-          chunked_byte_buffer_.PopChunk().Pass();
+    if (status.is_success() && IsResponseCodeSuccess(response_code)) {
+      chunked_byte_buffer_.Append(*download_data);
+      while (chunked_byte_buffer_.HasChunks()) {
+        scoped_ptr<std::vector<uint8_t> > chunk =
+            chunked_byte_buffer_.PopChunk().Pass();
 
-      proto::SpeechRecognitionEvent event;
-      if (!event.ParseFromString(std::string(chunk->begin(), chunk->end()))) {
-        DLOG(WARNING) << "Parse proto string error.";
-        return;
-      }
+        proto::SpeechRecognitionEvent event;
+        if (!event.ParseFromString(std::string(chunk->begin(), chunk->end()))) {
+          DLOG(WARNING) << "Parse proto string error.";
+          return;
+        }
 
-      if (event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS) {
-        ProcessAndFireSuccessEvent(ProcessProtoSuccessResults(event));
-      } else {
-        ProcessAndFireErrorEvent(event, event_callback_);
+        if (event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS) {
+          ProcessAndFireSuccessEvent(ProcessProtoSuccessResults(event));
+        } else {
+          ProcessAndFireErrorEvent(event, event_callback_);
+        }
       }
+    } else {
+      event_callback_.Run(new SpeechRecognitionError(
+          SpeechRecognitionError::kNetwork, "Network response failure."));
     }
   }
 }
@@ -243,7 +273,14 @@
   up_url = AppendQueryParameter(up_url, "client", kClient);
   up_url = AppendQueryParameter(up_url, "pair", pair);
   up_url = AppendQueryParameter(up_url, "output", "pb");
-  up_url = AppendQueryParameter(up_url, "key", GetSpeechAPIKey());
+
+  const char* speech_api_key = NULL;
+#if defined(SB_USE_SB_MICROPHONE)
+  speech_api_key = SbMicrophoneGetSpeechApiKey();
+#else
+  speech_api_key = "";
+#endif
+  up_url = AppendQueryParameter(up_url, "key", speech_api_key);
 
   // Language is required. If no language is specified, use the system language.
   if (!config.lang.empty()) {
diff --git a/src/cobalt/speech/testdata/audio1.raw b/src/cobalt/speech/testdata/audio1.raw
new file mode 100644
index 0000000..5ebf79d
--- /dev/null
+++ b/src/cobalt/speech/testdata/audio1.raw
Binary files differ
diff --git a/src/cobalt/speech/testdata/audio2.raw b/src/cobalt/speech/testdata/audio2.raw
new file mode 100644
index 0000000..35413b7
--- /dev/null
+++ b/src/cobalt/speech/testdata/audio2.raw
Binary files differ
diff --git a/src/cobalt/speech/testdata/audio3.raw b/src/cobalt/speech/testdata/audio3.raw
new file mode 100644
index 0000000..c503c9e
--- /dev/null
+++ b/src/cobalt/speech/testdata/audio3.raw
Binary files differ
diff --git a/src/cobalt/speech/testdata/quit.raw b/src/cobalt/speech/testdata/quit.raw
new file mode 100644
index 0000000..a01dfc4
--- /dev/null
+++ b/src/cobalt/speech/testdata/quit.raw
Binary files differ
diff --git a/src/cobalt/trace_event/json_file_outputter.cc b/src/cobalt/trace_event/json_file_outputter.cc
index f17f2d3..1f7a30b 100644
--- a/src/cobalt/trace_event/json_file_outputter.cc
+++ b/src/cobalt/trace_event/json_file_outputter.cc
@@ -18,12 +18,38 @@
 
 #include <string>
 
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+#include "base/command_line.h"
+#endif
 #include "base/logging.h"
 #include "base/platform_file.h"
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+#include "base/string_piece.h"
+#include "cobalt/trace_event/switches.h"
+#endif
 
 namespace cobalt {
 namespace trace_event {
 
+// Returns true if and only if log_timed_trace == "on", and
+// ENABLE_DEBUG_COMMAND_LINE_SWITCHES is set.
+bool ShouldLogTimedTrace() {
+  bool isTimedTraceSet = false;
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kLogTimedTrace) &&
+      command_line->GetSwitchValueASCII(switches::kLogTimedTrace) ==
+          "on") {
+    isTimedTraceSet = true;
+  }
+
+#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+  return isTimedTraceSet;
+}
+
 JSONFileOutputter::JSONFileOutputter(const FilePath& output_path)
     : output_path_(output_path),
       output_trace_event_call_count_(0),
@@ -72,6 +98,14 @@
     return;
   }
 
+  if (ShouldLogTimedTrace()) {
+    // These markers assist in locating the trace data in the log, and can be
+    // used by scripts to help extract the trace data.
+    LOG(INFO) << "BEGIN_TRACELOG_MARKER" << base::StringPiece(buffer, length)
+              << "END_TRACELOG_MARKER";
+    return;
+  }
+
   int count = base::WritePlatformFileAtCurrentPos(file_, buffer, length);
   if (count < 0) {
     Close();
diff --git a/src/cobalt/trace_event/switches.cc b/src/cobalt/trace_event/switches.cc
new file mode 100644
index 0000000..6cf65e6
--- /dev/null
+++ b/src/cobalt/trace_event/switches.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/trace_event/switches.h"
+
+namespace cobalt {
+namespace trace_event {
+namespace switches {
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+// If this flag is set, then the contents of the timed_trace is sent to the log
+// such that it can be collected by examining console output.  This may be
+// useful on devices where it is difficult to gain access to files written by
+// Cobalt.  log_timed_trace: on | off.
+const char kLogTimedTrace[] = "log_timed_trace";
+#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+}  // namespace switches
+}  // namespace trace_event
+}  // namespace cobalt
diff --git a/src/cobalt/trace_event/switches.h b/src/cobalt/trace_event/switches.h
new file mode 100644
index 0000000..98fc0ec
--- /dev/null
+++ b/src/cobalt/trace_event/switches.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_TRACE_EVENT_SWITCHES_H_
+#define COBALT_TRACE_EVENT_SWITCHES_H_
+
+namespace cobalt {
+namespace trace_event {
+namespace switches {
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+extern const char kLogTimedTrace[];
+#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+}  // namespace switches
+}  // namespace trace_event
+}  // namespace cobalt
+
+#endif  // COBALT_TRACE_EVENT_SWITCHES_H_
diff --git a/src/cobalt/trace_event/trace_event.gyp b/src/cobalt/trace_event/trace_event.gyp
index 75d5c82..4e4279e 100644
--- a/src/cobalt/trace_event/trace_event.gyp
+++ b/src/cobalt/trace_event/trace_event.gyp
@@ -27,6 +27,8 @@
         'scoped_event_parser_trace.h',
         'scoped_trace_to_file.cc',
         'scoped_trace_to_file.h',
+        'switches.cc',
+        'switches.h',
       ],
       'dependencies': [
         '<(DEPTH)/base/base.gyp:base',
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index aa9b2a5..3c57db9 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -17,6 +17,6 @@
 #define COBALT_VERSION_H_
 
 // Cobalt release number.
-#define COBALT_VERSION "4"
+#define COBALT_VERSION "6"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/webdriver/ScriptExecutor.idl b/src/cobalt/webdriver/ScriptExecutor.idl
index d82d27a..1b77e2f 100644
--- a/src/cobalt/webdriver/ScriptExecutor.idl
+++ b/src/cobalt/webdriver/ScriptExecutor.idl
@@ -25,4 +25,4 @@
 };
 
 callback ExecuteFunctionCallback =
-    DOMString(DOMString functionBody, DOMString json_args);
+    void(ScriptExecutorParams params, ScriptExecutorResult resultHandler);
diff --git a/src/cobalt/webdriver/ScriptExecutorParams.idl b/src/cobalt/webdriver/ScriptExecutorParams.idl
new file mode 100644
index 0000000..cda45c5
--- /dev/null
+++ b/src/cobalt/webdriver/ScriptExecutorParams.idl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+[
+  NoInterfaceObject,
+  Conditional=ENABLE_WEBDRIVER
+]
+interface ScriptExecutorParams {
+  readonly attribute object functionObject;
+  readonly attribute DOMString jsonArgs;
+  readonly attribute long? asyncTimeout;
+};
diff --git a/src/cobalt/webdriver/ScriptExecutorResult.idl b/src/cobalt/webdriver/ScriptExecutorResult.idl
new file mode 100644
index 0000000..718059c
--- /dev/null
+++ b/src/cobalt/webdriver/ScriptExecutorResult.idl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+[
+  NoInterfaceObject,
+  Conditional=ENABLE_WEBDRIVER
+]
+interface ScriptExecutorResult {
+  void onResult(DOMString result);
+  void onTimeout();
+};
diff --git a/src/cobalt/webdriver/content/webdriver-init.js b/src/cobalt/webdriver/content/webdriver-init.js
index 2a0b120..03c5099 100644
--- a/src/cobalt/webdriver/content/webdriver-init.js
+++ b/src/cobalt/webdriver/content/webdriver-init.js
@@ -14,71 +14,84 @@
  * limitations under the License.
  */
 
+// Run the JSON deserialize algorithm on the value.
+// https://www.w3.org/TR/webdriver/#dfn-json-deserialize
+webdriverExecutor.deserialize = function(value) {
+  if (value instanceof Array) {
+    // Deserialize each of the array's values;
+    var deserializedArray = [];
+    var numArgs = value.length;
+    for (var i = 0; i < numArgs; i++) {
+      deserializedArray.push(webdriverExecutor.deserialize(value[i]));
+    }
+    return deserializedArray;
+  } else if (value instanceof Object && value.ELEMENT) {
+    // The argument is a WebElement, as denoted by the presence of the
+    // ELEMENT property, so get the actual Element the id is mapped to.
+    return webdriverExecutor.idToElement(value.ELEMENT);
+  } else if (value instanceof Object) {
+    // Deserialize each of the object's values.
+    var deserializedObject = {};
+    for (var key in value) {
+      deserializedObject[key] = webdriverExecutor.deserialize(value[key]);
+    }
+    return deserializedObject;
+  }
+  return value;
+};
+
+// Run the JSON clone algorithm on the value.
+// https://www.w3.org/TR/webdriver/#dfn-internal-json-clone-algorithm
+webdriverExecutor.serialize = function(value, seen) {
+  if (value === undefined || value === null) {
+    return null;
+  } else if (value instanceof Element) {
+    var webElementId = webdriverExecutor.elementToId(value);
+    return { "ELEMENT": webElementId };
+  } else if (value instanceof Object) {
+    if (seen.indexOf(value) != -1) {
+      throw "Reference cycle found trying to serialize.";
+    }
+    seen.push(value);
+    if (value instanceof Array || value instanceof NodeList
+        || value instanceof HTMLCollection) {
+      var serializedArray = [];
+      var length = value.length;
+      for (var i = 0; i < length; i++) {
+        serializedArray.push(webdriverExecutor.serialize(value[i], seen));
+      }
+      return serializedArray;
+    } else {
+      // Deserialize each of the object's its values.
+      var serializedObject = {};
+      for (var key in value) {
+        serializedObject[key] = webdriverExecutor.serialize(value[key], seen);
+      }
+      return serializedObject;
+    }
+  }
+  return value;
+};
+
 // Set the executeScriptHarness callback function.
 // The function takes a functionObject and a JSON string representing an array
 // of arguments. The arguments are applied to the function |functionObject|.
-webdriverExecutor.executeScriptHarness = function(functionObject, jsonArgString) {
-
-  // Run the JSON deserialize algorithm on the value.
-  // https://www.w3.org/TR/webdriver/#dfn-json-deserialize
-  var deserialize = function(value) {
-    if (value instanceof Array) {
-      // Deserialize each of the array's values;
-      var deserializedArray = [];
-      var numArgs = value.length;
-      for (var i = 0; i < numArgs; i++) {
-        deserializedArray.push(deserialize(value[i]));
-      }
-      return deserializedArray;
-    } else if (value instanceof Object && value.ELEMENT) {
-      // The argument is a WebElement, as denoted by the presence of the
-      // ELEMENT property, so get the actual Element the id is mapped to.
-      return webdriverExecutor.idToElement(value.ELEMENT);
-    } else if (value instanceof Object) {
-      // Deserialize each of the object's values.
-      var deserializedObject = {};
-      for (var key in value) {
-        deserializedObject[key] = deserialize(value[key]);
-      }
-      return deserializedObject;
+webdriverExecutor.executeScriptHarness = function(params, resultHandler) {
+  var parameters = webdriverExecutor.deserialize(JSON.parse(params.jsonArgs));
+  if (params.asyncTimeout === null) {
+    var result = params.functionObject.apply(window, parameters);
+    resultHandler.onResult(JSON.stringify(webdriverExecutor.serialize(result, [])));
+  } else {
+    var timeoutCallback = function() {
+      resultHandler.onTimeout();
     }
-    return value;
-  };
+    var timerId = window.setTimeout(timeoutCallback, params.asyncTimeout);
 
-  // Run the JSON clone algorithm on the value.
-  // https://www.w3.org/TR/webdriver/#dfn-internal-json-clone-algorithm
-  var serialize = function(value, seen) {
-    if (value === undefined || value === null) {
-      return null;
-    } else if (value instanceof Element) {
-      var webElementId = webdriverExecutor.elementToId(value);
-      return { "ELEMENT": webElementId };
-    } else if (value instanceof Object) {
-      if (seen.indexOf(value) != -1) {
-        throw "Reference cycle found trying to serialize.";
-      }
-      seen.push(value);
-      if (value instanceof Array || value instanceof NodeList
-          || value instanceof HTMLCollection) {
-        var serializedArray = [];
-        var length = value.length;
-        for (var i = 0; i < length; i++) {
-          serializedArray.push(serialize(value[i], seen));
-        }
-        return serializedArray;
-      } else {
-        // Deserialize each of the object's its values.
-        var serializedObject = {};
-        for (var key in value) {
-          serializedObject[key] = serialize(value[key], seen);
-        }
-        return serializedObject;
-      }
+    var resultCallback = function(result) {
+      resultHandler.onResult(JSON.stringify(webdriverExecutor.serialize(result, [])));
+      window.clearTimeout(timerId);
     }
-    return value;
-  };
-  var parameters = deserialize(JSON.parse(jsonArgString));
-  var result = functionObject.apply(window, parameters);
-  return JSON.stringify(serialize(result, []));
-
+    parameters.push(resultCallback);
+    params.functionObject.apply(window, parameters);
+  }
 };
diff --git a/src/cobalt/webdriver/execute_test.cc b/src/cobalt/webdriver/execute_test.cc
new file mode 100644
index 0000000..6edec33
--- /dev/null
+++ b/src/cobalt/webdriver/execute_test.cc
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/run_loop.h"
+#include "cobalt/dom/document.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/webdriver/script_executor.h"
+#include "cobalt/webdriver/testing/stub_window.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DefaultValue;
+using ::testing::Return;
+
+namespace cobalt {
+namespace webdriver {
+namespace {
+
+class MockElementMapping : public ElementMapping {
+ public:
+  MOCK_METHOD1(ElementToId,
+               protocol::ElementId(const scoped_refptr<dom::Element>&));
+  MOCK_METHOD1(IdToElement,
+               scoped_refptr<dom::Element>(const protocol::ElementId& id));
+};
+
+class MockScriptExecutorResult : public ScriptExecutorResult::ResultHandler {
+ public:
+  MOCK_METHOD1(OnResult, void(const std::string&));
+  MOCK_METHOD0(OnTimeout, void());
+};
+
+class JSONScriptExecutorResult : public ScriptExecutorResult::ResultHandler {
+ public:
+  void OnResult(const std::string& result) {
+    json_result_.reset(base::JSONReader::Read(result.c_str()));
+  }
+  void OnTimeout() { NOTREACHED(); }
+  base::Value* json_result() { return json_result_.get(); }
+
+ private:
+  scoped_ptr<base::Value> json_result_;
+};
+
+class ScriptExecutorTest : public ::testing::Test {
+ protected:
+  void SetUp() OVERRIDE {
+    stub_window_.reset(new testing::StubWindow());
+    script_executor_ =
+        ScriptExecutor::Create(&element_mapping_, global_environment());
+
+    ON_CALL(element_mapping_, IdToElement(_))
+        .WillByDefault(Return(scoped_refptr<dom::Element>()));
+    ON_CALL(element_mapping_, ElementToId(_))
+        .WillByDefault(Return(protocol::ElementId("bad-id")));
+  }
+
+  scoped_refptr<dom::Window> window() { return stub_window_->window(); }
+  scoped_refptr<script::GlobalEnvironment> global_environment() {
+    return stub_window_->global_environment();
+  }
+
+ protected:
+  scoped_ptr<testing::StubWindow> stub_window_;
+  MockElementMapping element_mapping_;
+  scoped_refptr<ScriptExecutor> script_executor_;
+};
+
+}  // namespace
+
+TEST_F(ScriptExecutorTest, CreateSyncScript) {
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment(), "return 5;", "[]");
+  ASSERT_TRUE(params);
+  EXPECT_NE(reinterpret_cast<intptr_t>(params->function_object()), NULL);
+  EXPECT_STREQ(params->json_args().c_str(), "[]");
+  EXPECT_EQ(params->async_timeout(), base::nullopt);
+}
+
+TEST_F(ScriptExecutorTest, CreateAsyncScript) {
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment(), "return 5;", "[]",
+                                   base::TimeDelta::FromMilliseconds(5));
+  ASSERT_TRUE(params);
+  EXPECT_NE(reinterpret_cast<intptr_t>(params->function_object()), NULL);
+  EXPECT_STREQ(params->json_args().c_str(), "[]");
+  EXPECT_EQ(params->async_timeout(), 5);
+}
+
+TEST_F(ScriptExecutorTest, CreateInvalidScript) {
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment(), "retarn 5ish;", "[]");
+  ASSERT_TRUE(params);
+  EXPECT_EQ(reinterpret_cast<intptr_t>(params->function_object()), NULL);
+}
+
+TEST_F(ScriptExecutorTest, ExecuteSync) {
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(), "return \"retval\";", "[]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("\"retval\"")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ExecuteAsync) {
+  // Create a script that will call the async callback after 50 ms, with
+  // an async timeout of 100 ms.
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(),
+      "var callback = arguments[0];"
+      "window.setTimeout(function() { callback(72); }, 50);",
+      "[]", base::TimeDelta::FromMilliseconds(100));
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("72")));
+  EXPECT_CALL(result_handler, OnTimeout()).Times(0);
+
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+
+  // Let the message loop run for 200ms to allow enough time for the async
+  // script to fire the callback.
+  base::RunLoop run_loop;
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(),
+      base::TimeDelta::FromMilliseconds(200));
+  run_loop.Run();
+}
+
+TEST_F(ScriptExecutorTest, AsyncTimeout) {
+  // Create a script that will call the async callback after 10 seconds, with
+  // an async timeout of 100 ms.
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(),
+      "var callback = arguments[0];"
+      "window.setTimeout(function() { callback(72); }, 10000);",
+      "[]", base::TimeDelta::FromMilliseconds(100));
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(_)).Times(0);
+  EXPECT_CALL(result_handler, OnTimeout()).Times(1);
+
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+
+  // Let the message loop run for 200ms to allow enough time for the async
+  // timeout to fire.
+  base::RunLoop run_loop;
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(),
+      base::TimeDelta::FromMilliseconds(200));
+  run_loop.Run();
+}
+
+TEST_F(ScriptExecutorTest, ScriptThrowsException) {
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment(), "throw Error()", "[]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_FALSE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ConvertBoolean) {
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(), "return arguments[0];", "[true]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("true")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+
+  params = ScriptExecutorParams::Create(global_environment(),
+                                        "return arguments[0];", "[false]");
+  ASSERT_TRUE(params);
+  EXPECT_CALL(result_handler, OnResult(std::string("false")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ConvertNull) {
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(), "return arguments[0];", "[null]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("null")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ConvertNumericType) {
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(), "return arguments[0];", "[6]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("6")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+
+  params = ScriptExecutorParams::Create(global_environment(),
+                                        "return arguments[0];", "[-6.4]");
+  ASSERT_TRUE(params);
+  EXPECT_CALL(result_handler, OnResult(std::string("-6.4")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ConvertString) {
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(), "return arguments[0];", "[\"Mr. T\"]");
+  ASSERT_TRUE(params);
+  MockScriptExecutorResult result_handler;
+  EXPECT_CALL(result_handler, OnResult(std::string("\"Mr. T\"")));
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+}
+
+TEST_F(ScriptExecutorTest, ConvertWebElement) {
+  // Create a dom::Element for the MockElementMapping to return.
+  scoped_refptr<dom::Element> element =
+      window()->document()->CreateElement("p");
+  EXPECT_CALL(element_mapping_, IdToElement(protocol::ElementId("id123")))
+      .WillRepeatedly(Return(element));
+  EXPECT_CALL(element_mapping_, ElementToId(element))
+      .WillRepeatedly(Return(protocol::ElementId("id123")));
+
+  // Create a script that will pass a web element argument as a parameter, and
+  // return it back. This will invoke the lookup to and from an id.
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment(), "return arguments[0];",
+                                   "[ {\"ELEMENT\": \"id123\"} ]");
+  ASSERT_TRUE(params);
+
+  // Execute the script and parse the result as JSON, ensuring we got the same
+  // web element.
+  JSONScriptExecutorResult result_handler;
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+  ASSERT_TRUE(result_handler.json_result());
+
+  std::string element_id;
+  base::DictionaryValue* dictionary_value;
+  ASSERT_TRUE(result_handler.json_result()->GetAsDictionary(&dictionary_value));
+  EXPECT_TRUE(dictionary_value->GetString(protocol::ElementId::kElementKey,
+                                          &element_id));
+  EXPECT_STREQ(element_id.c_str(), "id123");
+}
+
+TEST_F(ScriptExecutorTest, ConvertArray) {
+  // Create a script that takes an array of numbers as input, and returns an
+  // array of those numbers incremented by one.
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(),
+      "return [ (arguments[0][0]+1), (arguments[0][1]+1) ];", "[ [5, 6] ]");
+  ASSERT_TRUE(params);
+
+  JSONScriptExecutorResult result_handler;
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+  ASSERT_TRUE(result_handler.json_result());
+
+  base::ListValue* list_value;
+  ASSERT_TRUE(result_handler.json_result()->GetAsList(&list_value));
+  ASSERT_EQ(list_value->GetSize(), 2);
+
+  int value;
+  EXPECT_TRUE(list_value->GetInteger(0, &value));
+  EXPECT_EQ(value, 6);
+  EXPECT_TRUE(list_value->GetInteger(1, &value));
+  EXPECT_EQ(value, 7);
+}
+
+TEST_F(ScriptExecutorTest, ConvertObject) {
+  // Create a script that takes an Object with two properties as input, and
+  // returns an Object with one property that is the sum of the other Object's
+  // properties.
+  scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create(
+      global_environment(),
+      "return {\"sum\": arguments[0].a + arguments[0].b};",
+      "[ {\"a\":5, \"b\":6} ]");
+  ASSERT_TRUE(params);
+
+  JSONScriptExecutorResult result_handler;
+  EXPECT_TRUE(script_executor_->Execute(params, &result_handler));
+  ASSERT_TRUE(result_handler.json_result());
+
+  int value;
+  base::DictionaryValue* dictionary_value;
+  ASSERT_TRUE(result_handler.json_result()->GetAsDictionary(&dictionary_value));
+  EXPECT_TRUE(dictionary_value->GetInteger("sum", &value));
+  EXPECT_EQ(value, 11);
+}
+
+}  // namespace webdriver
+}  // namespace cobalt
diff --git a/src/cobalt/webdriver/protocol/element_id.cc b/src/cobalt/webdriver/protocol/element_id.cc
index 8891cc7..9d03cf9 100644
--- a/src/cobalt/webdriver/protocol/element_id.cc
+++ b/src/cobalt/webdriver/protocol/element_id.cc
@@ -19,9 +19,8 @@
 namespace cobalt {
 namespace webdriver {
 namespace protocol {
-namespace {
-const char kElementKey[] = "ELEMENT";
-}
+
+const char ElementId::kElementKey[] = "ELEMENT";
 
 scoped_ptr<base::Value> ElementId::ToValue(const ElementId& element_id) {
   scoped_ptr<base::DictionaryValue> element_object(new base::DictionaryValue());
diff --git a/src/cobalt/webdriver/protocol/element_id.h b/src/cobalt/webdriver/protocol/element_id.h
index c4e0f58..c19dfb5 100644
--- a/src/cobalt/webdriver/protocol/element_id.h
+++ b/src/cobalt/webdriver/protocol/element_id.h
@@ -30,6 +30,8 @@
 // Opaque type that uniquely identifies an Element from a WebDriver session.
 class ElementId {
  public:
+  static const char kElementKey[];
+
   // Convert the ElementId to a WebElement JSON object:
   // https://code.google.com/p/selenium/wiki/JsonWireProtocol#WebElement_JSON_Object
   static scoped_ptr<base::Value> ToValue(const ElementId& element_id);
diff --git a/src/cobalt/webdriver/protocol/response.h b/src/cobalt/webdriver/protocol/response.h
index 28ffe9a..8cfc549 100644
--- a/src/cobalt/webdriver/protocol/response.h
+++ b/src/cobalt/webdriver/protocol/response.h
@@ -62,6 +62,9 @@
     // An error occurred while executing user supplied JavaScript.
     kJavaScriptError = 17,
 
+    // An operation did not complete before its timeout expired.
+    kTimeOut = 21,
+
     // The specified window has been closed, or otherwise couldn't be found.
     kNoSuchWindow = 23,
 
diff --git a/src/cobalt/webdriver/script_executor.cc b/src/cobalt/webdriver/script_executor.cc
index ab7a9c8..cd3d63f 100644
--- a/src/cobalt/webdriver/script_executor.cc
+++ b/src/cobalt/webdriver/script_executor.cc
@@ -16,40 +16,107 @@
 
 #include "cobalt/webdriver/script_executor.h"
 
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/path_service.h"
+#include "cobalt/script/source_code.h"
+
 namespace cobalt {
 namespace webdriver {
+namespace {
+// Path to the script to initialize the script execution harness.
+const char kWebDriverInitScriptPath[] = "webdriver/webdriver-init.js";
+
+// Wrapper around a scoped_refptr<script::SourceCode> instance. The script
+// at kWebDriverInitScriptPath will be loaded from disk and a new
+// script::SourceCode will be created.
+class LazySourceLoader {
+ public:
+  LazySourceLoader() {
+    FilePath exe_path;
+    if (!PathService::Get(base::DIR_EXE, &exe_path)) {
+      NOTREACHED() << "Failed to get EXE path.";
+      return;
+    }
+    FilePath script_path = exe_path.Append(kWebDriverInitScriptPath);
+    std::string script_contents;
+    if (!file_util::ReadFileToString(script_path, &script_contents)) {
+      NOTREACHED() << "Failed to read script contents.";
+      return;
+    }
+    source_code_ = script::SourceCode::CreateSourceCode(
+        script_contents.c_str(),
+        base::SourceLocation(kWebDriverInitScriptPath, 1, 1));
+  }
+  const scoped_refptr<script::SourceCode>& source_code() {
+    return source_code_;
+  }
+
+ private:
+  scoped_refptr<script::SourceCode> source_code_;
+};
+
+// The script only needs to be loaded once, so allow it to persist as a
+// LazyInstance and be shared amongst different WindowDriver instances.
+base::LazyInstance<LazySourceLoader> lazy_source_loader =
+    LAZY_INSTANCE_INITIALIZER;
+}  // namespace
+
+void ScriptExecutor::LoadExecutorSourceCode() { lazy_source_loader.Get(); }
+
+scoped_refptr<ScriptExecutor> ScriptExecutor::Create(
+    ElementMapping* element_mapping,
+    const scoped_refptr<script::GlobalEnvironment>& global_environment) {
+  // This could be NULL if there was an error loading the harness source from
+  // disk.
+  scoped_refptr<script::SourceCode> source =
+      lazy_source_loader.Get().source_code();
+  if (!source) {
+    return NULL;
+  }
+
+  // Create a new ScriptExecutor and bind it to the global object.
+  scoped_refptr<ScriptExecutor> script_executor =
+      new ScriptExecutor(element_mapping);
+  global_environment->Bind("webdriverExecutor", script_executor);
+
+  // Evaluate the harness initialization script.
+  std::string result;
+  if (!global_environment->EvaluateScript(source, &result)) {
+    return NULL;
+  }
+
+  // The initialization script should have set this.
+  DCHECK(script_executor->execute_script_harness());
+  return script_executor;
+}
+
+bool ScriptExecutor::Execute(
+    const scoped_refptr<ScriptExecutorParams>& params,
+    ScriptExecutorResult::ResultHandler* result_handler) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  scoped_refptr<ScriptExecutorResult> executor_result(
+      new ScriptExecutorResult(result_handler));
+  return ExecuteInternal(params, executor_result);
+}
 
 void ScriptExecutor::set_execute_script_harness(
     const ExecuteFunctionCallbackHolder& callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  callback_.emplace(this, callback);
+  execute_callback_.emplace(this, callback);
 }
 
 const ScriptExecutor::ExecuteFunctionCallbackHolder*
 ScriptExecutor::execute_script_harness() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (callback_) {
-    return &(callback_->referenced_object());
+  if (execute_callback_) {
+    return &(execute_callback_->referenced_object());
   } else {
     return NULL;
   }
 }
 
-base::optional<std::string> ScriptExecutor::Execute(
-    const script::OpaqueHandleHolder* function_object,
-    const std::string& json_arguments) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(callback_);
-
-  ExecuteFunctionCallback::ReturnValue callback_result =
-      callback_->value().Run(function_object, json_arguments);
-  if (callback_result.exception) {
-    return base::nullopt;
-  } else {
-    return callback_result.result;
-  }
-}
-
 scoped_refptr<dom::Element> ScriptExecutor::IdToElement(const std::string& id) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(element_mapping_);
@@ -63,5 +130,16 @@
   return element_mapping_->ElementToId(element).id();
 }
 
+bool ScriptExecutor::ExecuteInternal(
+    const scoped_refptr<ScriptExecutorParams>& params,
+    const scoped_refptr<ScriptExecutorResult>& result_handler) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(params);
+  DCHECK(result_handler);
+  ExecuteFunctionCallback::ReturnValue callback_result =
+      execute_callback_->value().Run(params, result_handler);
+  return !callback_result.exception;
+}
+
 }  // namespace webdriver
 }  // namespace cobalt
diff --git a/src/cobalt/webdriver/script_executor.h b/src/cobalt/webdriver/script_executor.h
index 494cd5e..a46f1f2 100644
--- a/src/cobalt/webdriver/script_executor.h
+++ b/src/cobalt/webdriver/script_executor.h
@@ -31,6 +31,8 @@
 #include "cobalt/script/wrappable.h"
 #include "cobalt/webdriver/element_mapping.h"
 #include "cobalt/webdriver/protocol/element_id.h"
+#include "cobalt/webdriver/script_executor_params.h"
+#include "cobalt/webdriver/script_executor_result.h"
 
 namespace cobalt {
 namespace webdriver {
@@ -40,14 +42,32 @@
     public base::SupportsWeakPtr<ScriptExecutor>,
     public script::Wrappable {
  public:
-  typedef script::CallbackFunction<std::string(
-      const script::OpaqueHandleHolder*, const std::string&)>
-      ExecuteFunctionCallback;
+  typedef script::CallbackFunction<void(
+      const scoped_refptr<ScriptExecutorParams>&,
+      const scoped_refptr<ScriptExecutorResult>&)> ExecuteFunctionCallback;
   typedef script::ScriptObject<ExecuteFunctionCallback>
       ExecuteFunctionCallbackHolder;
 
-  explicit ScriptExecutor(ElementMapping* mapping)
-      : element_mapping_(mapping) {}
+  // This can be called on any thread to preload the webdriver javascript code.
+  // If this is not called, then it will be lazily loaded the first time a
+  // ScriptExecutor is created, which will be the Web Module thread in most
+  // cases.
+  static void LoadExecutorSourceCode();
+
+  // Create a new ScriptExecutor instance.
+  static scoped_refptr<ScriptExecutor> Create(
+      ElementMapping* element_mapping,
+      const scoped_refptr<script::GlobalEnvironment>& global_environment);
+
+  // Calls the function set to the executeScriptHarness property with the
+  // provided |param|. The result of script execution will be passed to the
+  // caller through |result_handler|.
+  // Returns false on execution failure.
+  bool Execute(const scoped_refptr<ScriptExecutorParams>& params,
+               ScriptExecutorResult::ResultHandler* result_handler);
+
+  // ScriptExecutor bindings implementation.
+  //
 
   void set_execute_script_harness(
       const ExecuteFunctionCallbackHolder& callback);
@@ -56,19 +76,19 @@
   scoped_refptr<dom::Element> IdToElement(const std::string& id);
   std::string ElementToId(const scoped_refptr<dom::Element>& id);
 
-  // Calls the function set to the executeScriptHarness property with the
-  // provided |function_body| and |json_arguments|. Returns a JSON
-  // serialized result string, of base::nullopt on execution failure.
-  base::optional<std::string> Execute(
-      const script::OpaqueHandleHolder* function_object,
-      const std::string& json_arguments);
-
   DEFINE_WRAPPABLE_TYPE(ScriptExecutor);
 
  private:
+  explicit ScriptExecutor(ElementMapping* mapping)
+      : element_mapping_(mapping) {}
+
+  bool ExecuteInternal(
+      const scoped_refptr<ScriptExecutorParams>& params,
+      const scoped_refptr<ScriptExecutorResult>& result_handler);
+
   base::ThreadChecker thread_checker_;
   ElementMapping* element_mapping_;
-  base::optional<ExecuteFunctionCallbackHolder::Reference> callback_;
+  base::optional<ExecuteFunctionCallbackHolder::Reference> execute_callback_;
 };
 
 }  // namespace webdriver
diff --git a/src/cobalt/webdriver/script_executor_params.cc b/src/cobalt/webdriver/script_executor_params.cc
new file mode 100644
index 0000000..d96a325
--- /dev/null
+++ b/src/cobalt/webdriver/script_executor_params.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cobalt/webdriver/script_executor_params.h"
+
+#include "base/stringprintf.h"
+#include "cobalt/script/source_code.h"
+
+namespace cobalt {
+namespace webdriver {
+
+scoped_refptr<ScriptExecutorParams> ScriptExecutorParams::Create(
+    const scoped_refptr<script::GlobalEnvironment>& global_environment,
+    const std::string& function_body, const std::string& json_args,
+    base::optional<base::TimeDelta> async_timeout) {
+  scoped_refptr<ScriptExecutorParams> params(new ScriptExecutorParams());
+  params->json_args_ = json_args;
+
+  if (async_timeout) {
+    int async_timeout_ms = async_timeout->InMilliseconds();
+    params->async_timeout_ = async_timeout_ms;
+  }
+
+  std::string function =
+      StringPrintf("(function() {\n%s\n})", function_body.c_str());
+  scoped_refptr<script::SourceCode> function_source =
+      script::SourceCode::CreateSourceCode(
+          function.c_str(), base::SourceLocation("[webdriver]", 1, 1));
+
+  if (!global_environment->EvaluateScript(function_source, params.get(),
+                                          &params->function_object_)) {
+    DLOG(ERROR) << "Failed to create Function object";
+  }
+  return params;
+}
+}  // namespace webdriver
+}  // namespace cobalt
diff --git a/src/cobalt/webdriver/script_executor_params.h b/src/cobalt/webdriver/script_executor_params.h
new file mode 100644
index 0000000..2256beb
--- /dev/null
+++ b/src/cobalt/webdriver/script_executor_params.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_
+#define COBALT_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_
+
+#if defined(ENABLE_WEBDRIVER)
+
+#include <string>
+
+#include "base/optional.h"
+#include "base/threading/thread_checker.h"
+#include "base/time.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/opaque_handle.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace webdriver {
+
+// An instance of the ScriptExecutorResult is passed to the webdriver script
+// execution harness to collect the results of running a script via webdriver.
+// The results are forwarded to a ResultHandler instance.
+class ScriptExecutorParams : public script::Wrappable {
+ public:
+  static scoped_refptr<ScriptExecutorParams> Create(
+      const scoped_refptr<script::GlobalEnvironment>& global_environment,
+      const std::string& function_body, const std::string& json_args) {
+    return Create(global_environment, function_body, json_args, base::nullopt);
+  }
+
+  static scoped_refptr<ScriptExecutorParams> Create(
+      const scoped_refptr<script::GlobalEnvironment>& global_environment,
+      const std::string& function_body, const std::string& json_args,
+      base::optional<base::TimeDelta> async_timeout);
+
+  const script::OpaqueHandleHolder* function_object() {
+    return function_object_ ? &function_object_->referenced_object() : NULL;
+  }
+  const std::string& json_args() { return json_args_; }
+  base::optional<int32_t> async_timeout() { return async_timeout_; }
+
+  DEFINE_WRAPPABLE_TYPE(ScriptExecutorParams);
+
+ private:
+  std::string function_body_;
+  base::optional<script::OpaqueHandleHolder::Reference> function_object_;
+  std::string json_args_;
+  base::optional<int32_t> async_timeout_;
+};
+
+}  // namespace webdriver
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_WEBDRIVER)
+
+#endif  // COBALT_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_
diff --git a/src/cobalt/webdriver/script_executor_result.h b/src/cobalt/webdriver/script_executor_result.h
new file mode 100644
index 0000000..aa68d80
--- /dev/null
+++ b/src/cobalt/webdriver/script_executor_result.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_
+#define COBALT_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_
+
+#if defined(ENABLE_WEBDRIVER)
+
+#include <string>
+
+#include "base/threading/thread_checker.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace webdriver {
+
+// An instance of the ScriptExecutorResult is passed to the webdriver script
+// execution harness to collect the results of running a script via webdriver.
+// The results are forwarded to a ResultHandler instance.
+class ScriptExecutorResult : public script::Wrappable {
+ public:
+  // The ScriptExecutorResult class must only be accessed from the main thread,
+  // so use an instance of the ResultHandler class to access the results of
+  // execution from another thread.
+  // Exactly one of OnResult and OnTimeout will be called. After one of these
+  // is called, the ScriptExectutorResult object will no longer hold a pointer
+  // to the ResultHandler instance, so it's safe for the owning thread to
+  // destroy it.
+  class ResultHandler {
+   public:
+    virtual void OnResult(const std::string& result) = 0;
+    virtual void OnTimeout() = 0;
+  };
+
+  explicit ScriptExecutorResult(ScriptExecutorResult::ResultHandler* handler)
+      : result_handler_(handler) {}
+
+  void OnResult(const std::string& result) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    if (result_handler_) {
+      result_handler_->OnResult(result);
+      result_handler_ = NULL;
+    }
+  }
+
+  void OnTimeout() {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    if (result_handler_) {
+      result_handler_->OnTimeout();
+      result_handler_ = NULL;
+    }
+  }
+
+  DEFINE_WRAPPABLE_TYPE(ScriptExecutorResult);
+
+ private:
+  base::ThreadChecker thread_checker_;
+  ResultHandler* result_handler_;
+};
+
+}  // namespace webdriver
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_WEBDRIVER)
+
+#endif  // COBALT_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_
diff --git a/src/cobalt/webdriver/server.cc b/src/cobalt/webdriver/server.cc
index 6573cd7..94ad4ca 100644
--- a/src/cobalt/webdriver/server.cc
+++ b/src/cobalt/webdriver/server.cc
@@ -179,7 +179,7 @@
 WebDriverServer::WebDriverServer(int port,
                                  const HandleRequestCallback& callback)
     : handle_request_callback_(callback) {
-  DLOG(INFO) << "Starting WebDriver server on port " << port;
+  LOG(INFO) << "Starting WebDriver server on port " << port;
   // Create http server
   factory_.reset(new net::TCPListenSocketFactory("0.0.0.0", port));
   server_ = new net::HttpServer(*factory_, this);
diff --git a/src/cobalt/webdriver/testing/stub_window.h b/src/cobalt/webdriver/testing/stub_window.h
new file mode 100644
index 0000000..aa6c912
--- /dev/null
+++ b/src/cobalt/webdriver/testing/stub_window.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COBALT_WEBDRIVER_TESTING_STUB_WINDOW_H_
+#define COBALT_WEBDRIVER_TESTING_STUB_WINDOW_H_
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/message_loop.h"
+#include "cobalt/css_parser/parser.h"
+#include "cobalt/dom/local_storage_database.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/dom_parser/parser.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/media/media_module_stub.h"
+#include "cobalt/network/network_module.h"
+#include "googleurl/src/gurl.h"
+
+namespace cobalt {
+namespace webdriver {
+namespace testing {
+
+// A helper class for WebDriver tests that brings up a dom::Window with a number
+// of parts stubbed out.
+class StubWindow {
+ public:
+  StubWindow()
+      : message_loop_(MessageLoop::TYPE_DEFAULT),
+        css_parser_(css_parser::Parser::Create()),
+        dom_parser_(new dom_parser::Parser(base::Bind(&StubErrorCallback))),
+        fetcher_factory_(new loader::FetcherFactory(&network_module_)),
+        local_storage_database_(NULL),
+        stub_media_module_(new media::MediaModuleStub()),
+        url_("about:blank"),
+        dom_stat_tracker_(new dom::DomStatTracker("StubWindow")) {
+    engine_ = script::JavaScriptEngine::CreateEngine();
+    global_environment_ = engine_->CreateGlobalEnvironment();
+    window_ = new dom::Window(
+        1920, 1080, css_parser_.get(), dom_parser_.get(),
+        fetcher_factory_.get(), NULL, NULL, NULL, NULL,
+        &local_storage_database_, stub_media_module_.get(),
+        stub_media_module_.get(), NULL, NULL, NULL, dom_stat_tracker_.get(),
+        url_, "", "en-US", base::Callback<void(const GURL&)>(),
+        base::Bind(&StubErrorCallback), NULL, network_bridge::PostSender(),
+        std::string() /* default security policy */, dom::kCspEnforcementEnable,
+        base::Closure() /* csp_policy_changed */,
+        base::Closure() /* window_close */);
+    global_environment_->CreateGlobalObject(window_, &environment_settings_);
+  }
+
+  scoped_refptr<dom::Window> window() { return window_; }
+  scoped_refptr<script::GlobalEnvironment> global_environment() {
+    return global_environment_;
+  }
+
+ private:
+  static void StubErrorCallback(const std::string& /*error*/) {}
+
+  MessageLoop message_loop_;
+  scoped_ptr<css_parser::Parser> css_parser_;
+  scoped_ptr<dom_parser::Parser> dom_parser_;
+  network::NetworkModule network_module_;
+  scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  dom::LocalStorageDatabase local_storage_database_;
+  scoped_ptr<media::MediaModule> stub_media_module_;
+  GURL url_;
+  scoped_ptr<dom::DomStatTracker> dom_stat_tracker_;
+  script::EnvironmentSettings environment_settings_;
+  scoped_ptr<script::JavaScriptEngine> engine_;
+  scoped_refptr<script::GlobalEnvironment> global_environment_;
+  scoped_refptr<dom::Window> window_;
+};
+
+}  // namespace testing
+}  // namespace webdriver
+}  // namespace cobalt
+
+#endif  // COBALT_WEBDRIVER_TESTING_STUB_WINDOW_H_
diff --git a/src/cobalt/webdriver/web_driver_module.cc b/src/cobalt/webdriver/web_driver_module.cc
index 46a77e8..3d510cb 100644
--- a/src/cobalt/webdriver/web_driver_module.cc
+++ b/src/cobalt/webdriver/web_driver_module.cc
@@ -294,6 +294,11 @@
           base::Bind(&WindowDriver::Execute)));
   webdriver_dispatcher_->RegisterCommand(
       WebDriverServer::kPost,
+      StringPrintf("/session/%s/execute_async", kSessionIdVariable),
+      current_window_command_factory->GetCommandHandler(
+          base::Bind(&WindowDriver::ExecuteAsync)));
+  webdriver_dispatcher_->RegisterCommand(
+      WebDriverServer::kPost,
       StringPrintf("/session/%s/element", kSessionIdVariable),
       current_window_command_factory->GetCommandHandler(
           base::Bind(&WindowDriver::FindElement)));
diff --git a/src/cobalt/webdriver/webdriver.gyp b/src/cobalt/webdriver/webdriver.gyp
index 558a0ee..1463a01 100644
--- a/src/cobalt/webdriver/webdriver.gyp
+++ b/src/cobalt/webdriver/webdriver.gyp
@@ -61,6 +61,9 @@
             'protocol/window_id.h',
             'script_executor.cc',
             'script_executor.h',
+            'script_executor_params.cc',
+            'script_executor_params.h',
+            'script_executor_result.h',
             'search.cc',
             'search.h',
             'server.cc',
@@ -86,53 +89,7 @@
       ],
     },
 
-    {
-      'target_name': 'webdriver_test',
-      'type': '<(gtest_target_type)',
-      'conditions': [
-        ['enable_webdriver==1', {
-          'sources': [
-            'get_element_text_test.cc',
-            'is_displayed_test.cc',
-            'keyboard_test.cc',
-          ],
-        }],
-      ],
-      'dependencies': [
-        '<(DEPTH)/base/base.gyp:run_all_unittests',
-        '<(DEPTH)/cobalt/base/base.gyp:base',
-        '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
-        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
-        '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
-        '<(DEPTH)/testing/gmock.gyp:gmock',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
-        'webdriver',
-      ],
-      'actions': [
-        {
-          'action_name': 'webdriver_test_copy_test_data',
-          'variables': {
-            'input_files': [
-              '<(DEPTH)/cobalt/webdriver/testdata/',
-            ],
-            'output_dir': 'cobalt/webdriver_test',
-          },
-          'includes': ['../build/copy_test_data.gypi'],
-        }
-      ],
-    },
 
-    {
-      'target_name': 'webdriver_test_deploy',
-      'type': 'none',
-      'dependencies': [
-        'webdriver_test',
-      ],
-      'variables': {
-        'executable_name': 'webdriver_test',
-      },
-      'includes': [ '../../starboard/build/deploy.gypi' ],
-    },
 
     {
       'target_name': 'copy_webdriver_data',
diff --git a/src/cobalt/webdriver/webdriver_test.gyp b/src/cobalt/webdriver/webdriver_test.gyp
new file mode 100644
index 0000000..d01bef5
--- /dev/null
+++ b/src/cobalt/webdriver/webdriver_test.gyp
@@ -0,0 +1,48 @@
+{
+  'targets': [
+    {
+      'target_name': 'webdriver_test',
+      'type': '<(gtest_target_type)',
+      'conditions': [
+        ['enable_webdriver==1', {
+          'sources': [
+            'get_element_text_test.cc',
+            'is_displayed_test.cc',
+            'keyboard_test.cc',
+            'execute_test.cc',
+          ],
+        }],
+      ],
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:run_all_unittests',
+        '<(DEPTH)/cobalt/browser/browser.gyp:browser',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+      'actions': [
+        {
+          'action_name': 'webdriver_test_copy_test_data',
+          'variables': {
+            'input_files': [
+              '<(DEPTH)/cobalt/webdriver/testdata/',
+            ],
+            'output_dir': 'cobalt/webdriver_test',
+          },
+          'includes': ['../build/copy_test_data.gypi'],
+        }
+      ],
+    },
+
+    {
+      'target_name': 'webdriver_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'webdriver_test',
+      ],
+      'variables': {
+        'executable_name': 'webdriver_test',
+      },
+      'includes': [ '../../starboard/build/deploy.gypi' ],
+    },
+  ]
+}
diff --git a/src/cobalt/webdriver/window_driver.cc b/src/cobalt/webdriver/window_driver.cc
index a21d3b6..57b0467 100644
--- a/src/cobalt/webdriver/window_driver.cc
+++ b/src/cobalt/webdriver/window_driver.cc
@@ -18,16 +18,10 @@
 
 #include <utility>
 
-#include "base/file_path.h"
-#include "base/file_util.h"
-#include "base/lazy_instance.h"
-#include "base/path_service.h"
-#include "base/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/location.h"
 #include "cobalt/script/global_environment.h"
-#include "cobalt/script/source_code.h"
 #include "cobalt/webdriver/keyboard.h"
 #include "cobalt/webdriver/search.h"
 #include "cobalt/webdriver/util/call_on_message_loop.h"
@@ -35,87 +29,45 @@
 namespace cobalt {
 namespace webdriver {
 namespace {
-// Path to the script to initialize the script execution harness.
-const char kWebDriverInitScriptPath[] = "webdriver/webdriver-init.js";
 
-// Wrapper around a scoped_refptr<script::SourceCode> instance. The script
-// at kWebDriverInitScriptPath will be loaded from disk and a new
-// script::SourceCode will be created.
-class LazySourceLoader {
+class SyncExecuteResultHandler : public ScriptExecutorResult::ResultHandler {
  public:
-  LazySourceLoader() {
-    FilePath exe_path;
-    if (!PathService::Get(base::DIR_EXE, &exe_path)) {
-      NOTREACHED() << "Failed to get EXE path.";
-      return;
-    }
-    FilePath script_path = exe_path.Append(kWebDriverInitScriptPath);
-    std::string script_contents;
-    if (!file_util::ReadFileToString(script_path, &script_contents)) {
-      NOTREACHED() << "Failed to read script contents.";
-      return;
-    }
-    source_code_ = script::SourceCode::CreateSourceCode(
-        script_contents.c_str(),
-        base::SourceLocation(kWebDriverInitScriptPath, 1, 1));
+  void OnResult(const std::string& result) OVERRIDE {
+    DCHECK(!result_);
+    result_ = result;
   }
-  const scoped_refptr<script::SourceCode>& source_code() {
-    return source_code_;
+  void OnTimeout() OVERRIDE { NOTREACHED(); }
+  std::string result() const {
+    DCHECK(result_);
+    return result_.value_or(std::string());
   }
 
  private:
-  scoped_refptr<script::SourceCode> source_code_;
+  base::optional<std::string> result_;
 };
 
-// The script only needs to be loaded once, so allow it to persist as a
-// LazyInstance and be shared amongst different WindowDriver instances.
-base::LazyInstance<LazySourceLoader> lazy_source_loader =
-    LAZY_INSTANCE_INITIALIZER;
+class AsyncExecuteResultHandler : public ScriptExecutorResult::ResultHandler {
+ public:
+  AsyncExecuteResultHandler() : timed_out_(false), event_(true, false) {}
 
-scoped_refptr<ScriptExecutor> CreateScriptExecutor(
-    ElementMapping* element_mapping,
-    const scoped_refptr<script::GlobalEnvironment>& global_environment) {
-  // This could be NULL if there was an error loading the harness source from
-  // disk.
-  scoped_refptr<script::SourceCode> source =
-      lazy_source_loader.Get().source_code();
-  if (!source) {
-    return NULL;
+  void WaitForResult() { event_.Wait(); }
+  bool timed_out() const { return timed_out_; }
+  const std::string& result() const { return result_; }
+
+ private:
+  void OnResult(const std::string& result) OVERRIDE {
+    result_ = result;
+    event_.Signal();
+  }
+  void OnTimeout() OVERRIDE {
+    timed_out_ = true;
+    event_.Signal();
   }
 
-  // Create a new ScriptExecutor and bind it to the global object.
-  scoped_refptr<ScriptExecutor> script_executor =
-      new ScriptExecutor(element_mapping);
-  global_environment->Bind("webdriverExecutor", script_executor);
-
-  // Evaluate the harness initialization script.
-  std::string result;
-  if (!global_environment->EvaluateScript(source, &result)) {
-    return NULL;
-  }
-
-  // The initialization script should have set this.
-  DCHECK(script_executor->execute_script_harness());
-  return script_executor;
-}
-
-void CreateFunction(
-    const std::string& function_body,
-    const scoped_refptr<script::GlobalEnvironment>& global_environment,
-    scoped_refptr<ScriptExecutor> script_executor,
-    base::optional<script::OpaqueHandleHolder::Reference>* out_opaque_handle) {
-  std::string function =
-      StringPrintf("(function() {\n%s\n})", function_body.c_str());
-  scoped_refptr<script::SourceCode> function_source =
-      script::SourceCode::CreateSourceCode(
-          function.c_str(), base::SourceLocation("[webdriver]", 1, 1));
-
-  if (!global_environment->EvaluateScript(
-          function_source, make_scoped_refptr(script_executor.get()),
-          out_opaque_handle)) {
-    DLOG(ERROR) << "Failed to create Function object";
-  }
-}
+  std::string result_;
+  bool timed_out_;
+  base::WaitableEvent event_;
+};
 
 std::string GetCurrentUrl(dom::Window* window) {
   DCHECK(window);
@@ -258,12 +210,51 @@
 
 util::CommandResult<protocol::ScriptResult> WindowDriver::Execute(
     const protocol::Script& script) {
+  typedef util::CommandResult<protocol::ScriptResult> CommandResult;
   DCHECK(thread_checker_.CalledOnValidThread());
-  // Poke the lazy loader so we don't hit the disk on window_message_loop_.
-  lazy_source_loader.Get();
-  return util::CallOnMessageLoop(
-      window_message_loop_, base::Bind(&WindowDriver::ExecuteScriptInternal,
-                                       base::Unretained(this), script));
+  // Pre-load the ScriptExecutor source so we don't hit the disk on
+  // window_message_loop_.
+  ScriptExecutor::LoadExecutorSourceCode();
+
+  SyncExecuteResultHandler result_handler;
+
+  CommandResult result = util::CallOnMessageLoop(
+      window_message_loop_,
+      base::Bind(&WindowDriver::ExecuteScriptInternal, base::Unretained(this),
+                 script, base::nullopt, &result_handler));
+  if (result.is_success()) {
+    return CommandResult(protocol::ScriptResult(result_handler.result()));
+  } else {
+    return result;
+  }
+}
+
+util::CommandResult<protocol::ScriptResult> WindowDriver::ExecuteAsync(
+    const protocol::Script& script) {
+  typedef util::CommandResult<protocol::ScriptResult> CommandResult;
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Pre-load the ScriptExecutor source so we don't hit the disk on
+  // window_message_loop_.
+  ScriptExecutor::LoadExecutorSourceCode();
+
+  const base::TimeDelta kDefaultAsyncTimeout =
+      base::TimeDelta::FromMilliseconds(0);
+  AsyncExecuteResultHandler result_handler;
+  CommandResult result = util::CallOnMessageLoop(
+      window_message_loop_,
+      base::Bind(&WindowDriver::ExecuteScriptInternal, base::Unretained(this),
+                 script, kDefaultAsyncTimeout, &result_handler));
+
+  if (!result.is_success()) {
+    return result;
+  }
+
+  result_handler.WaitForResult();
+  if (result_handler.timed_out()) {
+    return CommandResult(protocol::Response::kTimeOut);
+  } else {
+    return CommandResult(protocol::ScriptResult(result_handler.result()));
+  }
 }
 
 util::CommandResult<void> WindowDriver::SendKeys(const protocol::Keys& keys) {
@@ -376,7 +367,9 @@
 }
 
 util::CommandResult<protocol::ScriptResult> WindowDriver::ExecuteScriptInternal(
-    const protocol::Script& script) {
+    const protocol::Script& script,
+    base::optional<base::TimeDelta> async_timeout,
+    ScriptExecutorResult::ResultHandler* async_handler) {
   typedef util::CommandResult<protocol::ScriptResult> CommandResult;
   DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_);
   if (!window_) {
@@ -393,7 +386,7 @@
   // global object, thus with the WindowDriver.
   if (!script_executor_) {
     scoped_refptr<ScriptExecutor> script_executor =
-        CreateScriptExecutor(this, global_environment);
+        ScriptExecutor::Create(this, global_environment);
     if (!script_executor) {
       DLOG(INFO) << "Failed to create ScriptExecutor.";
       return CommandResult(protocol::Response::kUnknownError);
@@ -404,20 +397,16 @@
   DLOG(INFO) << "Executing: " << script.function_body();
   DLOG(INFO) << "Arguments: " << script.argument_array();
 
-  base::optional<script::OpaqueHandleHolder::Reference> function_object;
-  CreateFunction(script.function_body(), global_environment,
-                 make_scoped_refptr(script_executor_.get()), &function_object);
-  if (!function_object) {
-    return CommandResult(protocol::Response::kJavaScriptError);
-  }
+  scoped_refptr<ScriptExecutorParams> params =
+      ScriptExecutorParams::Create(global_environment, script.function_body(),
+                                   script.argument_array(), async_timeout);
 
-  base::optional<std::string> script_result = script_executor_->Execute(
-      &(function_object->referenced_object()), script.argument_array());
-  if (script_result) {
-    return CommandResult(protocol::ScriptResult(script_result.value()));
-  } else {
-    return CommandResult(protocol::Response::kJavaScriptError);
+  if (params->function_object()) {
+    if (script_executor_->Execute(params, async_handler)) {
+      return CommandResult(protocol::Response::kSuccess);
+    }
   }
+  return CommandResult(protocol::Response::kJavaScriptError);
 }
 
 util::CommandResult<void> WindowDriver::SendKeysInternal(
diff --git a/src/cobalt/webdriver/window_driver.h b/src/cobalt/webdriver/window_driver.h
index f67d272..e68f06d 100644
--- a/src/cobalt/webdriver/window_driver.h
+++ b/src/cobalt/webdriver/window_driver.h
@@ -82,6 +82,8 @@
   util::CommandResult<std::string> GetSource();
   util::CommandResult<protocol::ScriptResult> Execute(
       const protocol::Script& script);
+  util::CommandResult<protocol::ScriptResult> ExecuteAsync(
+      const protocol::Script& script);
   util::CommandResult<void> SendKeys(const protocol::Keys& keys);
   util::CommandResult<protocol::ElementId> GetActiveElement();
   util::CommandResult<void> SwitchFrame(const protocol::FrameId& frame_id);
@@ -117,7 +119,9 @@
       const protocol::SearchStrategy& strategy);
 
   util::CommandResult<protocol::ScriptResult> ExecuteScriptInternal(
-      const protocol::Script& script);
+      const protocol::Script& script,
+      base::optional<base::TimeDelta> async_timeout,
+      ScriptExecutorResult::ResultHandler* result_handler);
 
   util::CommandResult<void> SendKeysInternal(
       scoped_ptr<Keyboard::KeyboardEventVector> keyboard_events);
diff --git a/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
index fa2642d..cc7547c 100755
--- a/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
+++ b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
@@ -15,6 +15,7 @@
 import re
 import socket
 import sys
+import thread
 import threading
 import unittest
 
@@ -49,6 +50,10 @@
 # opened.
 RE_WEBDRIVER_LISTEN = re.compile(r"Starting WebDriver server on port (\d+)$")
 
+STARTUP_TIMEOUT_SECONDS = 2 * 60
+
+COBALT_EXIT_TIMEOUT_SECONDS = 5
+
 COBALT_WEBDRIVER_CAPABILITIES = {
     "browserName": "cobalt",
     "javascriptEnabled": True,
@@ -96,15 +101,20 @@
   return module
 
 
+class TimeoutException(Exception):
+  pass
+
+
 class CobaltRunner(object):
   """Runs a Cobalt browser w/ a WebDriver client attached."""
   test_script_started = threading.Event()
-  should_exit = threading.Event()
   selenium_webdriver_module = None
   webdriver = None
   launcher = None
   log_file_path = None
   thread = None
+  failed = False
+  should_exit = threading.Event()
 
   def __init__(self, platform, executable, devkit_name, log_file_path):
     self.selenium_webdriver_module = ImportSeleniumModule("webdriver")
@@ -126,31 +136,41 @@
   def __enter__(self):
     self.thread = threading.Thread(target=self.Run)
     self.thread.start()
-    self.WaitForStart()
+    try:
+      self.WaitForStart()
+    except KeyboardInterrupt:
+      # potentially from thread.interrupt_main(). We will treat as
+      # a timeout regardless
+      raise TimeoutException
 
   def __exit__(self, exc_type, exc_value, traceback):
-    self.SetShouldExit()
-    self.thread.join()
+    # The unittest module terminates with a SystemExit
+    # If this is a successful exit, then this is a successful run
+    success = exc_type is None or (exc_type is SystemExit
+                                   and not exc_value.code)
+    self.SetShouldExit(failed=not success)
+    self.thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
 
   def _HandleLine(self, line):
     """Internal log line callback."""
 
-    done = self.should_exit.is_set()
     # Wait for WebDriver port here then connect
     if self.test_script_started.is_set():
-      return done
+      return
 
     match = RE_WEBDRIVER_LISTEN.search(line)
     if not match:
-      return done
+      return
 
     port = match.group(1)
+    sys.stderr.write("WebDriver port opened:" + port + "\n")
     self._StartWebdriver(port)
-    return done
 
-  def SetShouldExit(self):
-    """Indicates cobalt process should exit. Done at next log line output."""
+  def SetShouldExit(self, failed=False):
+    """Indicates cobalt process should exit."""
+    self.failed = failed
     self.should_exit.set()
+    self.launcher.SendKill()
 
   def _GetIPAddress(self):
     return self.launcher.GetIPAddress()
@@ -160,12 +180,16 @@
     url = "http://{}:{}/".format(self._GetIPAddress(), port)
     self.webdriver = self.selenium_webdriver_module.Remote(
         url, COBALT_WEBDRIVER_CAPABILITIES)
+    sys.stderr.write("Selenium Connected\n")
     _webdriver = self.webdriver
     self.test_script_started.set()
 
   def WaitForStart(self):
     """Waits for the webdriver client to attach to cobalt."""
-    self.test_script_started.wait()
+    if not self.test_script_started.wait(STARTUP_TIMEOUT_SECONDS):
+      self.SetShouldExit(failed=True)
+      raise TimeoutException
+    sys.stderr.write("Cobalt started\n")
 
   def Run(self):
     """Thread run routine."""
@@ -181,12 +205,21 @@
         self.log_file = sys.stdout
 
       self.launcher.SetOutputFile(self.log_file)
+      sys.stderr.write("Running launcher \n")
       self.launcher.Run()
+      sys.stderr.write("Cobalt terminated. failed: " + str(self.failed) + "\n")
       # This is watched for in webdriver_benchmark_test.py
-      sys.stdout.write("partial_layout_benchmark TEST COMPLETE\n")
+      if not self.failed:
+        sys.stdout.write("partial_layout_benchmark TEST COMPLETE\n")
+    except Exception as ex:
+      print("Exception running Cobalt " + str(ex), file=sys.stderr)
     finally:
       if to_close:
         to_close.close()
+      if not self.should_exit.is_set():
+        # If the main thread is not expecting us to exit,
+        # we must interrupt it.
+        thread.interrupt_main()
     return 0
 
 
@@ -226,9 +259,13 @@
   if devkit_name is None:
     devkit_name = socket.gethostname()
 
-  with CobaltRunner(platform, executable, devkit_name, args.log_file):
-    unittest.main()
-  return 0
+  try:
+    with CobaltRunner(platform, executable, devkit_name, args.log_file):
+      unittest.main()
+    return 0
+  except TimeoutException:
+    print("Timeout waiting for Cobalt to start", file=sys.stderr)
+    return 1
 
 
 if __name__ == "__main__":
diff --git a/src/cobalt/webdriver_benchmarks/tests/guide.py b/src/cobalt/webdriver_benchmarks/tests/guide.py
index 2ebf58e..324daef 100755
--- a/src/cobalt/webdriver_benchmarks/tests/guide.py
+++ b/src/cobalt/webdriver_benchmarks/tests/guide.py
@@ -9,8 +9,7 @@
 import sys
 
 # The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(
-    os.path.realpath(__file__))))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
 
 # pylint: disable=C6204,C6203
 import tv
@@ -36,9 +35,7 @@
       self.assert_displayed(tv.FOCUSED_GUIDE)
       self.wait_for_layout_complete()
       self.send_keys(tv.FOCUSED_GUIDE, keys.Keys.ARROW_RIGHT)
-      self.poll_until_found(tv.FOCUSED_SHELF)
-      self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
-      self.wait_for_layout_complete()
+      self.wait_for_layout_complete_after_focused_shelf()
       layout_times_us.append(self.get_keyup_layout_duration_us())
 
     self.record_results("GuideTest.test_simple", layout_times_us)
diff --git a/src/cobalt/webdriver_benchmarks/tests/shelf.py b/src/cobalt/webdriver_benchmarks/tests/shelf.py
index 7372e55..ce058e0 100755
--- a/src/cobalt/webdriver_benchmarks/tests/shelf.py
+++ b/src/cobalt/webdriver_benchmarks/tests/shelf.py
@@ -9,8 +9,7 @@
 import sys
 
 # The parent directory is a module
-sys.path.insert(0, os.path.dirname(os.path.dirname(
-    os.path.realpath(__file__))))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
 
 # pylint: disable=C6204,C6203
 import tv
@@ -33,16 +32,12 @@
 
     for _ in xrange(DEFAULT_SHELVES_COUNT):
       self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_DOWN)
-      self.poll_until_found(tv.FOCUSED_SHELF)
-      self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
-      self.wait_for_layout_complete()
+      self.wait_for_layout_complete_after_focused_shelf()
       layout_times_us.append(self.get_keyup_layout_duration_us())
 
     for _ in xrange(SHELF_ITEMS_COUNT):
       self.send_keys(tv.FOCUSED_TILE, keys.Keys.ARROW_RIGHT)
-      self.poll_until_found(tv.FOCUSED_TILE)
-      self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
-      self.wait_for_layout_complete()
+      self.wait_for_layout_complete_after_focused_shelf()
       layout_times_us.append(self.get_keyup_layout_duration_us())
 
     self.record_results("ShelfTest.test_simple", layout_times_us)
diff --git a/src/cobalt/webdriver_benchmarks/tests/test_median.py b/src/cobalt/webdriver_benchmarks/tests/test_median.py
new file mode 100755
index 0000000..f265b15
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/test_median.py
@@ -0,0 +1,50 @@
+#!/usr/bin/python2
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests the functionality median function."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+# The parent directory is a module
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+
+# pylint: disable=C6204,C6203
+from tv_testcase import _median as median
+import unittest
+
+
+class MedianTest(unittest.TestCase):
+
+  def test_empty_case(self):
+    self.assertEqual(median([]), None)
+
+  def test_one_item(self):
+    self.assertEqual(median([1]), 1)
+
+  def test_two_items(self):
+    self.assertAlmostEqual(median([4, 1]), 2.5)
+
+  def test_three_items(self):
+    self.assertAlmostEqual(median([4, -4, -2]), -2)
+
+  def test_four_items(self):
+    self.assertAlmostEqual(median([4, 0, 1, -2]), 0.5)
+
+
+if __name__ == '__main__':
+  sys.exit(unittest.main())
diff --git a/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py b/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py
new file mode 100755
index 0000000..b1ede63
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python2
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Calculate time to download YouTube TV app and initial layout of shelves."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+
+# The parent directory is a module
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
+
+# pylint: disable=C6204,C6203
+import tv_testcase
+
+
+class TimeToShelf(tv_testcase.TvTestCase):
+
+  def test_simple(self):
+    """This test tries to measure the startup time for the YouTube TV page.
+
+    Specifically, this test uses the Cobalt CVal Cobalt.Lifetime, which gets
+    updated ~60Hz on a best effort basis.  This value is in microseconds.
+
+    Note: t0 is defined after Cobalt starts up, but has not navigated to a page.
+    If that true startup time metric is desired, perhaps a separate should be
+    used.
+    """
+    metrics_array = []
+    blank_startup_time_microseconds = self.get_int_cval('Cobalt.Lifetime')
+    for _ in range(10):
+      t0 = self.get_int_cval('Cobalt.Lifetime')
+      self.load_tv()
+      self.wait_for_layout_complete_after_focused_shelf()
+      t1 = self.get_int_cval('Cobalt.Lifetime')
+      startup_time_microseconds = t1 - t0
+      metrics_array.append(startup_time_microseconds)
+
+    self.record_results('TimeToShelf.test_time_shelf_display', metrics_array)
+    self.record_results('TimeToShelf.blank_startup_time',
+                        blank_startup_time_microseconds)
+
+
+if __name__ == '__main__':
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/timer.py b/src/cobalt/webdriver_benchmarks/timer.py
new file mode 100644
index 0000000..0ee08dc
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/timer.py
@@ -0,0 +1,94 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Contains a contextmanager for a timer.
+
+  Example usage:
+
+    from timer import Timer
+
+    with Timer('SomeTask') as t:
+      # Do some time consuming task
+      print('So far {} seconds have passed'.format(t.seconds_elapsed))
+
+  print(t)  # This will print something like 'SomeTask took 1.2 seconds'
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import timeit
+
+
+class Timer(object):
+  """ContextManager for measuring time since an event."""
+
+  def __init__(self, description):
+    """Initializes the timer.
+
+    Args:
+      description: A string containing the description of the timer.
+    """
+    self.description = description
+    self._timer = timeit.default_timer  # Choose best timer for the platform.
+    self.start_time = None
+    self._seconds_elapsed = None
+
+  def __enter__(self):
+    """Starts the timer.
+
+    __enter__ allows this class to be used as a ContextManager.
+
+    Returns:
+      The object itself so it works with the |with| statement.
+    """
+    self.start_time = self._timer()
+    return self
+
+  def __exit__(self, unused_ex_type, unused_ex, unused_ex_trace):
+    """Stops the timer and records the duration.
+
+    __enter__ allows this class to be used as a ContextManager.
+
+    Returns:
+      False.  Any exception raised within the body of the contextmanager will
+      propogate up the stack.
+    """
+    self._seconds_elapsed = self._timer() - self.start_time
+    return False
+
+  @property
+  def seconds_elapsed(self):
+    """Number of seconds elapsed since start until now or time when timer ended.
+
+    This property will return the number of seconds since the timer was started,
+    or the duration of the timer's context manager.
+
+    Returns:
+      A float containing the number of seconds elapsed.
+    """
+    if self._seconds_elapsed is None:
+      return self._timer() - self.start_time
+
+    return self._seconds_elapsed
+
+  def __str__(self):
+    """A magic method for generating a string representation of the object.
+
+    Returns:
+      A string containing a description and a human readable version of timer's
+      value.
+    """
+    return '{} took {} seconds.'.format(self.description, self.seconds_elapsed)
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py
index 3189523..cbb5903 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -9,7 +9,16 @@
 import sys
 import time
 import unittest
-import urlparse
+
+# pylint: disable=C6204
+try:
+  # This code works for Python 2.
+  import urlparse
+  from urllib import urlencode
+except ImportError:
+  # This code works for Python 3.
+  import urllib.parse as urlparse
+  from urllib.parse import urlencode
 
 # This directory is a package
 sys.path.insert(0, os.path.abspath("."))
@@ -22,15 +31,48 @@
 WebDriverWait = partial_layout_benchmark.ImportSeleniumModule(
     submodule="webdriver.support.ui").WebDriverWait
 
-ElementNotVisibleException = (
-    partial_layout_benchmark.ImportSeleniumModule(
-        submodule="common.exceptions").ElementNotVisibleException)
+ElementNotVisibleException = (partial_layout_benchmark.ImportSeleniumModule(
+    submodule="common.exceptions").ElementNotVisibleException)
 
-BASE_URL = "https://www.youtube.com/tv?env_forcedOffAllExperiments=true"
+BASE_URL = "https://www.youtube.com/"
+TV_APP_PATH = "/tv"
+BASE_PARAMS = {"env_forcedOffAllExperiments": True}
 PAGE_LOAD_WAIT_SECONDS = 30
 LAYOUT_TIMEOUT_SECONDS = 5
 
 
+def _median(l):
+  """Returns median value of items in a list.
+
+  Note: This function is setup for convieniance, and might not be performant
+  for large datasets.  Also, no care has been taken to deal with nans, or
+  infinities, so please expand the functionality and tests if that is desired.
+  The other option is to use median.
+
+  Running time: O(n^2)
+  Space complexity: O(n)
+
+  Args:
+    l: List containing sortable items.
+
+  Returns:
+    Median of the items in a list.  None if |l| is empty.
+  """
+  if not l:
+    return None
+  l_length = len(l)
+  if l_length == 0:
+    return l[0]
+
+  l_sorted = sorted(l)
+  middle_index = l_length // 2
+  if len(l) % 2 == 1:
+    return l_sorted[middle_index]
+
+  # If there are even number of items, take the average of two middle values.
+  return 0.5 * (l_sorted[middle_index] + l_sorted[middle_index - 1])
+
+
 class TvTestCase(unittest.TestCase):
   """Base class for WebDriver tests.
 
@@ -44,23 +86,66 @@
   def get_webdriver(self):
     return partial_layout_benchmark.GetWebDriver()
 
-  def goto(self, path):
+  def get_cval(self, cval_name):
+    """Returns value of a cval.
+
+    Args:
+      cval_name: Name of the cval.
+    Returns:
+      Value of the cval.
+    """
+    javascript_code = "return h5vcc.cVal.getValue('{}')".format(cval_name)
+    return self.get_webdriver().execute_script(javascript_code)
+
+  def get_int_cval(self, cval_name):
+    """Returns int value of a cval.
+
+    The cval value must be an integer, if it is a float, then this function will
+    throw a ValueError.
+
+    Args:
+      cval_name: Name of the cval.
+    Returns:
+      Value of the cval.
+    Raises:
+      ValueError if the cval is cannot be converted to int.
+    """
+    answer = self.get_cval(cval_name)
+    if answer is None:
+      return answer
+    return int(answer)
+
+  def goto(self, path, query_params=None):
     """Goes to a path off of BASE_URL.
 
     Args:
-      path: URL path
+      path: URL path without the hostname.
+      query_params: Dictionary of parameter names and values.
     Raises:
       Underlying WebDriver exceptions
     """
-    self.get_webdriver().get(urlparse.urljoin(BASE_URL, path))
+    parsed_url = list(urlparse.urlparse(BASE_URL))
+    parsed_url[2] = path
+    query_dict = BASE_PARAMS.copy()
+    if query_params:
+      query_dict.update(urlparse.parse_qsl(parsed_url[4]))
+      query_dict.update(query_params)
+    parsed_url[4] = urlencode(query_dict)
+    final_url = urlparse.urlunparse(parsed_url)
+    self.get_webdriver().get(final_url)
 
-  def load_tv(self):
+  def load_tv(self, label=None):
     """Loads the main TV page and waits for it to display.
 
+    Args:
+      label: A value for the label query parameter.
     Raises:
       Underlying WebDriver exceptions
     """
-    self.goto("")
+    query_params = None
+    if label is not None:
+      query_params = {"label": label}
+    self.goto(TV_APP_PATH, query_params)
     # Note that the internal tests use "expect_transition" which is
     # a mechanism that sets a maximum timeout for a "@with_retries"
     # decorator-driven success retry loop for subsequent webdriver requests.
@@ -156,8 +241,7 @@
   def wait_for_layout_complete(self):
     """Waits for Cobalt to complete pending layouts."""
     start_time = time.time()
-    while int(self.get_webdriver().execute_script(
-        "return h5vcc.cVal.getValue('Event.MainWebModule.IsProcessing')")):
+    while self.get_int_cval("Event.MainWebModule.IsProcessing"):
 
       if time.time() - start_time > LAYOUT_TIMEOUT_SECONDS:
         raise TvTestCase.LayoutTimeoutException()
@@ -165,8 +249,13 @@
       time.sleep(0.1)
 
   def get_keyup_layout_duration_us(self):
-    return int(self.get_webdriver().execute_script(
-        "return h5vcc.cVal.getValue('Event.Duration.MainWebModule.KeyUp')"))
+    return self.get_int_cval("Event.Duration.MainWebModule.KeyUp")
+
+  def wait_for_layout_complete_after_focused_shelf(self):
+    """Waits for Cobalt to focus on a shelf and complete pending layouts."""
+    self.poll_until_found(tv.FOCUSED_SHELF)
+    self.assert_displayed(tv.FOCUSED_SHELF_TITLE)
+    self.wait_for_layout_complete()
 
   def record_results(self, name, results):
     """Records results of benchmark.
@@ -177,8 +266,13 @@
       name: name of test case
       results: Test results. Must be JSON encodable
     """
-    print("tv_testcase RESULT: " + name + " "
-          + json.JSONEncoder().encode(results))
+    if isinstance(results, list):
+      value_to_record = _median(results)
+    else:
+      value_to_record = results
+
+    string_value_to_record = json.JSONEncoder().encode(value_to_record)
+    print("tv_testcase RESULT: {} {}".format(name, string_value_to_record))
 
 
 def main():
diff --git a/src/cobalt/xhr/xml_http_request_test.cc b/src/cobalt/xhr/xml_http_request_test.cc
index e39fe5f..ae2f187 100644
--- a/src/cobalt/xhr/xml_http_request_test.cc
+++ b/src/cobalt/xhr/xml_http_request_test.cc
@@ -93,7 +93,7 @@
 class FakeSettings : public dom::DOMSettings {
  public:
   FakeSettings()
-      : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL) {}
+      : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) {}
   GURL base_url() const OVERRIDE { return GURL("http://example.com"); }
 };
 
diff --git a/src/content/browser/speech/chunked_byte_buffer.cc b/src/content/browser/speech/chunked_byte_buffer.cc
new file mode 100644
index 0000000..a43e40a
--- /dev/null
+++ b/src/content/browser/speech/chunked_byte_buffer.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/speech/chunked_byte_buffer.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+namespace {
+
+static const size_t kHeaderLength = sizeof(uint32_t);
+
+static_assert(sizeof(size_t) >= kHeaderLength,
+              "chunked byte buffer not supported on this architecture");
+
+uint32_t ReadBigEndian32(const uint8_t* buffer) {
+  return (static_cast<uint32_t>(buffer[3])) |
+         (static_cast<uint32_t>(buffer[2]) << 8) |
+         (static_cast<uint32_t>(buffer[1]) << 16) |
+         (static_cast<uint32_t>(buffer[0]) << 24);
+}
+
+}  // namespace
+
+namespace content {
+
+ChunkedByteBuffer::ChunkedByteBuffer()
+    : partial_chunk_(new Chunk()),
+      total_bytes_stored_(0) {
+}
+
+ChunkedByteBuffer::~ChunkedByteBuffer() {
+  Clear();
+}
+
+void ChunkedByteBuffer::Append(const uint8_t* start, size_t length) {
+  size_t remaining_bytes = length;
+  const uint8_t* next_data = start;
+
+  while (remaining_bytes > 0) {
+    DCHECK(partial_chunk_ != NULL);
+    size_t insert_length = 0;
+    bool header_completed = false;
+    bool content_completed = false;
+    std::vector<uint8_t>* insert_target;
+
+    if (partial_chunk_->header.size() < kHeaderLength) {
+      const size_t bytes_to_complete_header =
+          kHeaderLength - partial_chunk_->header.size();
+      insert_length = std::min(bytes_to_complete_header, remaining_bytes);
+      insert_target = &partial_chunk_->header;
+      header_completed = (remaining_bytes >= bytes_to_complete_header);
+    } else {
+      DCHECK_LT(partial_chunk_->content->size(),
+                partial_chunk_->ExpectedContentLength());
+      const size_t bytes_to_complete_chunk =
+          partial_chunk_->ExpectedContentLength() -
+          partial_chunk_->content->size();
+      insert_length = std::min(bytes_to_complete_chunk, remaining_bytes);
+      insert_target = partial_chunk_->content.get();
+      content_completed = (remaining_bytes >= bytes_to_complete_chunk);
+    }
+
+    DCHECK_GT(insert_length, 0U);
+    DCHECK_LE(insert_length, remaining_bytes);
+    DCHECK_LE(next_data + insert_length, start + length);
+    insert_target->insert(insert_target->end(),
+                          next_data,
+                          next_data + insert_length);
+    next_data += insert_length;
+    remaining_bytes -= insert_length;
+
+    if (header_completed) {
+      DCHECK_EQ(partial_chunk_->header.size(), kHeaderLength);
+      if (partial_chunk_->ExpectedContentLength() == 0) {
+        // Handle zero-byte chunks.
+        chunks_.push_back(partial_chunk_.release());
+        partial_chunk_.reset(new Chunk());
+      } else {
+        partial_chunk_->content->reserve(
+            partial_chunk_->ExpectedContentLength());
+      }
+    } else if (content_completed) {
+      DCHECK_EQ(partial_chunk_->content->size(),
+                partial_chunk_->ExpectedContentLength());
+      chunks_.push_back(partial_chunk_.release());
+      partial_chunk_.reset(new Chunk());
+    }
+  }
+  DCHECK_EQ(next_data, start + length);
+  total_bytes_stored_ += length;
+}
+
+void ChunkedByteBuffer::Append(const std::string& string) {
+  Append(reinterpret_cast<const uint8_t*>(string.data()), string.size());
+}
+
+bool ChunkedByteBuffer::HasChunks() const {
+  return !chunks_.empty();
+}
+
+std::unique_ptr<std::vector<uint8_t>> ChunkedByteBuffer::PopChunk() {
+  if (chunks_.empty())
+    return std::unique_ptr<std::vector<uint8_t>>();
+  std::unique_ptr<Chunk> chunk(*chunks_.begin());
+  chunks_.weak_erase(chunks_.begin());
+  DCHECK_EQ(chunk->header.size(), kHeaderLength);
+  DCHECK_EQ(chunk->content->size(), chunk->ExpectedContentLength());
+  total_bytes_stored_ -= chunk->content->size();
+  total_bytes_stored_ -= kHeaderLength;
+  return std::move(chunk->content);
+}
+
+void ChunkedByteBuffer::Clear() {
+  chunks_.clear();
+  partial_chunk_.reset(new Chunk());
+  total_bytes_stored_ = 0;
+}
+
+ChunkedByteBuffer::Chunk::Chunk() : content(new std::vector<uint8_t>()) {}
+
+ChunkedByteBuffer::Chunk::~Chunk() {
+}
+
+size_t ChunkedByteBuffer::Chunk::ExpectedContentLength() const {
+  DCHECK_EQ(header.size(), kHeaderLength);
+  return static_cast<size_t>(ReadBigEndian32(&header[0]));
+}
+
+}  // namespace content
diff --git a/src/content/browser/speech/chunked_byte_buffer.h b/src/content/browser/speech/chunked_byte_buffer.h
new file mode 100644
index 0000000..41e273d
--- /dev/null
+++ b/src/content/browser/speech/chunked_byte_buffer.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
+#define CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Models a chunk-oriented byte buffer. The term chunk is herein defined as an
+// arbitrary sequence of bytes that is preceeded by N header bytes, indicating
+// its size. Data may be appended to the buffer with no particular respect of
+// chunks boundaries. However, chunks can be extracted (FIFO) only when their
+// content (according to their header) is fully available in the buffer.
+// The current implementation support only 4 byte Big Endian headers.
+// Empty chunks (i.e. the sequence 00 00 00 00) are NOT allowed.
+//
+// E.g. 00 00 00 04 xx xx xx xx 00 00 00 02 yy yy 00 00 00 04 zz zz zz zz
+//      [----- CHUNK 1 -------] [--- CHUNK 2 ---] [------ CHUNK 3 ------]
+class CONTENT_EXPORT ChunkedByteBuffer {
+ public:
+  ChunkedByteBuffer();
+  ~ChunkedByteBuffer();
+
+  // Appends |length| bytes starting from |start| to the buffer.
+  void Append(const uint8_t* start, size_t length);
+
+  // Appends bytes contained in the |string| to the buffer.
+  void Append(const std::string& string);
+
+  // Checks whether one or more complete chunks are available in the buffer.
+  bool HasChunks() const;
+
+  // If enough data is available, reads and removes the first complete chunk
+  // from the buffer. Returns a NULL pointer if no complete chunk is available.
+  std::unique_ptr<std::vector<uint8_t>> PopChunk();
+
+  // Clears all the content of the buffer.
+  void Clear();
+
+  // Returns the number of raw bytes (including headers) present.
+  size_t GetTotalLength() const { return total_bytes_stored_; }
+
+ private:
+  struct Chunk {
+    Chunk();
+    ~Chunk();
+
+    std::vector<uint8_t> header;
+    std::unique_ptr<std::vector<uint8_t>> content;
+    size_t ExpectedContentLength() const;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Chunk);
+  };
+
+  ScopedVector<Chunk> chunks_;
+  std::unique_ptr<Chunk> partial_chunk_;
+  size_t total_bytes_stored_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChunkedByteBuffer);
+};
+
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
diff --git a/src/content/browser/speech/chunked_byte_buffer_unittest.cc b/src/content/browser/speech/chunked_byte_buffer_unittest.cc
new file mode 100644
index 0000000..d8a5cb2
--- /dev/null
+++ b/src/content/browser/speech/chunked_byte_buffer_unittest.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "content/browser/speech/chunked_byte_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+typedef std::vector<uint8_t> ByteVector;
+
+TEST(ChunkedByteBufferTest, BasicTest) {
+  ChunkedByteBuffer buffer;
+
+  const uint8_t kChunks[] = {
+      0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04,  // Chunk 1: 4 bytes
+      0x00, 0x00, 0x00, 0x02, 0x05, 0x06,              // Chunk 2: 2 bytes
+      0x00, 0x00, 0x00, 0x01, 0x07                     // Chunk 3: 1 bytes
+  };
+
+  EXPECT_EQ(0U, buffer.GetTotalLength());
+  EXPECT_FALSE(buffer.HasChunks());
+
+  // Append partially chunk 1.
+  buffer.Append(kChunks, 2);
+  EXPECT_EQ(2U, buffer.GetTotalLength());
+  EXPECT_FALSE(buffer.HasChunks());
+
+  // Complete chunk 1.
+  buffer.Append(kChunks + 2, 6);
+  EXPECT_EQ(8U, buffer.GetTotalLength());
+  EXPECT_TRUE(buffer.HasChunks());
+
+  // Append fully chunk 2.
+  buffer.Append(kChunks + 8, 6);
+  EXPECT_EQ(14U, buffer.GetTotalLength());
+  EXPECT_TRUE(buffer.HasChunks());
+
+  // Remove and check chunk 1.
+  std::unique_ptr<ByteVector> chunk;
+  chunk = buffer.PopChunk();
+  EXPECT_TRUE(chunk != NULL);
+  EXPECT_EQ(4U, chunk->size());
+  EXPECT_EQ(0, std::char_traits<uint8_t>::compare(kChunks + 4, &(*chunk)[0],
+                                                  chunk->size()));
+  EXPECT_EQ(6U, buffer.GetTotalLength());
+  EXPECT_TRUE(buffer.HasChunks());
+
+  // Read and check chunk 2.
+  chunk = buffer.PopChunk();
+  EXPECT_TRUE(chunk != NULL);
+  EXPECT_EQ(2U, chunk->size());
+  EXPECT_EQ(0, std::char_traits<uint8_t>::compare(kChunks + 12, &(*chunk)[0],
+                                                  chunk->size()));
+  EXPECT_EQ(0U, buffer.GetTotalLength());
+  EXPECT_FALSE(buffer.HasChunks());
+
+  // Append fully chunk 3.
+  buffer.Append(kChunks + 14, 5);
+  EXPECT_EQ(5U, buffer.GetTotalLength());
+
+  // Remove and check chunk 3.
+  chunk = buffer.PopChunk();
+  EXPECT_TRUE(chunk != NULL);
+  EXPECT_EQ(1U, chunk->size());
+  EXPECT_EQ((*chunk)[0], kChunks[18]);
+  EXPECT_EQ(0U, buffer.GetTotalLength());
+  EXPECT_FALSE(buffer.HasChunks());
+}
+
+}  // namespace content
diff --git a/src/content/browser/speech/endpointer/endpointer.cc b/src/content/browser/speech/endpointer/endpointer.cc
new file mode 100644
index 0000000..1758970
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/speech/endpointer/endpointer.h"
+
+#include "base/time/time.h"
+#include "content/browser/speech/audio_buffer.h"
+
+using base::Time;
+
+namespace {
+const int kFrameRate = 50;  // 1 frame = 20ms of audio.
+}
+
+namespace content {
+
+Endpointer::Endpointer(int sample_rate)
+    : speech_input_possibly_complete_silence_length_us_(-1),
+      speech_input_complete_silence_length_us_(-1),
+      audio_frame_time_us_(0),
+      sample_rate_(sample_rate),
+      frame_size_(0) {
+  Reset();
+
+  frame_size_ = static_cast<int>(sample_rate / static_cast<float>(kFrameRate));
+
+  speech_input_minimum_length_us_ =
+      static_cast<int64_t>(1.7 * Time::kMicrosecondsPerSecond);
+  speech_input_complete_silence_length_us_ =
+      static_cast<int64_t>(0.5 * Time::kMicrosecondsPerSecond);
+  long_speech_input_complete_silence_length_us_ = -1;
+  long_speech_length_us_ = -1;
+  speech_input_possibly_complete_silence_length_us_ =
+      1 * Time::kMicrosecondsPerSecond;
+
+  // Set the default configuration for Push To Talk mode.
+  EnergyEndpointerParams ep_config;
+  ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate));
+  ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate));
+  ep_config.set_endpoint_margin(0.2f);
+  ep_config.set_onset_window(0.15f);
+  ep_config.set_speech_on_window(0.4f);
+  ep_config.set_offset_window(0.15f);
+  ep_config.set_onset_detect_dur(0.09f);
+  ep_config.set_onset_confirm_dur(0.075f);
+  ep_config.set_on_maintain_dur(0.10f);
+  ep_config.set_offset_confirm_dur(0.12f);
+  ep_config.set_decision_threshold(1000.0f);
+  ep_config.set_min_decision_threshold(50.0f);
+  ep_config.set_fast_update_dur(0.2f);
+  ep_config.set_sample_rate(static_cast<float>(sample_rate));
+  ep_config.set_min_fundamental_frequency(57.143f);
+  ep_config.set_max_fundamental_frequency(400.0f);
+  ep_config.set_contamination_rejection_period(0.25f);
+  energy_endpointer_.Init(ep_config);
+}
+
+void Endpointer::Reset() {
+  old_ep_status_ = EP_PRE_SPEECH;
+  waiting_for_speech_possibly_complete_timeout_ = false;
+  waiting_for_speech_complete_timeout_ = false;
+  speech_previously_detected_ = false;
+  speech_input_complete_ = false;
+  audio_frame_time_us_ = 0; // Reset time for packets sent to endpointer.
+  speech_end_time_us_ = -1;
+  speech_start_time_us_ = -1;
+}
+
+void Endpointer::StartSession() {
+  Reset();
+  energy_endpointer_.StartSession();
+}
+
+void Endpointer::EndSession() {
+  energy_endpointer_.EndSession();
+}
+
+void Endpointer::SetEnvironmentEstimationMode() {
+  Reset();
+  energy_endpointer_.SetEnvironmentEstimationMode();
+}
+
+void Endpointer::SetUserInputMode() {
+  energy_endpointer_.SetUserInputMode();
+}
+
+EpStatus Endpointer::Status(int64_t* time) {
+  return energy_endpointer_.Status(time);
+}
+
+EpStatus Endpointer::ProcessAudio(const AudioChunk& raw_audio, float* rms_out) {
+  const int16_t* audio_data = raw_audio.SamplesData16();
+  const int num_samples = raw_audio.NumSamples();
+  EpStatus ep_status = EP_PRE_SPEECH;
+
+  // Process the input data in blocks of frame_size_, dropping any incomplete
+  // frames at the end (which is ok since typically the caller will be recording
+  // audio in multiples of our frame size).
+  int sample_index = 0;
+  while (sample_index + frame_size_ <= num_samples) {
+    // Have the endpointer process the frame.
+    energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_,
+                                         audio_data + sample_index,
+                                         frame_size_,
+                                         rms_out);
+    sample_index += frame_size_;
+    audio_frame_time_us_ += (frame_size_ * Time::kMicrosecondsPerSecond) /
+                         sample_rate_;
+
+    // Get the status of the endpointer.
+    int64_t ep_time;
+    ep_status = energy_endpointer_.Status(&ep_time);
+
+    // Handle state changes.
+    if ((EP_SPEECH_PRESENT == ep_status) &&
+        (EP_POSSIBLE_ONSET == old_ep_status_)) {
+      speech_end_time_us_ = -1;
+      waiting_for_speech_possibly_complete_timeout_ = false;
+      waiting_for_speech_complete_timeout_ = false;
+      // Trigger SpeechInputDidStart event on first detection.
+      if (false == speech_previously_detected_) {
+        speech_previously_detected_ = true;
+        speech_start_time_us_ = ep_time;
+      }
+    }
+    if ((EP_PRE_SPEECH == ep_status) &&
+        (EP_POSSIBLE_OFFSET == old_ep_status_)) {
+      speech_end_time_us_ = ep_time;
+      waiting_for_speech_possibly_complete_timeout_ = true;
+      waiting_for_speech_complete_timeout_ = true;
+    }
+    if (ep_time > speech_input_minimum_length_us_) {
+      // Speech possibly complete timeout.
+      if ((waiting_for_speech_possibly_complete_timeout_) &&
+          (ep_time - speech_end_time_us_ >
+              speech_input_possibly_complete_silence_length_us_)) {
+        waiting_for_speech_possibly_complete_timeout_ = false;
+      }
+      if (waiting_for_speech_complete_timeout_) {
+        // The length of the silence timeout period can be held constant, or it
+        // can be changed after a fixed amount of time from the beginning of
+        // speech.
+        bool has_stepped_silence =
+            (long_speech_length_us_ > 0) &&
+            (long_speech_input_complete_silence_length_us_ > 0);
+        int64_t requested_silence_length;
+        if (has_stepped_silence &&
+            (ep_time - speech_start_time_us_) > long_speech_length_us_) {
+          requested_silence_length =
+              long_speech_input_complete_silence_length_us_;
+        } else {
+          requested_silence_length =
+              speech_input_complete_silence_length_us_;
+        }
+
+        // Speech complete timeout.
+        if ((ep_time - speech_end_time_us_) > requested_silence_length) {
+          waiting_for_speech_complete_timeout_ = false;
+          speech_input_complete_ = true;
+        }
+      }
+    }
+    old_ep_status_ = ep_status;
+  }
+  return ep_status;
+}
+
+}  // namespace content
diff --git a/src/content/browser/speech/endpointer/endpointer.h b/src/content/browser/speech/endpointer/endpointer.h
new file mode 100644
index 0000000..5790672
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
+
+#include <stdint.h>
+
+#include "content/browser/speech/endpointer/energy_endpointer.h"
+#include "content/common/content_export.h"
+
+class EpStatus;
+
+namespace content {
+
+class AudioChunk;
+
+// A simple interface to the underlying energy-endpointer implementation, this
+// class lets callers provide audio as being recorded and let them poll to find
+// when the user has stopped speaking.
+//
+// There are two events that may trigger the end of speech:
+//
+// speechInputPossiblyComplete event:
+//
+// Signals that silence/noise has  been detected for a *short* amount of
+// time after some speech has been detected. It can be used for low latency
+// UI feedback. To disable it, set it to a large amount.
+//
+// speechInputComplete event:
+//
+// This event is intended to signal end of input and to stop recording.
+// The amount of time to wait after speech is set by
+// speech_input_complete_silence_length_ and optionally two other
+// parameters (see below).
+// This time can be held constant, or can change as more speech is detected.
+// In the latter case, the time changes after a set amount of time from the
+// *beginning* of speech.  This is motivated by the expectation that there
+// will be two distinct types of inputs: short search queries and longer
+// dictation style input.
+//
+// Three parameters are used to define the piecewise constant timeout function.
+// The timeout length is speech_input_complete_silence_length until
+// long_speech_length, when it changes to
+// long_speech_input_complete_silence_length.
+class CONTENT_EXPORT Endpointer {
+ public:
+  explicit Endpointer(int sample_rate);
+
+  // Start the endpointer. This should be called at the beginning of a session.
+  void StartSession();
+
+  // Stop the endpointer.
+  void EndSession();
+
+  // Start environment estimation. Audio will be used for environment estimation
+  // i.e. noise level estimation.
+  void SetEnvironmentEstimationMode();
+
+  // Start user input. This should be called when the user indicates start of
+  // input, e.g. by pressing a button.
+  void SetUserInputMode();
+
+  // Process a segment of audio, which may be more than one frame.
+  // The status of the last frame will be returned.
+  EpStatus ProcessAudio(const AudioChunk& raw_audio, float* rms_out);
+
+  // Get the status of the endpointer.
+  EpStatus Status(int64_t* time_us);
+
+  // Returns true if the endpointer detected reasonable audio levels above
+  // background noise which could be user speech, false if not.
+  bool DidStartReceivingSpeech() const {
+    return speech_previously_detected_;
+  }
+
+  bool IsEstimatingEnvironment() const {
+    return energy_endpointer_.estimating_environment();
+  }
+
+  void set_speech_input_complete_silence_length(int64_t time_us) {
+    speech_input_complete_silence_length_us_ = time_us;
+  }
+
+  void set_long_speech_input_complete_silence_length(int64_t time_us) {
+    long_speech_input_complete_silence_length_us_ = time_us;
+  }
+
+  void set_speech_input_possibly_complete_silence_length(int64_t time_us) {
+    speech_input_possibly_complete_silence_length_us_ = time_us;
+  }
+
+  void set_long_speech_length(int64_t time_us) {
+    long_speech_length_us_ = time_us;
+  }
+
+  bool speech_input_complete() const {
+    return speech_input_complete_;
+  }
+
+  // RMS background noise level in dB.
+  float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); }
+
+ private:
+  // Reset internal states. Helper method common to initial input utterance
+  // and following input utternaces.
+  void Reset();
+
+  // Minimum allowable length of speech input.
+  int64_t speech_input_minimum_length_us_;
+
+  // The speechInputPossiblyComplete event signals that silence/noise has been
+  // detected for a *short* amount of time after some speech has been detected.
+  // This proporty specifies the time period.
+  int64_t speech_input_possibly_complete_silence_length_us_;
+
+  // The speechInputComplete event signals that silence/noise has been
+  // detected for a *long* amount of time after some speech has been detected.
+  // This property specifies the time period.
+  int64_t speech_input_complete_silence_length_us_;
+
+  // Same as above, this specifies the required silence period after speech
+  // detection. This period is used instead of
+  // speech_input_complete_silence_length_ when the utterance is longer than
+  // long_speech_length_. This parameter is optional.
+  int64_t long_speech_input_complete_silence_length_us_;
+
+  // The period of time after which the endpointer should consider
+  // long_speech_input_complete_silence_length_ as a valid silence period
+  // instead of speech_input_complete_silence_length_. This parameter is
+  // optional.
+  int64_t long_speech_length_us_;
+
+  // First speech onset time, used in determination of speech complete timeout.
+  int64_t speech_start_time_us_;
+
+  // Most recent end time, used in determination of speech complete timeout.
+  int64_t speech_end_time_us_;
+
+  int64_t audio_frame_time_us_;
+  EpStatus old_ep_status_;
+  bool waiting_for_speech_possibly_complete_timeout_;
+  bool waiting_for_speech_complete_timeout_;
+  bool speech_previously_detected_;
+  bool speech_input_complete_;
+  EnergyEndpointer energy_endpointer_;
+  int sample_rate_;
+  int32_t frame_size_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
diff --git a/src/content/browser/speech/endpointer/endpointer_unittest.cc b/src/content/browser/speech/endpointer/endpointer_unittest.cc
new file mode 100644
index 0000000..53ec4d1
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "content/browser/speech/audio_buffer.h"
+#include "content/browser/speech/endpointer/endpointer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const int kFrameRate = 50;  // 20 ms long frames for AMR encoding.
+const int kSampleRate = 8000;  // 8 k samples per second for AMR encoding.
+
+// At 8 sample per second a 20 ms frame is 160 samples, which corrsponds
+// to the AMR codec.
+const int kFrameSize = kSampleRate / kFrameRate;  // 160 samples.
+static_assert(kFrameSize == 160, "invalid frame size");
+}
+
+namespace content {
+
+class FrameProcessor {
+ public:
+  // Process a single frame of test audio samples.
+  virtual EpStatus ProcessFrame(int64_t time,
+                                int16_t* samples,
+                                int frame_size) = 0;
+};
+
+void RunEndpointerEventsTest(FrameProcessor* processor) {
+  int16_t samples[kFrameSize];
+
+  // We will create a white noise signal of 150 frames. The frames from 50 to
+  // 100 will have more power, and the endpointer should fire on those frames.
+  const int kNumFrames = 150;
+
+  // Create a random sequence of samples.
+  srand(1);
+  float gain = 0.0;
+  int64_t time = 0;
+  for (int frame_count = 0; frame_count < kNumFrames; ++frame_count) {
+    // The frames from 50 to 100 will have more power, and the endpointer
+    // should detect those frames as speech.
+    if ((frame_count >= 50) && (frame_count < 100)) {
+      gain = 2000.0;
+    } else {
+      gain = 1.0;
+    }
+    // Create random samples.
+    for (int i = 0; i < kFrameSize; ++i) {
+      float randNum = static_cast<float>(rand() - (RAND_MAX / 2)) /
+          static_cast<float>(RAND_MAX);
+      samples[i] = static_cast<int16_t>(gain * randNum);
+    }
+
+    EpStatus ep_status = processor->ProcessFrame(time, samples, kFrameSize);
+    time += static_cast<int64_t>(kFrameSize * (1e6 / kSampleRate));
+
+    // Log the status.
+    if (20 == frame_count)
+      EXPECT_EQ(EP_PRE_SPEECH, ep_status);
+    if (70 == frame_count)
+      EXPECT_EQ(EP_SPEECH_PRESENT, ep_status);
+    if (120 == frame_count)
+      EXPECT_EQ(EP_PRE_SPEECH, ep_status);
+  }
+}
+
+// This test instantiates and initializes a stand alone endpointer module.
+// The test creates FrameData objects with random noise and send them
+// to the endointer module. The energy of the first 50 frames is low,
+// followed by 500 high energy frames, and another 50 low energy frames.
+// We test that the correct start and end frames were detected.
+class EnergyEndpointerFrameProcessor : public FrameProcessor {
+ public:
+  explicit EnergyEndpointerFrameProcessor(EnergyEndpointer* endpointer)
+      : endpointer_(endpointer) {}
+
+  EpStatus ProcessFrame(int64_t time,
+                        int16_t* samples,
+                        int frame_size) override {
+    endpointer_->ProcessAudioFrame(time, samples, kFrameSize, NULL);
+    int64_t ep_time;
+    return endpointer_->Status(&ep_time);
+  }
+
+ private:
+  EnergyEndpointer* endpointer_;
+};
+
+TEST(EndpointerTest, TestEnergyEndpointerEvents) {
+  // Initialize endpointer and configure it. We specify the parameters
+  // here for a 20ms window, and a 20ms step size, which corrsponds to
+  // the narrow band AMR codec.
+  EnergyEndpointerParams ep_config;
+  ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate));
+  ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate));
+  ep_config.set_endpoint_margin(0.2f);
+  ep_config.set_onset_window(0.15f);
+  ep_config.set_speech_on_window(0.4f);
+  ep_config.set_offset_window(0.15f);
+  ep_config.set_onset_detect_dur(0.09f);
+  ep_config.set_onset_confirm_dur(0.075f);
+  ep_config.set_on_maintain_dur(0.10f);
+  ep_config.set_offset_confirm_dur(0.12f);
+  ep_config.set_decision_threshold(100.0f);
+  EnergyEndpointer endpointer;
+  endpointer.Init(ep_config);
+
+  endpointer.StartSession();
+
+  EnergyEndpointerFrameProcessor frame_processor(&endpointer);
+  RunEndpointerEventsTest(&frame_processor);
+
+  endpointer.EndSession();
+};
+
+// Test endpointer wrapper class.
+class EndpointerFrameProcessor : public FrameProcessor {
+ public:
+  explicit EndpointerFrameProcessor(Endpointer* endpointer)
+      : endpointer_(endpointer) {}
+
+  EpStatus ProcessFrame(int64_t time,
+                        int16_t* samples,
+                        int frame_size) override {
+    scoped_refptr<AudioChunk> frame(
+        new AudioChunk(reinterpret_cast<uint8_t*>(samples), kFrameSize * 2, 2));
+    endpointer_->ProcessAudio(*frame.get(), NULL);
+    int64_t ep_time;
+    return endpointer_->Status(&ep_time);
+  }
+
+ private:
+  Endpointer* endpointer_;
+};
+
+TEST(EndpointerTest, TestEmbeddedEndpointerEvents) {
+  const int kSampleRate = 8000;  // 8 k samples per second for AMR encoding.
+
+  Endpointer endpointer(kSampleRate);
+  const int64_t kMillisecondsPerMicrosecond = 1000;
+  const int64_t short_timeout = 300 * kMillisecondsPerMicrosecond;
+  endpointer.set_speech_input_possibly_complete_silence_length(short_timeout);
+  const int64_t long_timeout = 500 * kMillisecondsPerMicrosecond;
+  endpointer.set_speech_input_complete_silence_length(long_timeout);
+  endpointer.StartSession();
+
+  EndpointerFrameProcessor frame_processor(&endpointer);
+  RunEndpointerEventsTest(&frame_processor);
+
+  endpointer.EndSession();
+}
+
+}  // namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer.cc b/src/content/browser/speech/endpointer/energy_endpointer.cc
new file mode 100644
index 0000000..fc1d871
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer.cc
@@ -0,0 +1,379 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// To know more about the algorithm used and the original code which this is
+// based of, see
+// https://wiki.corp.google.com/twiki/bin/view/Main/ChromeGoogleCodeXRef
+
+#include "content/browser/speech/endpointer/energy_endpointer.h"
+
+#include <math.h>
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace {
+
+// Returns the RMS (quadratic mean) of the input signal.
+float RMS(const int16_t* samples, int num_samples) {
+  int64_t ssq_int64 = 0;
+  int64_t sum_int64 = 0;
+  for (int i = 0; i < num_samples; ++i) {
+    sum_int64 += samples[i];
+    ssq_int64 += samples[i] * samples[i];
+  }
+  // now convert to floats.
+  double sum = static_cast<double>(sum_int64);
+  sum /= num_samples;
+  double ssq = static_cast<double>(ssq_int64);
+  return static_cast<float>(sqrt((ssq / num_samples) - (sum * sum)));
+}
+
+int64_t Secs2Usecs(float seconds) {
+  return static_cast<int64_t>(0.5 + (1.0e6 * seconds));
+}
+
+float GetDecibel(float value) {
+  if (value > 1.0e-100)
+    return 20 * log10(value);
+  return -2000.0;
+}
+
+}  // namespace
+
+namespace content {
+
+// Stores threshold-crossing histories for making decisions about the speech
+// state.
+class EnergyEndpointer::HistoryRing {
+ public:
+  HistoryRing() : insertion_index_(0) {}
+
+  // Resets the ring to |size| elements each with state |initial_state|
+  void SetRing(int size, bool initial_state);
+
+  // Inserts a new entry into the ring and drops the oldest entry.
+  void Insert(int64_t time_us, bool decision);
+
+  // Returns the time in microseconds of the most recently added entry.
+  int64_t EndTime() const;
+
+  // Returns the sum of all intervals during which 'decision' is true within
+  // the time in seconds specified by 'duration'. The returned interval is
+  // in seconds.
+  float RingSum(float duration_sec);
+
+ private:
+  struct DecisionPoint {
+    int64_t time_us;
+    bool decision;
+  };
+
+  std::vector<DecisionPoint> decision_points_;
+  int insertion_index_;  // Index at which the next item gets added/inserted.
+
+  DISALLOW_COPY_AND_ASSIGN(HistoryRing);
+};
+
+void EnergyEndpointer::HistoryRing::SetRing(int size, bool initial_state) {
+  insertion_index_ = 0;
+  decision_points_.clear();
+  DecisionPoint init = { -1, initial_state };
+  decision_points_.resize(size, init);
+}
+
+void EnergyEndpointer::HistoryRing::Insert(int64_t time_us, bool decision) {
+  decision_points_[insertion_index_].time_us = time_us;
+  decision_points_[insertion_index_].decision = decision;
+  insertion_index_ = (insertion_index_ + 1) % decision_points_.size();
+}
+
+int64_t EnergyEndpointer::HistoryRing::EndTime() const {
+  int ind = insertion_index_ - 1;
+  if (ind < 0)
+    ind = decision_points_.size() - 1;
+  return decision_points_[ind].time_us;
+}
+
+float EnergyEndpointer::HistoryRing::RingSum(float duration_sec) {
+  if (decision_points_.empty())
+    return 0.0;
+
+  int64_t sum_us = 0;
+  int ind = insertion_index_ - 1;
+  if (ind < 0)
+    ind = decision_points_.size() - 1;
+  int64_t end_us = decision_points_[ind].time_us;
+  bool is_on = decision_points_[ind].decision;
+  int64_t start_us =
+      end_us - static_cast<int64_t>(0.5 + (1.0e6 * duration_sec));
+  if (start_us < 0)
+    start_us = 0;
+  size_t n_summed = 1;  // n points ==> (n-1) intervals
+  while ((decision_points_[ind].time_us > start_us) &&
+         (n_summed < decision_points_.size())) {
+    --ind;
+    if (ind < 0)
+      ind = decision_points_.size() - 1;
+    if (is_on)
+      sum_us += end_us - decision_points_[ind].time_us;
+    is_on = decision_points_[ind].decision;
+    end_us = decision_points_[ind].time_us;
+    n_summed++;
+  }
+
+  return 1.0e-6f * sum_us;  //  Returns total time that was super threshold.
+}
+
+EnergyEndpointer::EnergyEndpointer()
+    : status_(EP_PRE_SPEECH),
+      offset_confirm_dur_sec_(0),
+      endpointer_time_us_(0),
+      fast_update_frames_(0),
+      frame_counter_(0),
+      max_window_dur_(4.0),
+      sample_rate_(0),
+      history_(new HistoryRing()),
+      decision_threshold_(0),
+      estimating_environment_(false),
+      noise_level_(0),
+      rms_adapt_(0),
+      start_lag_(0),
+      end_lag_(0),
+      user_input_start_time_us_(0) {
+}
+
+EnergyEndpointer::~EnergyEndpointer() {
+}
+
+int EnergyEndpointer::TimeToFrame(float time) const {
+  return static_cast<int32_t>(0.5 + (time / params_.frame_period()));
+}
+
+void EnergyEndpointer::Restart(bool reset_threshold) {
+  status_ = EP_PRE_SPEECH;
+  user_input_start_time_us_ = 0;
+
+  if (reset_threshold) {
+    decision_threshold_ = params_.decision_threshold();
+    rms_adapt_ = decision_threshold_;
+    noise_level_ = params_.decision_threshold() / 2.0f;
+    frame_counter_ = 0;  // Used for rapid initial update of levels.
+  }
+
+  // Set up the memories to hold the history windows.
+  history_->SetRing(TimeToFrame(max_window_dur_), false);
+
+  // Flag that indicates that current input should be used for
+  // estimating the environment. The user has not yet started input
+  // by e.g. pressed the push-to-talk button. By default, this is
+  // false for backward compatibility.
+  estimating_environment_ = false;
+}
+
+void EnergyEndpointer::Init(const EnergyEndpointerParams& params) {
+  params_ = params;
+
+  // Find the longest history interval to be used, and make the ring
+  // large enough to accommodate that number of frames.  NOTE: This
+  // depends upon ep_frame_period being set correctly in the factory
+  // that did this instantiation.
+  max_window_dur_ = params_.onset_window();
+  if (params_.speech_on_window() > max_window_dur_)
+    max_window_dur_ = params_.speech_on_window();
+  if (params_.offset_window() > max_window_dur_)
+    max_window_dur_ = params_.offset_window();
+  Restart(true);
+
+  offset_confirm_dur_sec_ = params_.offset_window() -
+                            params_.offset_confirm_dur();
+  if (offset_confirm_dur_sec_ < 0.0)
+    offset_confirm_dur_sec_ = 0.0;
+
+  user_input_start_time_us_ = 0;
+
+  // Flag that indicates that  current input should be used for
+  // estimating the environment. The user has not yet started input
+  // by e.g. pressed the push-to-talk button. By default, this is
+  // false for backward compatibility.
+  estimating_environment_ = false;
+  // The initial value of the noise and speech levels is inconsequential.
+  // The level of the first frame will overwrite these values.
+  noise_level_ = params_.decision_threshold() / 2.0f;
+  fast_update_frames_ =
+      static_cast<int64_t>(params_.fast_update_dur() / params_.frame_period());
+
+  frame_counter_ = 0;  // Used for rapid initial update of levels.
+
+  sample_rate_ = params_.sample_rate();
+  start_lag_ = static_cast<int>(sample_rate_ /
+                                params_.max_fundamental_frequency());
+  end_lag_ = static_cast<int>(sample_rate_ /
+                              params_.min_fundamental_frequency());
+}
+
+void EnergyEndpointer::StartSession() {
+  Restart(true);
+}
+
+void EnergyEndpointer::EndSession() {
+  status_ = EP_POST_SPEECH;
+}
+
+void EnergyEndpointer::SetEnvironmentEstimationMode() {
+  Restart(true);
+  estimating_environment_ = true;
+}
+
+void EnergyEndpointer::SetUserInputMode() {
+  estimating_environment_ = false;
+  user_input_start_time_us_ = endpointer_time_us_;
+}
+
+void EnergyEndpointer::ProcessAudioFrame(int64_t time_us,
+                                         const int16_t* samples,
+                                         int num_samples,
+                                         float* rms_out) {
+  endpointer_time_us_ = time_us;
+  float rms = RMS(samples, num_samples);
+
+  // Check that this is user input audio vs. pre-input adaptation audio.
+  // Input audio starts when the user indicates start of input, by e.g.
+  // pressing push-to-talk. Audio received prior to that is used to update
+  // noise and speech level estimates.
+  if (!estimating_environment_) {
+    bool decision = false;
+    if ((endpointer_time_us_ - user_input_start_time_us_) <
+        Secs2Usecs(params_.contamination_rejection_period())) {
+      decision = false;
+      DVLOG(1) << "decision: forced to false, time: " << endpointer_time_us_;
+    } else {
+      decision = (rms > decision_threshold_);
+    }
+
+    history_->Insert(endpointer_time_us_, decision);
+
+    switch (status_) {
+      case EP_PRE_SPEECH:
+        if (history_->RingSum(params_.onset_window()) >
+            params_.onset_detect_dur()) {
+          status_ = EP_POSSIBLE_ONSET;
+        }
+        break;
+
+      case EP_POSSIBLE_ONSET: {
+        float tsum = history_->RingSum(params_.onset_window());
+        if (tsum > params_.onset_confirm_dur()) {
+          status_ = EP_SPEECH_PRESENT;
+        } else {  // If signal is not maintained, drop back to pre-speech.
+          if (tsum <= params_.onset_detect_dur())
+            status_ = EP_PRE_SPEECH;
+        }
+        break;
+      }
+
+      case EP_SPEECH_PRESENT: {
+        // To induce hysteresis in the state residency, we allow a
+        // smaller residency time in the on_ring, than was required to
+        // enter the SPEECH_PERSENT state.
+        float on_time = history_->RingSum(params_.speech_on_window());
+        if (on_time < params_.on_maintain_dur())
+          status_ = EP_POSSIBLE_OFFSET;
+        break;
+      }
+
+      case EP_POSSIBLE_OFFSET:
+        if (history_->RingSum(params_.offset_window()) <=
+            offset_confirm_dur_sec_) {
+          // Note that this offset time may be beyond the end
+          // of the input buffer in a real-time system.  It will be up
+          // to the RecognizerSession to decide what to do.
+          status_ = EP_PRE_SPEECH;  // Automatically reset for next utterance.
+        } else {  // If speech picks up again we allow return to SPEECH_PRESENT.
+          if (history_->RingSum(params_.speech_on_window()) >=
+              params_.on_maintain_dur())
+            status_ = EP_SPEECH_PRESENT;
+        }
+        break;
+
+      default:
+        LOG(WARNING) << "Invalid case in switch: " << status_;
+        break;
+    }
+
+    // If this is a quiet, non-speech region, slowly adapt the detection
+    // threshold to be about 6dB above the average RMS.
+    if ((!decision) && (status_ == EP_PRE_SPEECH)) {
+      decision_threshold_ = (0.98f * decision_threshold_) + (0.02f * 2 * rms);
+      rms_adapt_ = decision_threshold_;
+    } else {
+      // If this is in a speech region, adapt the decision threshold to
+      // be about 10dB below the average RMS. If the noise level is high,
+      // the threshold is pushed up.
+      // Adaptation up to a higher level is 5 times faster than decay to
+      // a lower level.
+      if ((status_ == EP_SPEECH_PRESENT) && decision) {
+        if (rms_adapt_ > rms) {
+          rms_adapt_ = (0.99f * rms_adapt_) + (0.01f * rms);
+        } else {
+          rms_adapt_ = (0.95f * rms_adapt_) + (0.05f * rms);
+        }
+        float target_threshold = 0.3f * rms_adapt_ +  noise_level_;
+        decision_threshold_ = (.90f * decision_threshold_) +
+                              (0.10f * target_threshold);
+      }
+    }
+
+    // Set a floor
+    if (decision_threshold_ < params_.min_decision_threshold())
+      decision_threshold_ = params_.min_decision_threshold();
+  }
+
+  // Update speech and noise levels.
+  UpdateLevels(rms);
+  ++frame_counter_;
+
+  if (rms_out)
+    *rms_out = GetDecibel(rms);
+}
+
+float EnergyEndpointer::GetNoiseLevelDb() const {
+  return GetDecibel(noise_level_);
+}
+
+void EnergyEndpointer::UpdateLevels(float rms) {
+  // Update quickly initially. We assume this is noise and that
+  // speech is 6dB above the noise.
+  if (frame_counter_ < fast_update_frames_) {
+    // Alpha increases from 0 to (k-1)/k where k is the number of time
+    // steps in the initial adaptation period.
+    float alpha = static_cast<float>(frame_counter_) /
+        static_cast<float>(fast_update_frames_);
+    noise_level_ = (alpha * noise_level_) + ((1 - alpha) * rms);
+    DVLOG(1) << "FAST UPDATE, frame_counter_ " << frame_counter_
+             << ", fast_update_frames_ " << fast_update_frames_;
+  } else {
+    // Update Noise level. The noise level adapts quickly downward, but
+    // slowly upward. The noise_level_ parameter is not currently used
+    // for threshold adaptation. It is used for UI feedback.
+    if (noise_level_ < rms)
+      noise_level_ = (0.999f * noise_level_) + (0.001f * rms);
+    else
+      noise_level_ = (0.95f * noise_level_) + (0.05f * rms);
+  }
+  if (estimating_environment_ || (frame_counter_ < fast_update_frames_)) {
+    decision_threshold_ = noise_level_ * 2; // 6dB above noise level.
+    // Set a floor
+    if (decision_threshold_ < params_.min_decision_threshold())
+      decision_threshold_ = params_.min_decision_threshold();
+  }
+}
+
+EpStatus EnergyEndpointer::Status(int64_t* status_time) const {
+  *status_time = history_->EndTime();
+  return status_;
+}
+
+}  // namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer.h b/src/content/browser/speech/endpointer/energy_endpointer.h
new file mode 100644
index 0000000..7b0b292
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The EnergyEndpointer class finds likely speech onset and offset points.
+//
+// The implementation described here is about the simplest possible.
+// It is based on timings of threshold crossings for overall signal
+// RMS. It is suitable for light weight applications.
+//
+// As written, the basic idea is that one specifies intervals that
+// must be occupied by super- and sub-threshold energy levels, and
+// defers decisions re onset and offset times until these
+// specifications have been met.  Three basic intervals are tested: an
+// onset window, a speech-on window, and an offset window.  We require
+// super-threshold to exceed some mimimum total durations in the onset
+// and speech-on windows before declaring the speech onset time, and
+// we specify a required sub-threshold residency in the offset window
+// before declaring speech offset. As the various residency requirements are
+// met, the EnergyEndpointer instance assumes various states, and can return the
+// ID of these states to the client (see EpStatus below).
+//
+// The levels of the speech and background noise are continuously updated. It is
+// important that the background noise level be estimated initially for
+// robustness in noisy conditions. The first frames are assumed to be background
+// noise and a fast update rate is used for the noise level. The duration for
+// fast update is controlled by the fast_update_dur_ paramter.
+//
+// If used in noisy conditions, the endpointer should be started and run in the
+// EnvironmentEstimation mode, for at least 200ms, before switching to
+// UserInputMode.
+// Audio feedback contamination can appear in the input audio, if not cut
+// out or handled by echo cancellation. Audio feedback can trigger a false
+// accept. The false accepts can be ignored by setting
+// ep_contamination_rejection_period.
+
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "content/browser/speech/endpointer/energy_endpointer_params.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Endpointer status codes
+enum EpStatus {
+  EP_PRE_SPEECH = 10,
+  EP_POSSIBLE_ONSET,
+  EP_SPEECH_PRESENT,
+  EP_POSSIBLE_OFFSET,
+  EP_POST_SPEECH,
+};
+
+class CONTENT_EXPORT EnergyEndpointer {
+ public:
+  // The default construction MUST be followed by Init(), before any
+  // other use can be made of the instance.
+  EnergyEndpointer();
+  virtual ~EnergyEndpointer();
+
+  void Init(const EnergyEndpointerParams& params);
+
+  // Start the endpointer. This should be called at the beginning of a session.
+  void StartSession();
+
+  // Stop the endpointer.
+  void EndSession();
+
+  // Start environment estimation. Audio will be used for environment estimation
+  // i.e. noise level estimation.
+  void SetEnvironmentEstimationMode();
+
+  // Start user input. This should be called when the user indicates start of
+  // input, e.g. by pressing a button.
+  void SetUserInputMode();
+
+  // Computes the next input frame and modifies EnergyEndpointer status as
+  // appropriate based on the computation.
+  void ProcessAudioFrame(int64_t time_us,
+                         const int16_t* samples,
+                         int num_samples,
+                         float* rms_out);
+
+  // Returns the current state of the EnergyEndpointer and the time
+  // corresponding to the most recently computed frame.
+  EpStatus Status(int64_t* status_time_us) const;
+
+  bool estimating_environment() const {
+    return estimating_environment_;
+  }
+
+  // Returns estimated noise level in dB.
+  float GetNoiseLevelDb() const;
+
+ private:
+  class HistoryRing;
+
+  // Resets the endpointer internal state.  If reset_threshold is true, the
+  // state will be reset completely, including adaptive thresholds and the
+  // removal of all history information.
+  void Restart(bool reset_threshold);
+
+  // Update internal speech and noise levels.
+  void UpdateLevels(float rms);
+
+  // Returns the number of frames (or frame number) corresponding to
+  // the 'time' (in seconds).
+  int TimeToFrame(float time) const;
+
+  EpStatus status_;  // The current state of this instance.
+  float offset_confirm_dur_sec_;  // max on time allowed to confirm POST_SPEECH
+  int64_t
+      endpointer_time_us_;  // Time of the most recently received audio frame.
+  int64_t
+      fast_update_frames_;  // Number of frames for initial level adaptation.
+  int64_t
+      frame_counter_;     // Number of frames seen. Used for initial adaptation.
+  float max_window_dur_;  // Largest search window size (seconds)
+  float sample_rate_;  // Sampling rate.
+
+  // Ring buffers to hold the speech activity history.
+  std::unique_ptr<HistoryRing> history_;
+
+  // Configuration parameters.
+  EnergyEndpointerParams params_;
+
+  // RMS which must be exceeded to conclude frame is speech.
+  float decision_threshold_;
+
+  // Flag to indicate that audio should be used to estimate environment, prior
+  // to receiving user input.
+  bool estimating_environment_;
+
+  // Estimate of the background noise level. Used externally for UI feedback.
+  float noise_level_;
+
+  // An adaptive threshold used to update decision_threshold_ when appropriate.
+  float rms_adapt_;
+
+  // Start lag corresponds to the highest fundamental frequency.
+  int start_lag_;
+
+  // End lag corresponds to the lowest fundamental frequency.
+  int end_lag_;
+
+  // Time when mode switched from environment estimation to user input. This
+  // is used to time forced rejection of audio feedback contamination.
+  int64_t user_input_start_time_us_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnergyEndpointer);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
diff --git a/src/content/browser/speech/endpointer/energy_endpointer_params.cc b/src/content/browser/speech/endpointer/energy_endpointer_params.cc
new file mode 100644
index 0000000..9cdf024
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer_params.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/speech/endpointer/energy_endpointer_params.h"
+
+namespace content {
+
+EnergyEndpointerParams::EnergyEndpointerParams() {
+  SetDefaults();
+}
+
+void EnergyEndpointerParams::SetDefaults() {
+  frame_period_ = 0.01f;
+  frame_duration_ = 0.01f;
+  endpoint_margin_ = 0.2f;
+  onset_window_ = 0.15f;
+  speech_on_window_ = 0.4f;
+  offset_window_ = 0.15f;
+  onset_detect_dur_ = 0.09f;
+  onset_confirm_dur_ = 0.075f;
+  on_maintain_dur_ = 0.10f;
+  offset_confirm_dur_ = 0.12f;
+  decision_threshold_ = 150.0f;
+  min_decision_threshold_ = 50.0f;
+  fast_update_dur_ = 0.2f;
+  sample_rate_ = 8000.0f;
+  min_fundamental_frequency_ = 57.143f;
+  max_fundamental_frequency_ = 400.0f;
+  contamination_rejection_period_ = 0.25f;
+}
+
+void EnergyEndpointerParams::operator=(const EnergyEndpointerParams& source) {
+  frame_period_ = source.frame_period();
+  frame_duration_ = source.frame_duration();
+  endpoint_margin_ = source.endpoint_margin();
+  onset_window_ = source.onset_window();
+  speech_on_window_ = source.speech_on_window();
+  offset_window_ = source.offset_window();
+  onset_detect_dur_ = source.onset_detect_dur();
+  onset_confirm_dur_ = source.onset_confirm_dur();
+  on_maintain_dur_ = source.on_maintain_dur();
+  offset_confirm_dur_ = source.offset_confirm_dur();
+  decision_threshold_ = source.decision_threshold();
+  min_decision_threshold_ = source.min_decision_threshold();
+  fast_update_dur_ = source.fast_update_dur();
+  sample_rate_ = source.sample_rate();
+  min_fundamental_frequency_ = source.min_fundamental_frequency();
+  max_fundamental_frequency_ = source.max_fundamental_frequency();
+  contamination_rejection_period_ = source.contamination_rejection_period();
+}
+
+}  //  namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer_params.h b/src/content/browser/speech/endpointer/energy_endpointer_params.h
new file mode 100644
index 0000000..1510435
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer_params.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
+
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Input parameters for the EnergyEndpointer class.
+class CONTENT_EXPORT EnergyEndpointerParams {
+ public:
+  EnergyEndpointerParams();
+
+  void SetDefaults();
+
+  void operator=(const EnergyEndpointerParams& source);
+
+  // Accessors and mutators
+  float frame_period() const { return frame_period_; }
+  void set_frame_period(float frame_period) {
+    frame_period_ = frame_period;
+  }
+
+  float frame_duration() const { return frame_duration_; }
+  void set_frame_duration(float frame_duration) {
+    frame_duration_ = frame_duration;
+  }
+
+  float endpoint_margin() const { return endpoint_margin_; }
+  void set_endpoint_margin(float endpoint_margin) {
+    endpoint_margin_ = endpoint_margin;
+  }
+
+  float onset_window() const { return onset_window_; }
+  void set_onset_window(float onset_window) { onset_window_ = onset_window; }
+
+  float speech_on_window() const { return speech_on_window_; }
+  void set_speech_on_window(float speech_on_window) {
+    speech_on_window_ = speech_on_window;
+  }
+
+  float offset_window() const { return offset_window_; }
+  void set_offset_window(float offset_window) {
+    offset_window_ = offset_window;
+  }
+
+  float onset_detect_dur() const { return onset_detect_dur_; }
+  void set_onset_detect_dur(float onset_detect_dur) {
+    onset_detect_dur_ = onset_detect_dur;
+  }
+
+  float onset_confirm_dur() const { return onset_confirm_dur_; }
+  void set_onset_confirm_dur(float onset_confirm_dur) {
+    onset_confirm_dur_ = onset_confirm_dur;
+  }
+
+  float on_maintain_dur() const { return on_maintain_dur_; }
+  void set_on_maintain_dur(float on_maintain_dur) {
+    on_maintain_dur_ = on_maintain_dur;
+  }
+
+  float offset_confirm_dur() const { return offset_confirm_dur_; }
+  void set_offset_confirm_dur(float offset_confirm_dur) {
+    offset_confirm_dur_ = offset_confirm_dur;
+  }
+
+  float decision_threshold() const { return decision_threshold_; }
+  void set_decision_threshold(float decision_threshold) {
+    decision_threshold_ = decision_threshold;
+  }
+
+  float min_decision_threshold() const { return min_decision_threshold_; }
+  void set_min_decision_threshold(float min_decision_threshold) {
+    min_decision_threshold_ = min_decision_threshold;
+  }
+
+  float fast_update_dur() const { return fast_update_dur_; }
+  void set_fast_update_dur(float fast_update_dur) {
+    fast_update_dur_ = fast_update_dur;
+  }
+
+  float sample_rate() const { return sample_rate_; }
+  void set_sample_rate(float sample_rate) { sample_rate_ = sample_rate; }
+
+  float min_fundamental_frequency() const { return min_fundamental_frequency_; }
+  void set_min_fundamental_frequency(float min_fundamental_frequency) {
+    min_fundamental_frequency_ = min_fundamental_frequency;
+  }
+
+  float max_fundamental_frequency() const { return max_fundamental_frequency_; }
+  void set_max_fundamental_frequency(float max_fundamental_frequency) {
+    max_fundamental_frequency_ = max_fundamental_frequency;
+  }
+
+  float contamination_rejection_period() const {
+    return contamination_rejection_period_;
+  }
+  void set_contamination_rejection_period(
+      float contamination_rejection_period) {
+    contamination_rejection_period_ = contamination_rejection_period;
+  }
+
+ private:
+  float frame_period_;  // Frame period
+  float frame_duration_;  // Window size
+  float onset_window_;  // Interval scanned for onset activity
+  float speech_on_window_;  // Inverval scanned for ongoing speech
+  float offset_window_;  // Interval scanned for offset evidence
+  float offset_confirm_dur_;  // Silence duration required to confirm offset
+  float decision_threshold_;  // Initial rms detection threshold
+  float min_decision_threshold_;  // Minimum rms detection threshold
+  float fast_update_dur_;  // Period for initial estimation of levels.
+  float sample_rate_;  // Expected sample rate.
+
+  // Time to add on either side of endpoint threshold crossings
+  float endpoint_margin_;
+  // Total dur within onset_window required to enter ONSET state
+  float onset_detect_dur_;
+  // Total on time within onset_window required to enter SPEECH_ON state
+  float onset_confirm_dur_;
+  // Minimum dur in SPEECH_ON state required to maintain ON state
+  float on_maintain_dur_;
+  // Minimum fundamental frequency for autocorrelation.
+  float min_fundamental_frequency_;
+  // Maximum fundamental frequency for autocorrelation.
+  float max_fundamental_frequency_;
+  // Period after start of user input that above threshold values are ignored.
+  // This is to reject audio feedback contamination.
+  float contamination_rejection_period_;
+};
+
+}  //  namespace content
+
+#endif  // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
diff --git a/src/media/base/sbplayer_pipeline.cc b/src/media/base/sbplayer_pipeline.cc
index b9d2262..ff21883 100644
--- a/src/media/base/sbplayer_pipeline.cc
+++ b/src/media/base/sbplayer_pipeline.cc
@@ -201,8 +201,6 @@
 
   scoped_refptr<SbPlayerSetBoundsHelper> set_bounds_helper_;
 
-  bool flushing_;
-
   // The following member variables can be accessed from WMPI thread but all
   // modifications to them happens on the pipeline thread.  So any access of
   // them from the WMPI thread and any modification to them on the pipeline
@@ -232,7 +230,6 @@
       has_video_(false),
       audio_read_in_progress_(false),
       video_read_in_progress_(false),
-      flushing_(false),
       set_bounds_helper_(new SbPlayerSetBoundsHelper),
       suspended_(false) {}
 
@@ -327,19 +324,17 @@
     seek_cb.Run(PIPELINE_ERROR_INVALID_STATE);
   }
 
+  player_->PrepareForSeek();
+
   DCHECK(seek_cb_.is_null());
   DCHECK(!seek_cb.is_null());
 
-  flushing_ = true;
-
   if (audio_read_in_progress_ || video_read_in_progress_) {
     message_loop_->PostTask(
         FROM_HERE, base::Bind(&SbPlayerPipeline::Seek, this, time, seek_cb));
     return;
   }
 
-  player_->PrepareForSeek();
-
   {
     base::AutoLock auto_lock(lock_);
     seek_cb_ = seek_cb;
@@ -526,8 +521,8 @@
     return;
   }
 
-  if (error != PIPELINE_OK && !error_cb_.is_null()) {
-    base::ResetAndReturn(&error_cb_).Run(error);
+  if (error != PIPELINE_OK) {
+    ResetAndRunIfNotNull(&error_cb_, error);
   }
 }
 
@@ -600,9 +595,13 @@
   DCHECK(message_loop_->BelongsToCurrentThread());
 
   if (status != PIPELINE_OK) {
-    if (!error_cb_.is_null()) {
-      base::ResetAndReturn(&error_cb_).Run(status);
-    }
+    ResetAndRunIfNotNull(&error_cb_, status);
+    return;
+  }
+
+  if (demuxer_->GetStream(DemuxerStream::AUDIO) == NULL ||
+      demuxer_->GetStream(DemuxerStream::VIDEO) == NULL) {
+    ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
     return;
   }
 
@@ -637,7 +636,6 @@
   DCHECK(message_loop_->BelongsToCurrentThread());
 
   if (status == PIPELINE_OK) {
-    flushing_ = false;
     player_->Seek(seek_time_);
   }
 }
@@ -682,7 +680,6 @@
       video_read_in_progress_ = false;
     }
     if (!seek_cb_.is_null()) {
-      buffering_state_cb_.Run(kPrerollCompleted);
       PipelineStatusCB seek_cb;
       {
         base::AutoLock auto_lock(lock_);
@@ -718,10 +715,6 @@
     return;
   }
 
-  if (flushing_) {
-    return;
-  }
-
   if (type == DemuxerStream::AUDIO) {
     if (audio_read_in_progress_) {
       return;
@@ -768,9 +761,7 @@
     case kSbPlayerStateDestroyed:
       break;
     case kSbPlayerStateError:
-      if (!error_cb_.is_null()) {
-        base::ResetAndReturn(&error_cb_).Run(PIPELINE_ERROR_DECODE);
-      }
+      ResetAndRunIfNotNull(&error_cb_, PIPELINE_ERROR_DECODE);
       break;
   }
 }
diff --git a/src/media/base/shell_audio_bus.cc b/src/media/base/shell_audio_bus.cc
index 86f4129..4b5ad5e 100644
--- a/src/media/base/shell_audio_bus.cc
+++ b/src/media/base/shell_audio_bus.cc
@@ -23,6 +23,9 @@
 
 namespace {
 
+typedef ShellAudioBus::StorageType StorageType;
+typedef ShellAudioBus::SampleType SampleType;
+
 const float kFloat32ToInt16Factor = 32768.f;
 
 inline void ConvertSample(ShellAudioBus::SampleType src_type,
@@ -218,66 +221,86 @@
   }
 }
 
-template <ShellAudioBus::StorageType T>
-inline uint8* ShellAudioBus::GetSamplePtrForType(size_t channel,
-                                                 size_t frame) const {
-  DCHECK_LT(channel, channels_);
-  DCHECK_LT(frame, frames_);
-
-  if (T == kInterleaved) {
-    return channel_data_[0] + sizeof(float) * (channels_ * frame + channel);
-  } else if (T == kPlanar) {
-    return channel_data_[channel] + sizeof(float) * frame;
-  } else {
-    NOTREACHED();
-  }
-
-  return NULL;
-}
-
-template <ShellAudioBus::StorageType T>
-inline float ShellAudioBus::GetFloat32SampleForType(size_t channel,
-                                                    size_t frame) const {
-  return *reinterpret_cast<const float*>(
-      GetSamplePtrForType<T>(channel, frame));
-}
-
-template <ShellAudioBus::StorageType SourceStorageType,
-          ShellAudioBus::StorageType DestStorageType>
-void ShellAudioBus::MixForType(const ShellAudioBus& source) {
+template <typename SourceSampleType,
+          typename DestSampleType,
+          StorageType SourceStorageType,
+          StorageType DestStorageType>
+void ShellAudioBus::MixForTypes(const ShellAudioBus& source) {
   const size_t frames = std::min(frames_, source.frames_);
 
   for (size_t channel = 0; channel < channels_; ++channel) {
     for (size_t frame = 0; frame < frames; ++frame) {
-      *reinterpret_cast<float*>(
-          GetSamplePtrForType<DestStorageType>(channel, frame)) +=
-          source.GetFloat32SampleForType<SourceStorageType>(channel, frame);
+      *reinterpret_cast<DestSampleType*>(
+          GetSamplePtrForType<DestSampleType, DestStorageType>(channel,
+                                                               frame)) +=
+          source.GetSampleForType<SourceSampleType, SourceStorageType>(channel,
+                                                                       frame);
     }
   }
 }
 
 void ShellAudioBus::Mix(const ShellAudioBus& source) {
   DCHECK_EQ(channels_, source.channels_);
-  DCHECK_EQ(sample_type_, kFloat32);
-  DCHECK_EQ(source.sample_type_, kFloat32);
-  if (channels_ != source.channels_ || sample_type_ != kFloat32 ||
-      source.sample_type_ != kFloat32) {
+
+  if (channels_ != source.channels_) {
     ZeroAllFrames();
     return;
   }
 
   // Profiling has identified this area of code as hot, so instead of calling
-  // GetSamplePtr, which branches on storage_type_ each time it is called, we
-  // branch once before we loop and inline the branch of the function we want.
-  DCHECK_EQ(GetSampleSizeInBytes(), sizeof(float));
-  if (source.storage_type_ == kInterleaved && storage_type_ == kInterleaved) {
-    MixForType<kInterleaved, kInterleaved>(source);
-  } else if (source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
-    MixForType<kInterleaved, kPlanar>(source);
-  } else if (source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
-    MixForType<kPlanar, kInterleaved>(source);
-  } else if (source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
-    MixForType<kPlanar, kPlanar>(source);
+  // GetSamplePtr, which branches each time it is called, we branch once
+  // before we loop and inline the branch of the function we want.
+  if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
+      source.storage_type_ == kInterleaved && storage_type_ == kInterleaved) {
+    MixForTypes<int16, int16, kInterleaved, kInterleaved>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
+             source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
+    MixForTypes<int16, int16, kInterleaved, kPlanar>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
+             source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
+    MixForTypes<int16, int16, kPlanar, kInterleaved>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 &&
+             source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
+    MixForTypes<int16, int16, kPlanar, kPlanar>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kInterleaved &&
+             storage_type_ == kInterleaved) {
+    MixForTypes<int16, float, kInterleaved, kInterleaved>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
+    MixForTypes<int16, float, kInterleaved, kPlanar>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
+    MixForTypes<int16, float, kPlanar, kInterleaved>(source);
+  } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
+    MixForTypes<int16, float, kPlanar, kPlanar>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
+             source.storage_type_ == kInterleaved &&
+             storage_type_ == kInterleaved) {
+    MixForTypes<float, int16, kInterleaved, kInterleaved>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
+             source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
+    MixForTypes<float, int16, kInterleaved, kPlanar>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
+             source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
+    MixForTypes<float, int16, kPlanar, kInterleaved>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 &&
+             source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
+    MixForTypes<float, int16, kPlanar, kPlanar>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kInterleaved &&
+             storage_type_ == kInterleaved) {
+    MixForTypes<float, float, kInterleaved, kInterleaved>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kInterleaved && storage_type_ == kPlanar) {
+    MixForTypes<float, float, kInterleaved, kPlanar>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kPlanar && storage_type_ == kInterleaved) {
+    MixForTypes<float, float, kPlanar, kInterleaved>(source);
+  } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 &&
+             source.storage_type_ == kPlanar && storage_type_ == kPlanar) {
+    MixForTypes<float, float, kPlanar, kPlanar>(source);
   } else {
     NOTREACHED();
   }
diff --git a/src/media/base/shell_audio_bus.h b/src/media/base/shell_audio_bus.h
index 16c7012..5beaf2c 100644
--- a/src/media/base/shell_audio_bus.h
+++ b/src/media/base/shell_audio_bus.h
@@ -60,6 +60,7 @@
   size_t channels() const { return channels_; }
   size_t frames() const { return frames_; }
   SampleType sample_type() const { return sample_type_; }
+  StorageType storage_type() const { return storage_type_; }
   size_t GetSampleSizeInBytes() const;
   const uint8* interleaved_data() const;
   const uint8* planar_data(size_t channel) const;
@@ -102,6 +103,40 @@
   void Mix(const ShellAudioBus& source);
   void Mix(const ShellAudioBus& source, const std::vector<float>& matrix);
 
+ public:
+  // The .*ForTypes? functions below are optimized versions that assume what
+  // storage type the bus is using.  They are meant to be called after
+  // checking what storage type the bus is once, and then performing a batch
+  // of operations, where it is known that the type will not change.
+  template <typename SampleTypeName, StorageType T>
+  inline uint8* GetSamplePtrForType(size_t channel, size_t frame) const {
+    DCHECK_LT(channel, channels_);
+    DCHECK_LT(frame, frames_);
+
+    if (T == kInterleaved) {
+      return channel_data_[0] +
+             sizeof(SampleTypeName) * (channels_ * frame + channel);
+    } else if (T == kPlanar) {
+      return channel_data_[channel] + sizeof(SampleTypeName) * frame;
+    } else {
+      NOTREACHED();
+    }
+
+    return NULL;
+  }
+
+  template <typename SampleTypeName, StorageType T>
+  inline SampleTypeName GetSampleForType(size_t channel, size_t frame) const {
+    return *reinterpret_cast<const SampleTypeName*>(
+        GetSamplePtrForType<SampleTypeName, T>(channel, frame));
+  }
+
+  template <typename SourceSampleType,
+            typename DestSampleType,
+            StorageType SourceStorageType,
+            StorageType DestStorageType>
+  void MixForTypes(const ShellAudioBus& source);
+
  private:
   void SetFloat32Sample(size_t channel, size_t frame, float sample) {
     DCHECK_EQ(sample_type_, kFloat32);
@@ -110,20 +145,6 @@
   uint8* GetSamplePtr(size_t channel, size_t frame);
   const uint8* GetSamplePtr(size_t channel, size_t frame) const;
 
-  // The *ForType functions below are optimized versions that assume what
-  // storage type the bus is using.  They are meant to be called after checking
-  // what storage type the bus is once, and then performing a batch of
-  // operations, where it is known that the type will not change.
-  // Note: The bus must have storage type of kFloat32.
-  template <StorageType T>
-  inline uint8* GetSamplePtrForType(size_t channel, size_t frame) const;
-
-  template <StorageType T>
-  inline float GetFloat32SampleForType(size_t channel, size_t frame) const;
-
-  template <StorageType SourceStorageType, StorageType DestStorageType>
-  void MixForType(const ShellAudioBus& source);
-
   // Contiguous block of channel memory if the memory is owned by this object.
   scoped_ptr_malloc<uint8, base::ScopedPtrAlignedFree> data_;
 
diff --git a/src/media/base/starboard_player.cc b/src/media/base/starboard_player.cc
index ce5197c..2708468 100644
--- a/src/media/base/starboard_player.cc
+++ b/src/media/base/starboard_player.cc
@@ -40,6 +40,7 @@
       ticket_(SB_PLAYER_INITIAL_TICKET),
       volume_(1.0),
       paused_(true),
+      seek_pending_(false),
       state_(kPlaying) {
   DCHECK(audio_config.IsValidConfig());
   DCHECK(video_config.IsValidConfig());
@@ -131,6 +132,8 @@
 void StarboardPlayer::PrepareForSeek() {
   DCHECK(message_loop_->BelongsToCurrentThread());
   ++ticket_;
+  SbPlayerSetPause(player_, true);
+  seek_pending_ = true;
 }
 
 void StarboardPlayer::Seek(base::TimeDelta time) {
@@ -154,6 +157,10 @@
 
   ++ticket_;
   SbPlayerSeek(player_, TimeDeltaToSbMediaTime(time), ticket_);
+  seek_pending_ = false;
+  if (!paused_) {
+    SbPlayerSetPause(player_, false);
+  }
 }
 
 void StarboardPlayer::SetVolume(float volume) {
@@ -174,8 +181,10 @@
   DCHECK(message_loop_->BelongsToCurrentThread());
   DCHECK(SbPlayerIsValid(player_));
 
-  SbPlayerSetPause(player_, pause);
   paused_ = pause;
+  if (!seek_pending_) {
+    SbPlayerSetPause(player_, pause);
+  }
 }
 
 void StarboardPlayer::GetInfo(uint32* video_frames_decoded,
diff --git a/src/media/base/starboard_player.h b/src/media/base/starboard_player.h
index 0f71acb..2d9dc58 100644
--- a/src/media/base/starboard_player.h
+++ b/src/media/base/starboard_player.h
@@ -131,6 +131,7 @@
   int ticket_;
   float volume_;
   bool paused_;
+  bool seek_pending_;
   DecoderBufferCache decoder_buffer_cache_;
 
   // The following variables can be accessed from GetInfo(), which can be called
diff --git a/src/media/filters/source_buffer_stream.cc b/src/media/filters/source_buffer_stream.cc
index 7f3678d..25bcfe2 100644
--- a/src/media/filters/source_buffer_stream.cc
+++ b/src/media/filters/source_buffer_stream.cc
@@ -1682,7 +1682,7 @@
 }
 
 base::TimeDelta SourceBufferRange::GetStartTimestamp() const {
-  DCHECK(!buffers_.empty());
+  DLOG_IF(WARNING, buffers_.empty()) << "|buffers_| cannot be empty.";
   base::TimeDelta start_timestamp = media_segment_start_time_;
   if (start_timestamp == kNoTimestamp())
     start_timestamp = buffers_.front()->GetDecodeTimestamp();
diff --git a/src/nb/atomic.h b/src/nb/atomic.h
new file mode 100644
index 0000000..9bd5c61
--- /dev/null
+++ b/src/nb/atomic.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NB_ATOMIC_H_
+#define NB_ATOMIC_H_
+
+#include "starboard/mutex.h"
+#include "starboard/types.h"
+
+namespace nb {
+
+// Provides atomic types like integer and float. Some types like atomic_int32_t
+// are likely to be hardware accelerated for your platform.
+//
+// Never use the parent types like atomic<T>, atomic_number<T> or
+// atomic_integral<T> and instead use the fully qualified classes like
+// atomic_int32_t, atomic_pointer<T*>, etc.
+//
+// Note on template instantiation, avoid using the parent type and instead
+// use the fully qualified type.
+// BAD:
+//  template<typename T>
+//  void Foo(const atomic<T>& value);
+// GOOD:
+//  template<typename atomic_t>
+//  void Foo(const atomic_t& vlaue);
+
+// Atomic Pointer class. Instantiate as atomic_pointer<void*>
+// for void* pointer types.
+template<typename T>
+class atomic_pointer;
+
+// Atomic bool class.
+class atomic_bool;
+
+// Atomic int32 class
+class atomic_int32_t;
+
+// Atomic int64 class.
+class atomic_int64_t;
+
+// Atomic float class.
+class atomic_float;
+
+// Atomic double class.
+class atomic_double;
+
+///////////////////////////////////////////////////////////////////////////////
+// Class hiearchy.
+///////////////////////////////////////////////////////////////////////////////
+
+// Base functionality for atomic types. Defines exchange(), load(),
+// store(), compare_exhange_weak(), compare_exchange_strong()
+template <typename T>

+class atomic;
+
+// Subtype of atomic<T> for numbers likes float and integer types but not bool.
+// Adds fetch_add() and fetch_sub().
+template <typename T>
+class atomic_number;
+
+// Subtype of atomic_number<T> for integer types like int32 and int64. Adds
+// increment and decrement.
+template <typename T>
+class atomic_integral;
+
+///////////////////////////////////////////////////////////////////////////////
+// Implimentation.
+///////////////////////////////////////////////////////////////////////////////
+
+// Similar to C++11 std::atomic<T>.
+// atomic<T> may be instantiated with any TriviallyCopyable type T.
+// atomic<T> is neither copyable nor movable.
+template <typename T>
+class atomic {
+ public:
+  typedef T ValueType;
+
+  // C++11 forbids a copy constructor for std::atomic<T>, it also forbids
+  // a move operation.
+  atomic() : value_() {}
+  explicit atomic(T v) : value_(v) {}
+
+  // Checks whether the atomic operations on all objects of this type
+  // are lock-free.
+  // Returns true if the atomic operations on the objects of this type
+  // are lock-free, false otherwise.
+  //
+  // All atomic types may be implemented using mutexes or other locking
+  // operations, rather than using the lock-free atomic CPU instructions.
+  // atomic types are also allowed to be sometimes lock-free, e.g. if only
+  // aligned memory accesses are naturally atomic on a given architecture,
+  // misaligned objects of the same type have to use locks.
+  //
+  // See also std::atomic<T>::is_lock_free().
+  bool is_lock_free() const { return false; }
+  bool is_lock_free() const volatile { return false; }
+
+  // Atomically replaces the value of the atomic object
+  // and returns the value held previously.
+  // See also std::atomic<T>::exchange().
+  T exchange(T new_val) {
+    T old_value;
+    {
+      starboard::ScopedLock lock(mutex_);
+      old_value = value_;
+      value_ = new_val;
+    }
+    return old_value;
+  }
+
+  // Atomically obtains the value of the atomic object.
+  // See also std::atomic<T>::load().
+  T load() const {
+    starboard::ScopedLock lock(mutex_);
+    return value_;
+  }
+
+  // Stores the value. See std::atomic<T>::store(...)
+  void store(T val) {
+    starboard::ScopedLock lock(mutex_);
+    value_ = val;
+  }
+
+  // compare_exchange_strong(...) sets the new value if and only if
+  // *expected_value matches what is stored internally.
+  // If this succeeds then true is returned and *expected_value == new_value.
+  // Otherwise If there is a mismatch then false is returned and
+  // *expected_value is set to the internal value.
+  // Inputs:
+  //  new_value: Attempt to set the value to this new value.
+  //  expected_value: A test condition for success. If the actual value
+  //    matches the expected_value then the swap will succeed.
+  //
+  // See also std::atomic<T>::compare_exchange_strong(...).
+  bool compare_exchange_strong(T* expected_value, T new_value) {
+    // Save original value so that its local. This hints to the compiler
+    // that test_val doesn't have aliasing issues and should result in
+    // more optimal code inside of the lock.
+    const T test_val = *expected_value;
+    starboard::ScopedLock lock(mutex_);
+    if (test_val == value_) {
+      value_ = new_value;
+      return true;
+    } else {
+      *expected_value = value_;
+      return false;
+    }
+  }
+
+  // Weak version of this function is documented to be faster, but has allows
+  // weaker memory ordering and therefore will sometimes have a false negative:
+  // The value compared will actually be equal but the return value from this
+  // function indicates otherwise.
+  // By default, the function delegates to compare_exchange_strong(...).
+  //
+  // See also std::atomic<T>::compare_exchange_weak(...).
+  bool compare_exchange_weak(T* expected_value, T new_value) {
+    return compare_exchange_strong(expected_value, new_value);
+  }
+
+ protected:
+  T value_;
+  starboard::Mutex mutex_;
+};
+
+// A subclass of atomic<T> that adds fetch_add(...) and fetch_sub(...).
+template <typename T>
+class atomic_number : public atomic<T> {
+ public:
+  typedef atomic<T> Super;
+  typedef T ValueType;
+
+  atomic_number() : Super() {}
+  explicit atomic_number(T v) : Super(v) {}
+
+  // Returns the previous value before the input value was added.
+  // See also std::atomic<T>::fetch_add(...).
+  T fetch_add(T val) {
+    T old_val;
+    {
+      starboard::ScopedLock lock(this->mutex_);
+      old_val = this->value_;
+      this->value_ += val;
+    }
+    return old_val;
+  }
+
+  // Returns the value before the operation was applied.
+  // See also std::atomic<T>::fetch_sub(...).
+  T fetch_sub(T val) {
+    T old_val;
+    {
+      starboard::ScopedLock lock(this->mutex_);
+      old_val = this->value_;
+      this->value_ -= val;
+    }
+    return old_val;
+  }
+};
+
+// A subclass to classify Atomic Integers. Adds increment and decrement
+// functions.
+template <typename T>
+class atomic_integral : public atomic_number<T> {
+ public:
+  typedef atomic_number<T> Super;
+
+  atomic_integral() : Super() {}
+  explicit atomic_integral(T v) : Super(v) {}
+
+  T increment() { return this->fetch_add(T(1)); }
+  T decrement() { return this->fetch_sub(T(1)); }
+};
+
+// atomic_pointer class.
+template <typename T>
+class atomic_pointer : public atomic<T> {
+ public:
+  typedef atomic<T> Super;
+  atomic_pointer() : Super() {}
+  explicit atomic_pointer(T initial_val) : Super(initial_val) {}
+};
+
+// Simple atomic bool class. This could be optimized for speed using
+// compiler intrinsics for concurrent integer modification.
+class atomic_bool : public atomic<bool> {
+ public:
+  typedef atomic<bool> Super;
+  atomic_bool() : Super() {}
+  explicit atomic_bool(bool initial_val) : Super(initial_val) {}
+};
+
+// Simple atomic int class. This could be optimized for speed using
+// compiler intrinsics for concurrent integer modification.
+class atomic_int32_t : public atomic_integral<int32_t> {
+ public:
+  typedef atomic_integral<int32_t> Super;
+  atomic_int32_t() : Super() {}
+  explicit atomic_int32_t(int32_t initial_val) : Super(initial_val) {}
+};
+
+// Simple atomic int class. This could be optimized for speed using
+// compiler intrinsics for concurrent integer modification.
+class atomic_int64_t : public atomic_integral<int64_t> {
+ public:
+  typedef atomic_integral<int64_t> Super;
+  atomic_int64_t() : Super() {}
+  explicit atomic_int64_t(int64_t initial_val) : Super(initial_val) {}
+};
+
+class atomic_float : public atomic_number<float> {
+ public:
+  typedef atomic_number<float> Super;
+  atomic_float() : Super() {}
+  explicit atomic_float(float initial_val) : Super(initial_val) {}
+};
+
+class atomic_double : public atomic_number<double> {
+ public:
+  typedef atomic_number<double> Super;
+  atomic_double() : Super() {}
+  explicit atomic_double(double initial_val) : Super(initial_val) {}
+};
+
+}  // namespace nb
+
+#endif  // NB_ATOMIC_H_
diff --git a/src/nb/atomic_test.cc b/src/nb/atomic_test.cc
new file mode 100644
index 0000000..fc76703
--- /dev/null
+++ b/src/nb/atomic_test.cc
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nb/atomic.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "nb/test_thread.h"
+#include "starboard/configuration.h"
+#include "starboard/mutex.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace nb {
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+// Boilerplate for test setup.
+///////////////////////////////////////////////////////////////////////////////
+
+// Defines a typelist for all atomic types.
+typedef ::testing::Types<atomic_int32_t, atomic_int64_t,
+                         atomic_float, atomic_double,
+                         atomic_bool,
+                         atomic_pointer<int*> > AllAtomicTypes;
+
+// Defines a typelist for just atomic number types.
+typedef ::testing::Types<atomic_int32_t, atomic_int64_t,
+                         atomic_float, atomic_double> AtomicNumberTypes;
+
+// Defines test type that will be instantiated using each type in
+// AllAtomicTypes type list.
+template <typename T>

+class AtomicTest : public ::testing::Test {};
+TYPED_TEST_CASE(AtomicTest, AllAtomicTypes);  // Registration.
+
+// Defines test type that will be instantiated using each type in
+// AtomicNumberTypes type list.
+template <typename T>

+class AtomicNumberTest : public ::testing::Test {};
+TYPED_TEST_CASE(AtomicNumberTest, AtomicNumberTypes);  // Registration.
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Singlethreaded tests.
+///////////////////////////////////////////////////////////////////////////////
+
+// Tests default constructor and single-argument constructor.
+TYPED_TEST(AtomicTest, Constructors) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  const T zero(0);
+  const T one = zero + 1;  // Allows AtomicPointer<T*>.
+
+  AtomicT atomic_default;
+
+  // Tests that default value is zero.
+  ASSERT_EQ(atomic_default.load(), zero);
+  AtomicT atomic_val(one);
+  ASSERT_EQ(one, atomic_val.load());
+}
+
+// Tests load() and exchange().
+TYPED_TEST(AtomicTest, Load_Exchange_SingleThread) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  const T zero(0);
+  const T one = zero + 1;  // Allows AtomicPointer<T*>.
+
+  AtomicT atomic;
+  ASSERT_EQ(atomic.load(), zero);      // Default is 0.
+  ASSERT_EQ(zero, atomic.exchange(one));  // Old value was 0.
+
+  // Tests that AtomicType has  const get function.
+  const AtomicT& const_atomic = atomic;
+  ASSERT_EQ(one, const_atomic.load());
+}
+
+// Tests compare_exchange_strong().
+TYPED_TEST(AtomicNumberTest, CompareExchangeStrong_SingleThread) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  const T zero(0);
+  const T one = zero + 1;  // Allows AtomicPointer<T*>.
+
+  AtomicT atomic;
+  ASSERT_EQ(atomic.load(), zero); // Default is 0.
+  T expected_value = zero;
+  // Should succeed.
+  ASSERT_TRUE(atomic.compare_exchange_strong(&expected_value,
+                                             one));          // New value.
+
+  ASSERT_EQ(zero, expected_value);
+  ASSERT_EQ(one, atomic.load());  // Expect that value was set.
+
+  expected_value = zero;
+  // Asserts that when the expected and actual value is mismatched that the
+  // compare_exchange_strong() fails.
+  ASSERT_FALSE(atomic.compare_exchange_strong(&expected_value,  // Mismatched.
+                                              zero));           // New value.
+
+  // Failed and this means that expected_value should be set to what the
+  // internal value was. In this case, one.
+  ASSERT_EQ(expected_value, one);
+  ASSERT_EQ(one, atomic.load());
+
+  ASSERT_TRUE(atomic.compare_exchange_strong(&expected_value, // Matches.
+                                             zero));
+  ASSERT_EQ(expected_value, one);
+}
+
+// Tests atomic fetching and adding.
+TYPED_TEST(AtomicNumberTest, FetchAdd_SingleThread) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  const T zero(0);
+  const T one = zero + 1;  // Allows atomic_pointer<T*>.
+  const T two = zero + 2;
+
+  AtomicT atomic;
+  ASSERT_EQ(atomic.load(), zero);         // Default is 0.
+  ASSERT_EQ(zero, atomic.fetch_add(one)); // Prev value was 0.
+  ASSERT_EQ(one, atomic.load());          // Now value is this.
+  ASSERT_EQ(one, atomic.fetch_add(one));  // Prev value was 1.
+  ASSERT_EQ(two, atomic.exchange(one));   // Old value was 2.
+}
+
+// Tests atomic fetching and subtracting.
+TYPED_TEST(AtomicNumberTest, FetchSub_SingleThread) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  const T zero(0);
+  const T one = zero + 1;  // Allows AtomicPointer<T*>.
+  const T two = zero + 2;
+  const T neg_two(zero-2);
+
+  AtomicT atomic;
+  ASSERT_EQ(atomic.load(), zero);            // Default is 0.
+  atomic.exchange(two);
+  ASSERT_EQ(two, atomic.fetch_sub(one));     // Prev value was 2.
+  ASSERT_EQ(one, atomic.load());             // New value.
+  ASSERT_EQ(one, atomic.fetch_sub(one));     // Prev value was one.
+  ASSERT_EQ(zero, atomic.load());            // New 0.
+  ASSERT_EQ(zero, atomic.fetch_sub(two));
+  ASSERT_EQ(neg_two, atomic.load());         // 0-2 = -2
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Multithreaded tests.
+///////////////////////////////////////////////////////////////////////////////
+
+// A thread that will execute compare_exhange_strong() and write out a result
+// to a shared output.
+template <typename AtomicT>
+class CompareExchangeThread : public TestThread {
+ public:
+  typedef typename AtomicT::ValueType T;
+  CompareExchangeThread(int start_num,
+                        int end_num,
+                        AtomicT* atomic_value,
+                        std::vector<T>* output,
+                        starboard::Mutex* output_mutex)
+      : start_num_(start_num), end_num_(end_num),
+        atomic_value_(atomic_value), output_(output),
+        output_mutex_(output_mutex) {
+  }
+
+  virtual void Run() {
+    std::vector<T> output_buffer;
+    output_buffer.reserve(end_num_ - start_num_);
+    for (int i = start_num_; i < end_num_; ++i) {
+      T new_value = T(i);
+      while (true) {
+        if (std::rand() % 3 == 0) {
+          // 1 in 3 chance of yeilding.
+          // Attempt to cause more contention by giving other threads a chance
+          // to run.
+          SbThreadYield();
+        }
+
+        const T prev_value = atomic_value_->load();
+        T expected_value = prev_value;
+        const bool success =
+            atomic_value_->compare_exchange_strong(&expected_value,
+                                                   new_value);
+        if (success) {
+          output_buffer.push_back(prev_value);
+          break;
+        }
+      }
+    }
+
+    // Lock the output to append this output buffer.
+    starboard::ScopedLock lock(*output_mutex_);
+    output_->insert(output_->end(),
+                    output_buffer.begin(),
+                    output_buffer.end());
+  }
+ private:
+  const int start_num_;
+  const int end_num_;
+  AtomicT*const atomic_value_;
+  std::vector<T>*const output_;
+  starboard::Mutex*const output_mutex_;
+};
+
+// Tests Atomic<T>::compare_exchange_strong(). Each thread has a unique
+// sequential range [0,1,2,3 ... ), [5,6,8, ...) that it will generate.
+// The numbers are sequentially written to the shared Atomic type and then
+// exposed to other threads:
+//
+//    Generates [0,1,2,...,n/2)

+//   +------+ Thread A <--------+        (Write Exchanged Value)

+//   |                          |

+//   |    compare_exchange()    +---> exchanged? ---+

+//   +----> +------------+ +----+                   v

+//          | AtomicType |                   Output vector

+//   +----> +------------+ +----+                   ^

+//   |    compare_exchange()    +---> exchanged? ---+

+//   |                          |

+//   +------+ Thread B <--------+

+//    Generates [n/2,n/2+1,...,n)

+//
+// By repeatedly calling atomic<T>::compare_exchange_strong() by each of the
+// threads, each will see the previous value of the shared variable when their
+// exchange (atomic swap) operation is successful. If all of the swapped out
+// values are recombined then it will form the original generated sequence from
+// all threads.
+//
+//            TEST PHASE

+//  sort( output vector ) AND TEST THAT

+//  output vector Contains [0,1,2,...,n)
+//
+// The test passes when the output array is tested that it contains every
+// expected generated number from all threads. If compare_exchange_strong() is
+// written incorrectly for an atomic type then the output array will have
+// duplicates or otherwise not be equal to the expected natural number set.
+TYPED_TEST(AtomicNumberTest, Test_CompareExchange_MultiThreaded) {
+  typedef TypeParam AtomicT;
+  typedef typename AtomicT::ValueType T;
+
+  static const int kNumElements = 1000;
+  static const int kNumThreads = 4;
+
+  AtomicT atomic_value(T(-1));
+  std::vector<TestThread*> threads;
+  std::vector<T> output_values;
+  starboard::Mutex output_mutex;
+
+  for (int i = 0; i < kNumThreads; ++i) {
+    const int start_num = (kNumElements * i) / kNumThreads;
+    const int end_num = (kNumElements * (i + 1)) / kNumThreads;
+    threads.push_back(
+        new CompareExchangeThread<AtomicT>(
+            start_num,  // defines the number range to generate.
+            end_num,
+            &atomic_value,
+            &output_values,
+            &output_mutex));
+  }
+
+  // These threads will generate unique numbers in their range and then
+  // write them to the output array.
+  for (int i = 0; i < kNumThreads; ++i) {
+    threads[i]->Start();
+  }
+
+  for (int i = 0; i < kNumThreads; ++i) {
+    threads[i]->Join();
+  }
+  // Cleanup threads.
+  for (int i = 0; i < kNumThreads; ++i) {
+    delete threads[i];
+  }
+  threads.clear();
+
+  // Final value needs to be written out. The last thread to join doesn't
+  // know it's the last and therefore the final value in the shared
+  // has not be pushed to the output array.
+  output_values.push_back(atomic_value.load());
+  std::sort(output_values.begin(), output_values.end());
+
+  // We expect the -1 value because it was the explicit initial value of the
+  // shared atomic.
+  ASSERT_EQ(T(-1), output_values[0]);
+  ASSERT_EQ(T(0), output_values[1]);
+  output_values.erase(output_values.begin());  // Chop off the -1 at the front.
+
+  // Finally, assert that the output array is equal to the natural numbers
+  // after it has been sorted.
+  ASSERT_EQ(output_values.size(), kNumElements);
+  // All of the elements should be equal too.
+  for (int i = 0; i < output_values.size(); ++i) {
+    ASSERT_EQ(output_values[i], T(i));
+  }
+}
+
+}  // namespace
+}  // namespace nb
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp
index 3780eb9..f87d42a 100644
--- a/src/nb/nb.gyp
+++ b/src/nb/nb.gyp
@@ -24,6 +24,7 @@
         'allocator.h',
         'allocator_decorator.cc',
         'allocator_decorator.h',
+        'atomic.h',
         'fixed_no_free_allocator.cc',
         'fixed_no_free_allocator.h',
         'memory_pool.cc',
@@ -38,6 +39,7 @@
         'scoped_ptr.h',
         'thread_collision_warner.cc',
         'thread_collision_warner.h',
+        'thread_local_object.h',
       ],
 
       'dependencies': [
@@ -60,9 +62,11 @@
       'target_name': 'nb_test',
       'type': '<(gtest_target_type)',
       'sources': [
+        'atomic_test.cc',
         'fixed_no_free_allocator_test.cc',
         'reuse_allocator_test.cc',
         'run_all_unittests.cc',
+        'test_thread.h',
         'thread_local_object_test.cc',
       ],
       'dependencies': [
diff --git a/src/nb/test_thread.h b/src/nb/test_thread.h
new file mode 100644
index 0000000..0ea5768
--- /dev/null
+++ b/src/nb/test_thread.h
@@ -0,0 +1,73 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NB_TEST_THREAD_H_
+#define NB_TEST_THREAD_H_
+
+#include "starboard/configuration.h"
+#include "starboard/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace nb {
+
+// TestThread that is a bare bones class wrapper around Starboard
+// thread. Subclasses must override Run().
+class TestThread {
+ public:
+  TestThread() : thread_(kSbThreadInvalid) {}
+  virtual ~TestThread() {}
+
+  // Subclasses should override the Run method.
+  virtual void Run() = 0;
+
+  // Calls SbThreadCreate() with default parameters.
+  void Start() {
+    SbThreadEntryPoint entry_point = ThreadEntryPoint;
+
+    thread_ = SbThreadCreate(
+        0,                     // default stack_size.
+        kSbThreadNoPriority,   // default priority.
+        kSbThreadNoAffinity,   // default affinity.
+        true,                  // joinable.
+        "TestThread",
+        entry_point,
+        this);
+
+    if (kSbThreadInvalid == thread_) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
+    }
+    return;
+  }
+
+  void Join() {
+    if (!SbThreadJoin(thread_, NULL)) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread.";
+    }
+  }
+
+ private:
+  static void* ThreadEntryPoint(void* ptr) {
+    TestThread* this_ptr = static_cast<TestThread*>(ptr);
+    this_ptr->Run();
+    return NULL;
+  }
+
+  SbThread thread_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(TestThread);
+};
+
+}  // namespace nb.
+
+#endif  // NB_TEST_THREAD_H_
diff --git a/src/nb/thread_local_object.h b/src/nb/thread_local_object.h
index 4b6a7ab..a303f5a 100644
--- a/src/nb/thread_local_object.h
+++ b/src/nb/thread_local_object.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef BASE_THREADING_THREAD_LOCAL_OBJECT_H_
-#define BASE_THREADING_THREAD_LOCAL_OBJECT_H_
+#ifndef NB_THREAD_LOCAL_OBJECT_H_
+#define NB_THREAD_LOCAL_OBJECT_H_
 
 #include <set>
 
@@ -24,7 +24,7 @@
 #include "starboard/mutex.h"
 #include "starboard/thread.h"
 
-namespace base {
+namespace nb {
 
 // Like base::ThreadLocalPointer<T> but destroys objects. This is important
 // for using ThreadLocalObjects that aren't tied to a singleton, or for
@@ -75,12 +75,14 @@
   // Thread Local Objects are destroyed after this call.
   ~ThreadLocalObject() {
     CheckCurrentThreadAllowedToDestruct();
-    SB_DCHECK(entry_set_.size() < 2)
+    if (SB_DLOG_IS_ON(FATAL)) {
+      SB_DCHECK(entry_set_.size() < 2)
         << "Logic error: Some threads may still be accessing the objects that "
         << "are about to be destroyed. Only one object is expected and that "
         << "should be for the main thread. The caller should ensure that "
         << "other threads that access this object are externally "
         << "synchronized.";
+    }
     // No locking is done because the entries should not be accessed by
     // different threads while this object is shutting down. If access is
     // occuring then the caller has a race condition, external to this class.
@@ -209,6 +211,6 @@
   SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>);
 };
 
-}  // namespace base
+}  // namespace nb
 
-#endif  // BASE_THREADING_THREAD_LOCAL_H_
+#endif  // NB_THREAD_LOCAL_OBJECT_H_
diff --git a/src/nb/thread_local_object_test.cc b/src/nb/thread_local_object_test.cc
index 000c6f4..9fce6d6 100644
--- a/src/nb/thread_local_object_test.cc
+++ b/src/nb/thread_local_object_test.cc
@@ -15,172 +15,31 @@
 #include <map>
 #include <string>
 
+#include "nb/test_thread.h"
+#include "nb/atomic.h"
 #include "nb/thread_local_object.h"
 #include "nb/scoped_ptr.h"
 #include "starboard/mutex.h"
 #include "starboard/thread.h"
-
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace base {
+namespace nb {
 namespace {
 
-// Similar to C++11 std::atomic<T>.
-// Atomic<T> may be instantiated with any TriviallyCopyable type T.
-// Atomic<T> is neither copyable nor movable.
-// TODO: Lift this class out into the library.
-template <typename T>
-class Atomic {
- public:
-  // C++11 forbids a copy constructor for std::atomic<T>, it also forbids
-  // a move operation.
-  Atomic() : value_() {}
-  explicit Atomic(T v) : value_(v) {}
-
-  // Checks whether the atomic operations on all objects of this type
-  // are lock-free.
-  // Returns true if the atomic operations on the objects of this type
-  // are lock-free, false otherwise.
-  //
-  // All atomic types may be implemented using mutexes or other locking
-  // operations, rather than using the lock-free atomic CPU instructions.
-  // Atomic types are also allowed to be sometimes lock-free, e.g. if only
-  // aligned memory accesses are naturally atomic on a given architecture,
-  // misaligned objects of the same type have to use locks.
-  bool is_lock_free() const { return false; }
-  bool is_lock_free() const volatile { return false; }
-
-  // Atomically replaces the value of the atomic object
-  // and returns the value held previously.
-  T Swap(T new_val) {
-    int old_value = -1;
-    {
-      starboard::ScopedLock lock(mutex_);
-      old_value = value_;
-      value_ = new_val;
-    }
-    return old_value;
-  }
-
-  // Atomically obtains the value of the atomic object.
-  T Get() const {
-    starboard::ScopedLock lock(mutex_);
-    return value_;
-  }
-
-  // Returns the new updated value after the operation has been applied.
-  T Add(T val) {
-    starboard::ScopedLock lock(mutex_);
-    value_ += val;
-    return value_;
-  }
-
-  // TrySwap(...) sets the new value if and only if "expected_old_value"
-  // matches the actual value during the atomic assignment operation. If this
-  // succeeds then true is returned. If there is a mismatch then the value is
-  // left unchanged and false is returned.
-  // Inputs:
-  //  new_value: Attempt to set the value to this new value.
-  //  expected_old_value: A test condition for success. If the actual value
-  //    matches the expected_old_value then the swap will succeed.
-  //  optional_actual_value: If non-null, then the actual value at the time
-  //    of the attempted operation is set to this value.
-  bool TrySwap(T new_value, T expected_old_value,
-               T* optional_actual_value) {
-    starboard::ScopedLock lock(mutex_);
-    if (optional_actual_value) {
-      *optional_actual_value = value_;
-    }
-    if (expected_old_value == value_) {
-      value_ = new_value;
-      return true;
-    }
-    return false;
-  }
-
- private:
-  T value_;
-  starboard::Mutex mutex_;
-};
-
-// Simple atomic int class. This could be optimized for speed using
-// compiler intrinsics for concurrent integer modification.
-class AtomicInt : public Atomic<int> {
- public:
-  AtomicInt() : Atomic<int>(0) {}
-  explicit AtomicInt(int initial_val) : Atomic<int>(initial_val) {}
-  void Increment() { Add(1); }
-  void Decrement() { Add(-1); }
-};
-
-// Simple atomic bool class. This could be optimized for speed using
-// compiler intrinsics for concurrent integer modification.
-class AtomicBool : public Atomic<bool>  {
- public:
-  AtomicBool() : Atomic<bool>(false) {}
-  explicit AtomicBool(bool initial_val) : Atomic<bool>(initial_val) {}
-};
-
-// AbstractTestThread that is a bare bones class wrapper around Starboard
-// thread. Subclasses must override Run().
-// TODO: Move this to nplb/thread_helpers.h
-class AbstractTestThread {
- public:
-  explicit AbstractTestThread() : thread_(kSbThreadInvalid) {}
-  virtual ~AbstractTestThread() {}
-
-  // Subclasses should override the Run method.
-  virtual void Run() = 0;
-
-  // Calls SbThreadCreate() with default parameters.
-  void Start() {
-    SbThreadEntryPoint entry_point = ThreadEntryPoint;
-
-    thread_ = SbThreadCreate(
-        0,                     // default stack_size.
-        kSbThreadNoPriority,   // default priority.
-        kSbThreadNoAffinity,   // default affinity.
-        true,                  // joinable.
-        "AbstractTestThread",
-        entry_point,
-        this);
-
-    if (kSbThreadInvalid == thread_) {
-      ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread.";
-    }
-    return;
-  }
-
-  void Join() {
-    if (!SbThreadJoin(thread_, NULL)) {
-      ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread.";
-    }
-  }
-
- private:
-  static void* ThreadEntryPoint(void* ptr) {
-    AbstractTestThread* this_ptr = static_cast<AbstractTestThread*>(ptr);
-    this_ptr->Run();
-    return NULL;
-  }
-
-  SbThread thread_;
-};
-
 // Simple class that counts the number of instances alive.
 struct CountsInstances {
-  CountsInstances() { s_instances_.Increment(); }
-  ~CountsInstances() { s_instances_.Decrement(); }
-  static AtomicInt s_instances_;
-  static int NumInstances() { return s_instances_.Get(); }
-  static void ResetNumInstances() { s_instances_.Swap(0); }
+  CountsInstances() { s_instances_.increment(); }
+  ~CountsInstances() { s_instances_.decrement(); }
+  static atomic_int32_t s_instances_;
+  static int NumInstances() { return s_instances_.load(); }
+  static void ResetNumInstances() { s_instances_.exchange(0); }
 };
-AtomicInt CountsInstances::s_instances_(0);
+nb::atomic_int32_t CountsInstances::s_instances_(0);
 
 // A simple thread that just creates the an object from the supplied
 // ThreadLocalObject<T> and then exits.
 template <typename TYPE>
-class CreateThreadLocalObjectThenExit : public AbstractTestThread {
+class CreateThreadLocalObjectThenExit : public TestThread {
  public:
   explicit CreateThreadLocalObjectThenExit(
       ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {}
@@ -196,7 +55,7 @@
 // A simple thread that just deletes the object supplied on a thread and then
 // exists.
 template <typename TYPE>
-class DestroyTypeOnThread : public AbstractTestThread {
+class DestroyTypeOnThread : public TestThread {
  public:
   explicit DestroyTypeOnThread(TYPE* ptr)
       : ptr_(ptr) {}
@@ -285,7 +144,7 @@
 
   nb::scoped_ptr<TLO> tlo(new TLO);
   {
-    AbstractTestThread* thread =
+    TestThread* thread =
         new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get());
     thread->Start();
     thread->Join();
@@ -318,7 +177,7 @@
 
   // Thread will simply create the thread local object (CountsInstances)
   // and then return.
-  nb::scoped_ptr<AbstractTestThread> thread_ptr(
+  nb::scoped_ptr<TestThread> thread_ptr(
         new CreateThreadLocalObjectThenExit<CountsInstances>(tlo));
   thread_ptr->Start();  // Object is now created.
   thread_ptr->Join();   // ...then destroyed.
@@ -342,5 +201,4 @@
 }
 
 }  // anonymous namespace
-}  // namespace base
-
+}  // nb namespace
diff --git a/src/net/base/net_util.cc b/src/net/base/net_util.cc
index a5c6c64..7b406ba 100644
--- a/src/net/base/net_util.cc
+++ b/src/net/base/net_util.cc
@@ -63,6 +63,7 @@
 #include "net/base/escape.h"
 #include "net/base/mime_util.h"
 #include "net/base/net_module.h"
+
 #if defined(OS_WIN)
 #include "net/base/winsock_init.h"
 #endif
@@ -1870,7 +1871,16 @@
 // TODO(jar): The following is a simple estimate of IPv6 support.  We may need
 // to do a test resolution, and a test connection, to REALLY verify support.
 IPv6SupportResult TestIPv6SupportInternal() {
-#if defined(OS_ANDROID) || defined(__LB_ANDROID__)
+#if defined(OS_STARBOARD)
+  SbSocket test_socket =
+      SbSocketCreate(kSbSocketAddressTypeIpv6, kSbSocketProtocolTcp);
+  if (!SbSocketIsValid(test_socket)) {
+    return IPv6SupportResult(false, IPV6_CANNOT_CREATE_SOCKETS,
+                             SbSystemGetLastError());
+  }
+  SbSocketDestroy(test_socket);
+  return IPv6SupportResult(true, IPV6_SUPPORT_MAX, 0);
+#elif defined(OS_ANDROID) || defined(__LB_ANDROID__)
   // TODO: We should fully implement IPv6 probe once 'getifaddrs' API available;
   // Another approach is implementing the similar feature by
   // java.net.NetworkInterface through JNI.
diff --git a/src/net/base/net_util_unittest.cc b/src/net/base/net_util_unittest.cc
index 8ec69ad..d6102b2 100644
--- a/src/net/base/net_util_unittest.cc
+++ b/src/net/base/net_util_unittest.cc
@@ -600,7 +600,7 @@
          "%E9%A1%B5.doc"},
     {L"D:\\plane1\\\xD835\xDC00\xD835\xDC01.txt",  // Math alphabet "AB"
      "file:///D:/plane1/%F0%9D%90%80%F0%9D%90%81.txt"},
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_STARBOARD)
     {L"/foo/bar.txt", "file:///foo/bar.txt"},
     {L"/foo/BAR.txt", "file:///foo/BAR.txt"},
     {L"/C:/foo/bar.txt", "file:///C:/foo/bar.txt"},
@@ -647,7 +647,7 @@
     {L"\\\\foo\\bar.txt", "file:/foo/bar.txt"},
     {L"\\\\foo\\bar.txt", "file://foo\\bar.txt"},
     {L"C:\\foo\\bar.txt", "file:\\\\\\c:/foo/bar.txt"},
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_STARBOARD)
     {L"/c:/foo/bar.txt", "file:/c:/foo/bar.txt"},
     {L"/c:/foo/bar.txt", "file:///c:/foo/bar.txt"},
     {L"/foo/bar.txt", "file:/foo/bar.txt"},
diff --git a/src/net/http/http_pipelined_host_pool.h b/src/net/http/http_pipelined_host_pool.h
index 7a0a678..1f86618 100644
--- a/src/net/http/http_pipelined_host_pool.h
+++ b/src/net/http/http_pipelined_host_pool.h
@@ -81,7 +81,7 @@
   base::Value* PipelineInfoToValue() const;
 
  private:
-#if defined(__LB_SHELL__)
+#if defined(__LB_SHELL__) || defined(OS_STARBOARD)
   typedef std::map<HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap;
 #else
   typedef std::map<const HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap;
diff --git a/src/net/spdy/spdy_protocol.h b/src/net/spdy/spdy_protocol.h
index 30daee3..ab3bf3d 100644
--- a/src/net/spdy/spdy_protocol.h
+++ b/src/net/spdy/spdy_protocol.h
@@ -573,7 +573,7 @@
   void set_flags(uint8 flags) { frame_->flags_length_.flags_[0] = flags; }
 
   uint32 length() const {
-    return ntohl(frame_->flags_length_.length_) & kLengthMask;
+    return base::NetToHost32(frame_->flags_length_.length_) & kLengthMask;
   }
 
   void set_length(uint32 length) {
@@ -583,13 +583,13 @@
     | Flags (8)  |  Length (24 bits)   |
     +----------------------------------+
     */
-    frame_->flags_length_.length_ = htonl((length & kLengthMask)
-                                          | (flags() << 24));
+    frame_->flags_length_.length_ =
+        base::HostToNet32((length & kLengthMask) | (flags() << 24));
   }
 
   bool is_control_frame() const {
-    return (ntohs(frame_->control_.version_) & kControlFlagMask) ==
-        kControlFlagMask;
+    return (base::NetToHost16(frame_->control_.version_) & kControlFlagMask) ==
+           kControlFlagMask;
   }
 
   // The size of the SpdyFrameBlock structure.
@@ -614,7 +614,7 @@
       : SpdyFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(frame_->data_.stream_id_) & kStreamIdMask;
+    return base::NetToHost32(frame_->data_.stream_id_) & kStreamIdMask;
   }
 
   // Note that setting the stream id sets the control bit to false.
@@ -622,7 +622,7 @@
   // should always be set correctly.
   void set_stream_id(SpdyStreamId id) {
     DCHECK_EQ(0u, (id & ~kStreamIdMask));
-    frame_->data_.stream_id_ = htonl(id & kStreamIdMask);
+    frame_->data_.stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   // Returns the size of the SpdyFrameBlock structure.
@@ -648,7 +648,7 @@
   // frame.  Does not guarantee that there are no errors.
   bool AppearsToBeAValidControlFrame() const {
     // Right now we only check if the frame has an out-of-bounds type.
-    uint16 type = ntohs(block()->control_.type_);
+    uint16 type = base::NetToHost16(block()->control_.type_);
     // NOOP is not a 'valid' control frame in SPDY/3 and beyond.
     return type >= SYN_STREAM &&
         type < NUM_CONTROL_FRAME_TYPES &&
@@ -657,8 +657,8 @@
 
   uint16 version() const {
     const int kVersionMask = 0x7fff;
-    return static_cast<uint16>(
-        ntohs((block()->control_.version_)) & kVersionMask);
+    return static_cast<uint16>(base::NetToHost16((block()->control_.version_)) &
+                               kVersionMask);
   }
 
   void set_version(uint16 version) {
@@ -668,11 +668,12 @@
     +----------------------------------+
     */
     DCHECK_EQ(0U, version & kControlFlagMask);
-    mutable_block()->control_.version_ = htons(kControlFlagMask | version);
+    mutable_block()->control_.version_ =
+        base::HostToNet16(kControlFlagMask | version);
   }
 
   SpdyControlType type() const {
-    uint16 type = ntohs(block()->control_.type_);
+    uint16 type = base::NetToHost16(block()->control_.type_);
     LOG_IF(DFATAL, type < SYN_STREAM || type >= NUM_CONTROL_FRAME_TYPES)
         << "Invalid control frame type " << type;
     return static_cast<SpdyControlType>(type);
@@ -680,7 +681,8 @@
 
   void set_type(SpdyControlType type) {
     DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
-    mutable_block()->control_.type_ = htons(static_cast<uint16>(type));
+    mutable_block()->control_.type_ =
+        base::HostToNet16(static_cast<uint16>(type));
   }
 
   // Returns true if this control frame is of a type that has a header block,
@@ -707,19 +709,20 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(block()->stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->stream_id_) & kStreamIdMask;
   }
 
   void set_stream_id(SpdyStreamId id) {
-    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   SpdyStreamId associated_stream_id() const {
-    return ntohl(block()->associated_stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->associated_stream_id_) & kStreamIdMask;
   }
 
   void set_associated_stream_id(SpdyStreamId id) {
-    mutable_block()->associated_stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->associated_stream_id_ =
+        base::HostToNet32(id & kStreamIdMask);
   }
 
   SpdyPriority priority() const {
@@ -774,11 +777,11 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(block()->stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->stream_id_) & kStreamIdMask;
   }
 
   void set_stream_id(SpdyStreamId id) {
-    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   int header_block_len() const {
@@ -821,23 +824,23 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(block()->stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->stream_id_) & kStreamIdMask;
   }
 
   void set_stream_id(SpdyStreamId id) {
-    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   SpdyStatusCodes status() const {
     SpdyStatusCodes status =
-        static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+        static_cast<SpdyStatusCodes>(base::NetToHost32(block()->status_));
     if (status < INVALID || status >= NUM_STATUS_CODES) {
       status = INVALID;
     }
     return status;
   }
   void set_status(SpdyStatusCodes status) {
-    mutable_block()->status_ = htonl(static_cast<uint32>(status));
+    mutable_block()->status_ = base::HostToNet32(static_cast<uint32>(status));
   }
 
   // Returns the size of the SpdyRstStreamControlFrameBlock structure.
@@ -861,11 +864,11 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   uint32 num_entries() const {
-    return ntohl(block()->num_entries_);
+    return base::NetToHost32(block()->num_entries_);
   }
 
   void set_num_entries(int val) {
-    mutable_block()->num_entries_ = htonl(static_cast<uint32>(val));
+    mutable_block()->num_entries_ = base::HostToNet32(static_cast<uint32>(val));
   }
 
   int header_block_len() const {
@@ -896,12 +899,10 @@
   SpdyPingControlFrame(char* data, bool owns_buffer)
       : SpdyControlFrame(data, owns_buffer) {}
 
-  uint32 unique_id() const {
-    return ntohl(block()->unique_id_);
-  }
+  uint32 unique_id() const { return base::NetToHost32(block()->unique_id_); }
 
   void set_unique_id(uint32 unique_id) {
-    mutable_block()->unique_id_ = htonl(unique_id);
+    mutable_block()->unique_id_ = base::HostToNet32(unique_id);
   }
 
   static size_t size() { return sizeof(SpdyPingControlFrameBlock); }
@@ -941,7 +942,7 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId last_accepted_stream_id() const {
-    return ntohl(block()->last_accepted_stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->last_accepted_stream_id_) & kStreamIdMask;
   }
 
   SpdyGoAwayStatus status() const {
@@ -949,7 +950,7 @@
       LOG(DFATAL) << "Attempted to access status of SPDY 2 GOAWAY.";
       return GOAWAY_INVALID;
     } else {
-      uint32 status = ntohl(block()->status_);
+      uint32 status = base::NetToHost32(block()->status_);
       if (status >= GOAWAY_NUM_STATUS_CODES) {
         return GOAWAY_INVALID;
       } else {
@@ -959,7 +960,8 @@
   }
 
   void set_last_accepted_stream_id(SpdyStreamId id) {
-    mutable_block()->last_accepted_stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->last_accepted_stream_id_ =
+        base::HostToNet32(id & kStreamIdMask);
   }
 
   static size_t size() { return sizeof(SpdyGoAwayControlFrameBlock); }
@@ -982,11 +984,11 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(block()->stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->stream_id_) & kStreamIdMask;
   }
 
   void set_stream_id(SpdyStreamId id) {
-    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   // The number of bytes in the header block beyond the frame header length.
@@ -1030,19 +1032,19 @@
       : SpdyControlFrame(data, owns_buffer) {}
 
   SpdyStreamId stream_id() const {
-    return ntohl(block()->stream_id_) & kStreamIdMask;
+    return base::NetToHost32(block()->stream_id_) & kStreamIdMask;
   }
 
   void set_stream_id(SpdyStreamId id) {
-    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+    mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask);
   }
 
   uint32 delta_window_size() const {
-    return ntohl(block()->delta_window_size_);
+    return base::NetToHost32(block()->delta_window_size_);
   }
 
   void set_delta_window_size(uint32 delta_window_size) {
-    mutable_block()->delta_window_size_ = htonl(delta_window_size);
+    mutable_block()->delta_window_size_ = base::HostToNet32(delta_window_size);
   }
 
   // Returns the size of the SpdyWindowUpdateControlFrameBlock structure.
diff --git a/src/starboard/audio_sink.h b/src/starboard/audio_sink.h
index 59f507b..790741a 100644
--- a/src/starboard/audio_sink.h
+++ b/src/starboard/audio_sink.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// An interface to output raw audio data.
+// Module Overview: Starboard Audio Sink API
+//
+// Provides an interface to output raw audio data.
 
 #ifndef STARBOARD_AUDIO_SINK_H_
 #define STARBOARD_AUDIO_SINK_H_
@@ -69,37 +71,49 @@
 
 // --- Functions -------------------------------------------------------------
 
-// Returns whether the given audio sink handle is valid.
+// Indicates whether the given audio sink handle is valid.
+//
+// |audio_sink|: The audio sink handle to check.
 SB_EXPORT bool SbAudioSinkIsValid(SbAudioSink audio_sink);
 
 // Creates an audio sink for the specified |channels| and
-// |sampling_frequency_hz|, acquiring all resources needed to operate it, and
-// returning an opaque handle to it.
-//
-// |frame_buffers| is an array of pointers to sample data.  If the sink is
-// operating in interleaved mode, the array contains only one element, which is
-// an array containing |frames_per_channel| * |channels| samples.  If the sink
-// is operating in planar mode, the number of elements in the array will be the
-// same as |channels|, each of the elements will be an array of
-// |frames_per_channel| samples.  The caller has to ensure that |frame_buffers|
-// is valid until SbAudioSinkDestroy is called.
-//
-// |update_source_status_func| cannot be NULL.  The audio sink will call it on
-// an internal thread to query the status of the source.
-//
-// |consume_frames_func| cannot be NULL.  The audio sink will call it on an
-// internal thread to report consumed frames.
-//
-// |context| will be passed back into all callbacks, and is generally used to
-// point at a class or struct that contains state associated with the audio
-// sink.
-//
-// The audio sink will start to call |update_source_status_func| immediately
-// after SbAudioSinkCreate is called, even before it returns.  The caller has
-// to ensure that the above callbacks returns meaningful values in this case.
+// |sampling_frequency_hz|, acquires all resources needed to operate the
+// audio sink, and returns an opaque handle to the audio sink.
 //
 // If the particular platform doesn't support the requested audio sink, the
 // function returns kSbAudioSinkInvalid without calling any of the callbacks.
+//
+// |channels|: The number of audio channels, such as left and right channels
+// in stereo audio.
+// |sampling_frequency_hz|: The sample frequency of the audio data being
+// streamed. For example, 22,000 Hz means 22,000 sample elements represents
+// one second of audio data.
+// |audio_sample_type|: The type of each sample of the audio data --
+// |int16|, |float32|, etc.
+// |audio_frame_storage_type|: Indicates whether frames are interleaved or
+// planar.
+// |frame_buffers|: An array of pointers to sample data.
+// - If the sink is operating in interleaved mode, the array contains only
+//   one element, which is an array containing (|frames_per_channel| *
+//   |channels|) samples.
+// - If the sink is operating in planar mode, the number of elements in the
+//   array is the same as |channels|, and each element is an array of
+//   |frames_per_channel| samples. The caller has to ensure that
+//   |frame_buffers| is valid until SbAudioSinkDestroy is called.
+// |frames_per_channel|: The size of the frame buffers, in units of the
+// number of samples per channel. The frame, in this case, represents a
+// group of samples at the same media time, one for each channel.
+// |update_source_status_func|: The audio sink calls this function on an
+// internal thread to query the status of the source. The value cannot be NULL.
+// |consume_frames_func|: The audio sink calls this function on an internal
+// thread to report consumed frames. The value cannot be NULL.
+// |context|: A value that is passed back to all callbacks and is generally
+// used to point at a class or struct that contains state associated with the
+// audio sink.
+// |update_source_status_func|: A function that the audio sink starts to call
+// immediately after SbAudioSinkCreate is called, even before it returns.
+// The caller has to ensure that the callback functions above return
+// meaningful values in this case.
 SB_EXPORT SbAudioSink
 SbAudioSinkCreate(int channels,
                   int sampling_frequency_hz,
@@ -111,32 +125,36 @@
                   SbAudioSinkConsumeFramesFunc consume_frames_func,
                   void* context);
 
-// Destroys |audio_sink|, freeing all associated resources.  It will wait until
-// all callbacks in progress is finished before returning.  Upon returning of
-// this function, no callbacks passed into SbAudioSinkCreate will be called
-// further.  This function can be called on any thread but cannot be called
+// Destroys |audio_sink|, freeing all associated resources. Before
+// returning, the function waits until all callbacks that are in progress
+// have finished. After the function returns, no further calls are made
+// callbacks passed into SbAudioSinkCreate. In addition, you can not pass
+// |audio_sink| to any other SbAudioSink functions after SbAudioSinkDestroy
+// has been called on it.
+//
+// This function can be called on any thread. However, it cannot be called
 // within any of the callbacks passed into SbAudioSinkCreate.
-// It is not allowed to pass |audio_sink| into any other SbAudioSink function
-// once SbAudioSinkDestroy has been called on it.
+//
+// |audio_sink|: The audio sink to destroy.
 SB_EXPORT void SbAudioSinkDestroy(SbAudioSink audio_sink);
 
-// Returns the maximum channel supported on the platform.
+// Returns the maximum number of channels supported on the platform. For
+// example, the number would be |2| if the platform only supports stereo.
 SB_EXPORT int SbAudioSinkGetMaxChannels();
 
-// Returns the nearest supported sample rate of |sampling_frequency_hz|.  On
-// platforms that don't support all sample rates, it is the caller's
+// Returns the supported sample rate closest to |sampling_frequency_hz|.
+// On platforms that don't support all sample rates, it is the caller's
 // responsibility to resample the audio frames into the supported sample rate
 // returned by this function.
 SB_EXPORT int SbAudioSinkGetNearestSupportedSampleFrequency(
     int sampling_frequency_hz);
 
-// Returns true if the particular SbMediaAudioSampleType is supported on this
-// platform.
+// Indicates whether |audio_sample_type| is supported on this platform.
 SB_EXPORT bool SbAudioSinkIsAudioSampleTypeSupported(
     SbMediaAudioSampleType audio_sample_type);
 
-// Returns true if the particular SbMediaAudioFrameStorageType is supported on
-// this platform.
+// Indicates whether |audio_frame_storage_type| is supported on this
+// platform.
 SB_EXPORT bool SbAudioSinkIsAudioFrameStorageTypeSupported(
     SbMediaAudioFrameStorageType audio_frame_storage_type);
 
diff --git a/src/starboard/client_porting/eztime/eztime.gyp b/src/starboard/client_porting/eztime/eztime.gyp
index 009c29f..70f51e6 100644
--- a/src/starboard/client_porting/eztime/eztime.gyp
+++ b/src/starboard/client_porting/eztime/eztime.gyp
@@ -32,9 +32,9 @@
       'target_name': 'eztime_test',
       'type': '<(gtest_target_type)',
       'sources': [
+        '<(DEPTH)/starboard/common/test_main.cc',
         'test_constants.h',
         'eztime_test.cc',
-        '<(DEPTH)/starboard/nplb/main.cc',
       ],
       'dependencies': [
         '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/common/common.cc
similarity index 65%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/common/common.cc
index ec9eedf..86c63c2 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/common/common.cc
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#include "starboard/configuration.h"
 
-#include "starboard/linux/shared/thread_types_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+// This audit is here so it is only displayed once every build.
+#if SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION
+#pragma message "Your platform's SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION."
+#pragma message "You are implementing the experimental SB API at your own risk!"
+#endif
diff --git a/src/starboard/common/common.gyp b/src/starboard/common/common.gyp
index 0503988..e354d5e 100644
--- a/src/starboard/common/common.gyp
+++ b/src/starboard/common/common.gyp
@@ -24,11 +24,18 @@
         'includes_starboard': 1,
       },
       'sources': [
+        'common.cc',
+        'decode_target_provider.cc',
         'memory.cc',
         'move.h',
         'reset_and_return.h',
         'scoped_ptr.h',
       ],
+      'defines': [
+        # This must be defined when building Starboard, and must not when
+        # building Starboard client code.
+        'STARBOARD_IMPLEMENTATION',
+      ],
     },
   ],
 }
diff --git a/src/starboard/common/decode_target_provider.cc b/src/starboard/common/decode_target_provider.cc
new file mode 100644
index 0000000..673d091
--- /dev/null
+++ b/src/starboard/common/decode_target_provider.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/decode_target.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+namespace {
+struct Registry {
+  SbMutex mutex;
+  SbDecodeTargetProvider* provider;
+  void* context;
+};
+
+Registry g_registry = {SB_MUTEX_INITIALIZER};
+}  // namespace
+
+bool SbDecodeTargetRegisterProvider(SbDecodeTargetProvider* provider,
+                                    void* context) {
+  SbMutexAcquire(&(g_registry.mutex));
+  if (g_registry.provider || g_registry.context) {
+    SB_DLOG(WARNING) << __FUNCTION__ << ": "
+                     << "Registering (P" << provider << ", C" << context
+                     << ") while "
+                     << "(P" << g_registry.provider << ", C"
+                     << g_registry.context << ") is already registered.";
+  }
+  g_registry.provider = provider;
+  g_registry.context = context;
+  SbMutexRelease(&(g_registry.mutex));
+  return true;
+}
+
+void SbDecodeTargetUnregisterProvider(SbDecodeTargetProvider* provider,
+                                      void* context) {
+  SbMutexAcquire(&(g_registry.mutex));
+  if (g_registry.provider == provider && g_registry.context == context) {
+    g_registry.provider = NULL;
+    g_registry.context = NULL;
+  } else {
+    SB_DLOG(WARNING) << __FUNCTION__ << ": "
+                     << "Unregistering (P" << provider << ", C" << context
+                     << ") while "
+                     << "(P" << g_registry.provider << ", C"
+                     << g_registry.context << ") is what is registered.";
+  }
+  SbMutexRelease(&(g_registry.mutex));
+}
+
+SbDecodeTarget SbDecodeTargetAcquireFromProvider(SbDecodeTargetFormat format,
+                                                 int width,
+                                                 int height) {
+  SbDecodeTargetProvider* provider = NULL;
+  void* context = NULL;
+  SbMutexAcquire(&(g_registry.mutex));
+  provider = g_registry.provider;
+  context = g_registry.context;
+  SbMutexRelease(&(g_registry.mutex));
+  if (!provider) {
+    SB_DLOG(ERROR) << __FUNCTION__ << ": "
+                   << "No registered provider.";
+    return kSbDecodeTargetInvalid;
+  }
+
+  return provider->acquire(context, format, width, height);
+}
+
+void SbDecodeTargetReleaseToProvider(SbDecodeTarget decode_target) {
+  SbDecodeTargetProvider* provider = NULL;
+  void* context = NULL;
+  SbMutexAcquire(&(g_registry.mutex));
+  provider = g_registry.provider;
+  context = g_registry.context;
+  SbMutexRelease(&(g_registry.mutex));
+  if (!provider) {
+    SB_DLOG(ERROR) << __FUNCTION__ << ": "
+                   << "No registered provider.";
+    return;
+  }
+
+  provider->release(context, decode_target);
+}
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/main.cc b/src/starboard/common/test_main.cc
similarity index 100%
rename from src/starboard/nplb/main.cc
rename to src/starboard/common/test_main.cc
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index bfdf8d8..444cc9f 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -35,7 +35,12 @@
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MAXIMUM_API_VERSION 2
+#define SB_MAXIMUM_API_VERSION 3
+
+// The API version that is currently open for changes, and therefore is not
+// stable or frozen. Production-oriented ports should avoid declaring that they
+// implement the experimental Starboard API version.
+#define SB_EXPERIMENTAL_API_VERSION 3
 
 // --- Common Detected Features ----------------------------------------------
 
@@ -458,6 +463,15 @@
 #define SB_HAS_GLES2 !SB_GYP_GL_TYPE_IS_NONE
 #endif
 
+// Specifies whether this platform has any kind of supported graphics system.
+#if !defined(SB_HAS_GRAPHICS)
+#if SB_HAS(GLES2) || SB_HAS(BLITTER)
+#define SB_HAS_GRAPHICS 1
+#else
+#define SB_HAS_GRAPHICS 0
+#endif
+#endif
+
 // Specifies whether the starboard media pipeline components (SbPlayerPipeline
 // and StarboardDecryptor) are used.  Set to 0 means they are not used.
 #define SB_CAN_MEDIA_USE_STARBOARD_PIPELINE \
diff --git a/src/starboard/creator/ci20/configuration_public.h b/src/starboard/creator/ci20/configuration_public.h
deleted file mode 100644
index cd85a36..0000000
--- a/src/starboard/creator/ci20/configuration_public.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// The Starboard configuration for Creator Ci20 Debian.
-
-// Other source files should never include this header directly, but should
-// include the generic "starboard/configuration.h" instead.
-
-#ifndef STARBOARD_CREATOR_CI20_CONFIGURATION_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_CONFIGURATION_PUBLIC_H_
-
-// --- Architecture Configuration --------------------------------------------
-
-// Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
-// automatically set based on this.
-#define SB_IS_BIG_ENDIAN 0
-
-// Whether the current platform is an ARM architecture.
-#define SB_IS_ARCH_ARM 0
-
-// Whether the current platform is a MIPS architecture.
-#define SB_IS_ARCH_MIPS 1
-
-// Whether the current platform is a PPC architecture.
-#define SB_IS_ARCH_PPC 0
-
-// Whether the current platform is an x86 architecture.
-#define SB_IS_ARCH_X86 0
-
-// Whether the current platform is a 32-bit architecture.
-#define SB_IS_32_BIT 1
-
-// Whether the current platform is a 64-bit architecture.
-#define SB_IS_64_BIT 0
-
-// Whether the current platform's pointers are 32-bit.
-// Whether the current platform's longs are 32-bit.
-#if SB_IS(32_BIT)
-#define SB_HAS_32_BIT_POINTERS 1
-#define SB_HAS_32_BIT_LONG 1
-#else
-#define SB_HAS_32_BIT_POINTERS 0
-#define SB_HAS_32_BIT_LONG 0
-#endif
-
-// Whether the current platform's pointers are 64-bit.
-// Whether the current platform's longs are 64-bit.
-#if SB_IS(64_BIT)
-#define SB_HAS_64_BIT_POINTERS 1
-#define SB_HAS_64_BIT_LONG 1
-#else
-#define SB_HAS_64_BIT_POINTERS 0
-#define SB_HAS_64_BIT_LONG 0
-#endif
-
-// Configuration parameters that allow the application to make some general
-// compile-time decisions with respect to the the number of cores likely to be
-// available on this platform. For a definitive measure, the application should
-// still call SbSystemGetNumberOfProcessors at runtime.
-
-// Whether the current platform is expected to have many cores (> 6), or a
-// wildly varying number of cores.
-#define SB_HAS_MANY_CORES 0
-
-// Whether the current platform is expected to have exactly 1 core.
-#define SB_HAS_1_CORE 0
-
-// Whether the current platform is expected to have exactly 2 cores.
-#define SB_HAS_2_CORES 1
-
-// Whether the current platform is expected to have exactly 4 cores.
-#define SB_HAS_4_CORES 0
-
-// Whether the current platform is expected to have exactly 6 cores.
-#define SB_HAS_6_CORES 0
-
-// Whether the current platform's thread scheduler will automatically balance
-// threads between cores, as opposed to systems where threads will only ever run
-// on the specifically pinned core.
-#define SB_HAS_CROSS_CORE_SCHEDULER 1
-
-#include "starboard/creator/shared/configuration_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/ci20directfb/README.md b/src/starboard/creator/ci20directfb/README.md
new file mode 100644
index 0000000..e0c1251
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/README.md
@@ -0,0 +1,4 @@
+# Setting up Starboard to use DirectFB on a Creator Ci20
+
+See `starboard/raspi/directfb/README.md`.  The steps for getting directfb
+running on the Ci20 are identical.
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/creator/ci20directfb/atomic_public.h
similarity index 79%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/creator/ci20directfb/atomic_public.h
index 3b48d2e..2a182e7 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/creator/ci20directfb/atomic_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_
 
 #include "starboard/linux/shared/atomic_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#endif  // STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/creator/ci20directfb/configuration_public.h b/src/starboard/creator/ci20directfb/configuration_public.h
new file mode 100644
index 0000000..651c203
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/configuration_public.h
@@ -0,0 +1,46 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_
+
+#include "starboard/creator/shared/configuration_public.h"
+
+// Indicates whether or not the given platform supports rendering of NV12
+// textures. These textures typically originate from video decoders.
+#undef SB_HAS_NV12_TEXTURE_SUPPORT
+#define SB_HAS_NV12_TEXTURE_SUPPORT 0
+
+// This configuration supports the blitter API (implemented via DirectFB).
+#undef SB_HAS_BLITTER
+#define SB_HAS_BLITTER 1
+
+// Unfortunately, DirectFB does not support bilinear filtering.  According to
+// http://osdir.com/ml/graphics.directfb.user/2008-06/msg00028.html, "smooth
+// scaling is not supported in conjunction with blending", and we need blending
+// more.
+#undef SB_HAS_BILINEAR_FILTERING_SUPPORT
+#define SB_HAS_BILINEAR_FILTERING_SUPPORT 0
+
+// DirectFB's only 32-bit RGBA color format is word-order ARGB.  This translates
+// to byte-order ARGB for big endian platforms and byte-order BGRA for
+// little-endian platforms.
+#undef SB_PREFERRED_RGBA_BYTE_ORDER
+#if SB_IS(BIG_ENDIAN)
+#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_ARGB
+#else
+#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_BGRA
+#endif
+
+#endif  // STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/ci20directfb/gyp_configuration.gypi b/src/starboard/creator/ci20directfb/gyp_configuration.gypi
new file mode 100644
index 0000000..8d8c263
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/gyp_configuration.gypi
@@ -0,0 +1,45 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'platform_libraries': [
+      '-ldirectfb',
+      '-ldirect',
+    ],
+    'gl_type': 'none',
+  },
+
+  'target_defaults': {
+    'default_configuration': 'creator-ci20directfb_debug',
+    'configurations': {
+      'creator-ci20directfb_debug': {
+        'inherit_from': ['debug_base'],
+      },
+      'creator-ci20directfb_devel': {
+        'inherit_from': ['devel_base'],
+      },
+      'creator-ci20directfb_qa': {
+        'inherit_from': ['qa_base'],
+      },
+      'creator-ci20directfb_gold': {
+        'inherit_from': ['gold_base'],
+      },
+    }, # end of configurations
+  },
+
+  'includes': [
+    '../shared/gyp_configuration.gypi',
+  ],
+}
diff --git a/src/starboard/creator/ci20directfb/gyp_configuration.py b/src/starboard/creator/ci20directfb/gyp_configuration.py
new file mode 100644
index 0000000..4e712f5
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/gyp_configuration.py
@@ -0,0 +1,32 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Creator Ci20 DirectFB platform configuration for gyp_cobalt."""
+
+import logging
+import os
+import sys
+
+# Import the shared Creator platform configuration.
+sys.path.append(
+    os.path.realpath(
+        os.path.join(os.path.dirname(__file__), os.pardir, 'shared')))
+import gyp_configuration  # pylint: disable=import-self,g-import-not-at-top
+
+
+def CreatePlatformConfig():
+  try:
+    return gyp_configuration.PlatformConfig('creator-ci20directfb')
+  except RuntimeError as e:
+    logging.critical(e)
+    return None
diff --git a/src/starboard/creator/ci20directfb/main.cc b/src/starboard/creator/ci20directfb/main.cc
new file mode 100644
index 0000000..68d7761
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/main.cc
@@ -0,0 +1,29 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/configuration.h"
+#include "starboard/shared/directfb/application_directfb.h"
+#include "starboard/shared/signal/crash_signals.h"
+#include "starboard/shared/signal/suspend_signals.h"
+
+int main(int argc, char** argv) {
+  tzset();
+  starboard::shared::signal::InstallCrashSignalHandlers();
+  starboard::shared::signal::InstallSuspendSignalHandlers();
+  starboard::ApplicationDirectFB application;
+  int result = application.Run(argc, argv);
+  starboard::shared::signal::UninstallSuspendSignalHandlers();
+  starboard::shared::signal::UninstallCrashSignalHandlers();
+  return result;
+}
diff --git a/src/starboard/creator/ci20/starboard_platform.gyp b/src/starboard/creator/ci20directfb/starboard_platform.gyp
similarity index 84%
copy from src/starboard/creator/ci20/starboard_platform.gyp
copy to src/starboard/creator/ci20directfb/starboard_platform.gyp
index c6a9f11..b695c91 100644
--- a/src/starboard/creator/ci20/starboard_platform.gyp
+++ b/src/starboard/creator/ci20directfb/starboard_platform.gyp
@@ -25,14 +25,14 @@
       'target_name': 'starboard_platform',
       'type': 'static_library',
       'sources': [
-        '<(DEPTH)/starboard/creator/ci20/configuration_public.h',
-        '<(DEPTH)/starboard/creator/ci20/system_get_property.cc',
+        '<(DEPTH)/starboard/creator/ci20directfb/configuration_public.h',
+        '<(DEPTH)/starboard/creator/ci20directfb/main.cc',
+        '<(DEPTH)/starboard/creator/ci20directfb/system_get_property.cc',
         '<(DEPTH)/starboard/linux/shared/atomic_public.h',
         '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_path.cc',
         '<(DEPTH)/starboard/linux/shared/system_has_capability.cc',
-        '<(DEPTH)/starboard/linux/x64x11/main.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
         '<(DEPTH)/starboard/shared/alsa/alsa_util.cc',
@@ -41,6 +41,43 @@
         '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc',
         '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc',
         '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc',
+        '<(DEPTH)/starboard/shared/directfb/application_directfb.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_blit_rect_to_rect.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_context.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_default_device.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_pixel_data.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_render_target_surface.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_surface_from_pixel_data.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_create_swap_chain_from_window.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_destroy_context.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_destroy_device.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_destroy_pixel_data.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_destroy_surface.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_destroy_swap_chain.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_download_surface_pixels.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_fill_rect.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_flip_swap_chain.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_flush_context.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_max_contexts.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pitch_in_bytes.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pointer.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_surface.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_swap_chain.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_get_surface_info.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_internal.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_download_surface_pixels.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_pixel_data.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_is_surface_format_supported_by_render_target_surface.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_set_blending.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_set_color.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_set_modulate_blits_with_color.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_set_render_target.cc',
+        '<(DEPTH)/starboard/shared/directfb/blitter_set_scissor.cc',
+        '<(DEPTH)/starboard/shared/directfb/window_create.cc',
+        '<(DEPTH)/starboard/shared/directfb/window_destroy.cc',
+        '<(DEPTH)/starboard/shared/directfb/window_get_platform_handle.cc',
+        '<(DEPTH)/starboard/shared/directfb/window_get_size.cc',
+        '<(DEPTH)/starboard/shared/directfb/window_internal.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc',
@@ -210,6 +247,8 @@
         '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc',
         '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc',
         '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h',
+        '<(DEPTH)/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc',
+        '<(DEPTH)/starboard/shared/starboard/blitter_blit_rects_to_rects.cc',
         '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc',
         '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
         '<(DEPTH)/starboard/shared/starboard/event_schedule.cc',
@@ -275,12 +314,9 @@
         '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
         '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc',
         '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc',
-        '<(DEPTH)/starboard/shared/x11/application_x11.cc',
-        '<(DEPTH)/starboard/shared/x11/window_create.cc',
-        '<(DEPTH)/starboard/shared/x11/window_destroy.cc',
-        '<(DEPTH)/starboard/shared/x11/window_get_platform_handle.cc',
-        '<(DEPTH)/starboard/shared/x11/window_get_size.cc',
-        '<(DEPTH)/starboard/shared/x11/window_internal.cc',
+      ],
+      'include_dirs': [
+        '<(sysroot)/mipsel-r2-hard/usr/include/directfb',
       ],
       'defines': [
         # This must be defined when building Starboard, and must not when
diff --git a/src/starboard/creator/ci20directfb/system_get_property.cc b/src/starboard/creator/ci20directfb/system_get_property.cc
new file mode 100644
index 0000000..db0f0e0
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/system_get_property.cc
@@ -0,0 +1,69 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+namespace {
+
+const char* kFriendlyName = "Creator Ci20";
+const char* kPlatformName = "DirectFB; Creator Ci20 JZ4780";
+
+bool CopyStringAndTestIfSuccess(char* out_value,
+                                int value_length,
+                                const char* from_value) {
+  if (SbStringGetLength(from_value) + 1 > value_length)
+    return false;
+  SbStringCopy(out_value, from_value, value_length);
+  return true;
+}
+
+}  // namespace
+
+bool SbSystemGetProperty(SbSystemPropertyId property_id,
+                         char* out_value,
+                         int value_length) {
+  if (!out_value || !value_length) {
+    return false;
+  }
+
+  switch (property_id) {
+    case kSbSystemPropertyBrandName:
+    case kSbSystemPropertyChipsetModelNumber:
+    case kSbSystemPropertyFirmwareVersion:
+    case kSbSystemPropertyModelName:
+    case kSbSystemPropertyModelYear:
+    case kSbSystemPropertyNetworkOperatorName:
+      return false;
+
+    case kSbSystemPropertyFriendlyName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
+
+    case kSbSystemPropertyPlatformName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName);
+
+    case kSbSystemPropertyPlatformUuid:
+      SB_NOTIMPLEMENTED();
+      return CopyStringAndTestIfSuccess(out_value, value_length, "N/A");
+
+    default:
+      SB_DLOG(WARNING) << __FUNCTION__
+                       << ": Unrecognized property: " << property_id;
+      break;
+  }
+
+  return false;
+}
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/creator/ci20directfb/thread_types_public.h
similarity index 77%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/creator/ci20directfb/thread_types_public.h
index ec9eedf..b531032 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/creator/ci20directfb/thread_types_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
 
 #include "starboard/linux/shared/thread_types_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/creator/ci20x11/atomic_public.h
similarity index 80%
rename from src/starboard/creator/ci20/atomic_public.h
rename to src/starboard/creator/ci20x11/atomic_public.h
index 3b48d2e..932fe9d 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/creator/ci20x11/atomic_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
 
 #include "starboard/linux/shared/atomic_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#endif  // STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/creator/ci20x11/configuration_public.h
similarity index 71%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/creator/ci20x11/configuration_public.h
index ec9eedf..4c00bad 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/creator/ci20x11/configuration_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_
 
-#include "starboard/linux/shared/thread_types_public.h"
+#include "starboard/creator/shared/configuration_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/gyp_configuration.gypi b/src/starboard/creator/ci20x11/gyp_configuration.gypi
new file mode 100644
index 0000000..a2ea1b8
--- /dev/null
+++ b/src/starboard/creator/ci20x11/gyp_configuration.gypi
@@ -0,0 +1,48 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    'platform_libraries': [
+      '-lEGL',
+      '-lGLESv2',
+      '-lX11',
+      '-lXcomposite',
+      '-lXext',
+      '-lXrender',
+    ],
+  },
+
+  'target_defaults': {
+    'default_configuration': 'creator-ci20x11_debug',
+    'configurations': {
+      'creator-ci20x11_debug': {
+        'inherit_from': ['debug_base'],
+      },
+      'creator-ci20x11_devel': {
+        'inherit_from': ['devel_base'],
+      },
+      'creator-ci20x11_qa': {
+        'inherit_from': ['qa_base'],
+      },
+      'creator-ci20x11_gold': {
+        'inherit_from': ['gold_base'],
+      },
+    }, # end of configurations
+  },
+
+  'includes': [
+    '../shared/gyp_configuration.gypi',
+  ],
+}
diff --git a/src/starboard/creator/ci20x11/gyp_configuration.py b/src/starboard/creator/ci20x11/gyp_configuration.py
new file mode 100644
index 0000000..1c062df
--- /dev/null
+++ b/src/starboard/creator/ci20x11/gyp_configuration.py
@@ -0,0 +1,32 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Creator Ci20 X11 platform configuration for gyp_cobalt."""
+
+import logging
+import os
+import sys
+
+# Import the shared Creator platform configuration.
+sys.path.append(
+    os.path.realpath(
+        os.path.join(os.path.dirname(__file__), os.pardir, 'shared')))
+import gyp_configuration  # pylint: disable=import-self,g-import-not-at-top
+
+
+def CreatePlatformConfig():
+  try:
+    return gyp_configuration.PlatformConfig('creator-ci20x11')
+  except RuntimeError as e:
+    logging.critical(e)
+    return None
diff --git a/src/starboard/creator/ci20/main.cc b/src/starboard/creator/ci20x11/main.cc
similarity index 100%
rename from src/starboard/creator/ci20/main.cc
rename to src/starboard/creator/ci20x11/main.cc
diff --git a/src/starboard/creator/ci20/starboard_platform.gyp b/src/starboard/creator/ci20x11/starboard_platform.gyp
similarity index 97%
rename from src/starboard/creator/ci20/starboard_platform.gyp
rename to src/starboard/creator/ci20x11/starboard_platform.gyp
index c6a9f11..bab02c6 100644
--- a/src/starboard/creator/ci20/starboard_platform.gyp
+++ b/src/starboard/creator/ci20x11/starboard_platform.gyp
@@ -25,14 +25,15 @@
       'target_name': 'starboard_platform',
       'type': 'static_library',
       'sources': [
-        '<(DEPTH)/starboard/creator/ci20/configuration_public.h',
-        '<(DEPTH)/starboard/creator/ci20/system_get_property.cc',
+        '<(DEPTH)/starboard/creator/ci20x11/atomic_public.h',
+        '<(DEPTH)/starboard/creator/ci20x11/configuration_public.h',
+        '<(DEPTH)/starboard/creator/ci20x11/main.cc',
+        '<(DEPTH)/starboard/creator/ci20x11/system_get_property.cc',
         '<(DEPTH)/starboard/linux/shared/atomic_public.h',
         '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
         '<(DEPTH)/starboard/linux/shared/system_get_path.cc',
         '<(DEPTH)/starboard/linux/shared/system_has_capability.cc',
-        '<(DEPTH)/starboard/linux/x64x11/main.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
         '<(DEPTH)/starboard/shared/alsa/alsa_util.cc',
@@ -226,8 +227,8 @@
         '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
         '<(DEPTH)/starboard/shared/starboard/new.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
diff --git a/src/starboard/creator/ci20/system_get_property.cc b/src/starboard/creator/ci20x11/system_get_property.cc
similarity index 100%
rename from src/starboard/creator/ci20/system_get_property.cc
rename to src/starboard/creator/ci20x11/system_get_property.cc
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/creator/ci20x11/thread_types_public.h
similarity index 79%
rename from src/starboard/creator/ci20/thread_types_public.h
rename to src/starboard/creator/ci20x11/thread_types_public.h
index ec9eedf..7437a70 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/creator/ci20x11/thread_types_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_
 
 #include "starboard/linux/shared/thread_types_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/creator/shared/configuration_public.h b/src/starboard/creator/shared/configuration_public.h
index 8144c15..8898d5b 100644
--- a/src/starboard/creator/shared/configuration_public.h
+++ b/src/starboard/creator/shared/configuration_public.h
@@ -17,6 +17,76 @@
 #ifndef STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_
 
+// --- Architecture Configuration --------------------------------------------
+
+// Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
+// automatically set based on this.
+#define SB_IS_BIG_ENDIAN 0
+
+// Whether the current platform is an ARM architecture.
+#define SB_IS_ARCH_ARM 0
+
+// Whether the current platform is a MIPS architecture.
+#define SB_IS_ARCH_MIPS 1
+
+// Whether the current platform is a PPC architecture.
+#define SB_IS_ARCH_PPC 0
+
+// Whether the current platform is an x86 architecture.
+#define SB_IS_ARCH_X86 0
+
+// Whether the current platform is a 32-bit architecture.
+#define SB_IS_32_BIT 1
+
+// Whether the current platform is a 64-bit architecture.
+#define SB_IS_64_BIT 0
+
+// Whether the current platform's pointers are 32-bit.
+// Whether the current platform's longs are 32-bit.
+#if SB_IS(32_BIT)
+#define SB_HAS_32_BIT_POINTERS 1
+#define SB_HAS_32_BIT_LONG 1
+#else
+#define SB_HAS_32_BIT_POINTERS 0
+#define SB_HAS_32_BIT_LONG 0
+#endif
+
+// Whether the current platform's pointers are 64-bit.
+// Whether the current platform's longs are 64-bit.
+#if SB_IS(64_BIT)
+#define SB_HAS_64_BIT_POINTERS 1
+#define SB_HAS_64_BIT_LONG 1
+#else
+#define SB_HAS_64_BIT_POINTERS 0
+#define SB_HAS_64_BIT_LONG 0
+#endif
+
+// Configuration parameters that allow the application to make some general
+// compile-time decisions with respect to the the number of cores likely to be
+// available on this platform. For a definitive measure, the application should
+// still call SbSystemGetNumberOfProcessors at runtime.
+
+// Whether the current platform is expected to have many cores (> 6), or a
+// wildly varying number of cores.
+#define SB_HAS_MANY_CORES 0
+
+// Whether the current platform is expected to have exactly 1 core.
+#define SB_HAS_1_CORE 0
+
+// Whether the current platform is expected to have exactly 2 cores.
+#define SB_HAS_2_CORES 1
+
+// Whether the current platform is expected to have exactly 4 cores.
+#define SB_HAS_4_CORES 0
+
+// Whether the current platform is expected to have exactly 6 cores.
+#define SB_HAS_6_CORES 0
+
+// Whether the current platform's thread scheduler will automatically balance
+// threads between cores, as opposed to systems where threads will only ever run
+// on the specifically pinned core.
+#define SB_HAS_CROSS_CORE_SCHEDULER 1
+
 // The API version implemented by this platform.
 #define SB_API_VERSION 1
 
@@ -68,10 +138,8 @@
 
 // --- Architecture Configuration --------------------------------------------
 
-// On the current version of Raspbian, real time thread scheduling seems to be
-// broken in that higher priority threads do not always have priority over lower
-// priority threads.  It looks like the thread created last will always have the
-// highest priority.
+// On default Linux, you must be a superuser in order to set real time
+// scheduling on threads.
 #define SB_HAS_THREAD_PRIORITY_SUPPORT 0
 
 // --- Attribute Configuration -----------------------------------------------
diff --git a/src/starboard/creator/ci20/gyp_configuration.gypi b/src/starboard/creator/shared/gyp_configuration.gypi
similarity index 86%
rename from src/starboard/creator/ci20/gyp_configuration.gypi
rename to src/starboard/creator/shared/gyp_configuration.gypi
index 045e41c..c6cb2f2 100644
--- a/src/starboard/creator/ci20/gyp_configuration.gypi
+++ b/src/starboard/creator/shared/gyp_configuration.gypi
@@ -19,7 +19,7 @@
 
     'enable_webdriver': 0,
     'in_app_dial%': 0,
-    'gl_type': 'system_gles2',
+    'gl_type%': 'system_gles3',
     'image_cache_size_in_bytes': 32 * 1024 * 1024,
 
     'scratch_surface_cache_size_in_bytes' : 0,
@@ -39,6 +39,16 @@
       '-U__linux__',
       '--sysroot=<(sysroot)',
       '-EL',
+
+      # Suppress some warnings that will be hard to fix.
+      '-Wno-unused-local-typedefs',
+      '-Wno-unused-result',
+      '-Wno-deprecated-declarations',
+      '-Wno-missing-field-initializers',
+      '-Wno-comment',
+      '-Wno-narrowing',
+      '-Wno-unknown-pragmas',
+      '-Wno-type-limits',  # TODO: We should actually look into these.
     ],
     'linker_flags': [
       '--sysroot=<(sysroot)',
@@ -87,16 +97,10 @@
       '-lavformat',
       '-lavresample',
       '-lavutil',
-      '-lEGL',
-      '-lGLESv2',
       '-lm',
       '-lpthread',
       '-lpulse',
       '-lrt',
-      '-lX11',
-      '-lXcomposite',
-      '-lXext',
-      '-lXrender',
     ],
     'conditions': [
       ['cobalt_fastbuild==0', {
@@ -129,21 +133,6 @@
       '-std=gnu++11',
       '-Wno-literal-suffix',
     ],
-    'default_configuration': 'creator-ci20_debug',
-    'configurations': {
-      'creator-ci20_debug': {
-        'inherit_from': ['debug_base'],
-      },
-      'creator-ci20_devel': {
-        'inherit_from': ['devel_base'],
-      },
-      'creator-ci20_qa': {
-        'inherit_from': ['qa_base'],
-      },
-      'creator-ci20_gold': {
-        'inherit_from': ['gold_base'],
-      },
-    }, # end of configurations
     'target_conditions': [
       ['cobalt_code==1', {
         'cflags': [
diff --git a/src/starboard/creator/ci20/gyp_configuration.py b/src/starboard/creator/shared/gyp_configuration.py
similarity index 73%
rename from src/starboard/creator/ci20/gyp_configuration.py
rename to src/starboard/creator/shared/gyp_configuration.py
index 10bb683..373a70e 100644
--- a/src/starboard/creator/ci20/gyp_configuration.py
+++ b/src/starboard/creator/shared/gyp_configuration.py
@@ -11,7 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Starboard ci20 platform configuration for gyp_cobalt."""
+"""Starboard Creator Ci20 platform configuration for gyp_cobalt."""
 
 import logging
 import os
@@ -20,19 +20,11 @@
 import config.starboard
 
 
-def CreatePlatformConfig():
-  try:
-    return _PlatformConfig('creator-ci20')
-  except RuntimeError as e:
-    logging.critical(e)
-    return None
-
-
-class _PlatformConfig(config.starboard.PlatformConfigStarboard):
+class PlatformConfig(config.starboard.PlatformConfigStarboard):
   """Starboard ci20 platform configuration."""
 
   def __init__(self, platform):
-    super(_PlatformConfig, self).__init__(platform)
+    super(PlatformConfig, self).__init__(platform)
 
   def _GetCi20Home(self):
     try:
@@ -46,16 +38,16 @@
   def GetVariables(self, configuration):
     ci20_home = self._GetCi20Home()
 
-    sysroot = os.path.join(ci20_home, 'mips-mti-linux-gnu', '2016.05-03',
-                           'sysroot')
+    relative_sysroot = os.path.join('mips-mti-linux-gnu', '2016.05-03',
+                                    'sysroot')
+    sysroot = os.path.join(ci20_home, relative_sysroot)
 
     if not os.path.isdir(sysroot):
       logging.critical(
-          'ci20 builds require '
-          '$CI20_HOME/mips-mti-linux-gnu/2016.05-03/sysroot to be a valid '
-          'directory.')
+          'ci20 builds require $CI20_HOME/%s to be a valid directory.',
+          relative_sysroot)
       sys.exit(1)
-    variables = super(_PlatformConfig, self).GetVariables(configuration)
+    variables = super(PlatformConfig, self).GetVariables(configuration)
     variables.update({
         'clang': 0,
         'sysroot': sysroot,
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
new file mode 100644
index 0000000..6ea6951
--- /dev/null
+++ b/src/starboard/decode_target.h
@@ -0,0 +1,286 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Module Overview: Starboard Decode Target module
+//
+// A target for decoding image and video data into. This corresponds roughly to
+// an EGLImage, but that extension may not be fully supported on all GL
+// platforms. SbDecodeTarget supports multi-plane targets. We need a mechanism
+// for SbBlitter as well, and this is able to more-or-less unify the two.
+//
+// An SbDecodeTarget can be passed into any function which decodes video or
+// image data. This allows the application to allocate fast graphics memory, and
+// have decoding done directly into this memory, avoiding unnecessary memory
+// copies, and also avoiding pushing data between CPU and GPU memory
+// unnecessarily.
+//
+// #### SbDecodeTargetFormat
+//
+// SbDecodeTargets support several different formats that can be used to decode
+// into and render from. Some formats may be easier to decode into, and others
+// may be easier to render. Some may take less memory. Each decoder needs to
+// support the SbDecodeTargetFormat passed into it, or the decode will produce
+// an error. Each decoder provides a way to check if a given
+// SbDecodeTargetFormat is supported by that decoder.
+//
+// #### SbDecodeTargetProvider
+//
+// Some components may need to acquire SbDecodeTargets compatible with a certain
+// rendering context, which may need to be created on a particular thread. The
+// SbDecodeTargetProvider is a way for the primary rendering context to provide
+// an interface that can create SbDecodeTargets on demand by other system.
+//
+// The primary usage is likely to be the the SbPlayer implementation on some
+// platforms.
+//
+// #### SbDecodeTarget Example
+//
+// Let's say there's an image decoder for .foo files:
+//
+//     bool SbImageDecodeFooSupportsFormat(SbDecodeTargetFormat format);
+//     bool SbImageDecodeFoo(void *data, int data_size, SbDecodeTarget target);
+//
+// First, the client should enumerate which SbDecodeTargetFormats are supported
+// by that decoder.
+//
+//     SbDecodeTargetFormat kPreferredFormats[] = {
+//         kSbDecodeTargetFormat3PlaneYUVI420,
+//         kSbDecodeTargetFormat1PlaneRGBA,
+//         kSbDecodeTargetFormat1PlaneBGRA,
+//     };
+//
+//     SbDecodeTargetFormat format = kSbDecodeTargetFormatInvalid;
+//     for (int i = 0; i < SB_ARRAY_SIZE_INT(kPreferredFormats); ++i) {
+//       if (SbImageDecodeFooSupportsFormat(kPreferredFormats[i])) {
+//         format = kPreferredFormats[i];
+//         break;
+//       }
+//     }
+//
+// Now that the client has a format, it can create a decode target that it will
+// use to decode the .foo file into. Let's assume format is
+// kSbDecodeTargetFormat1PlaneRGBA, and that we are on an EGL/GLES2 platform.
+// Also, we won't do any error checking, to keep things even simpler.
+//
+//     // Allocate a sized texture of the right format.
+//     GLuint texture_handle;
+//     glGenTextures(1, &texture_handle);
+//     glBindTexture(GL_TEXTURE_2D, texture_handle);
+//     glTexImage2D(GL_TEXTURE_2D, 0 /*level*/, GL_RGBA, width, height,
+//                  0 /*border*/, GL_RGBA8, GL_UNSIGNED_BYTE, NULL /*data*/);
+//     glBindTexture(GL_TEXTURE_2D, 0);
+//
+//     // Create an SbDecodeTarget wrapping the new texture handle.
+//     SbDecodeTarget target =
+//         SbDecodeTargetCreate(display, context, format, &texture_handle);
+//
+//     // Now pass the SbDecodeTarget into the decoder.
+//     SbImageDecodeFoo(encoded_foo_data, encoded_foo_data_size, target);
+//
+//     // If the decode works, you can get the texture out and render it.
+//     GLuint texture =
+//         SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+//
+
+#ifndef STARBOARD_DECODE_TARGET_H_
+#define STARBOARD_DECODE_TARGET_H_
+
+#include "starboard/configuration.h"
+#include "starboard/export.h"
+#include "starboard/types.h"
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+#if SB_HAS(BLITTER)
+#include "starboard/blitter.h"
+#else
+#include <EGL/egl.h>
+#include <GL/gl.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// --- Types -----------------------------------------------------------------
+
+// Private structure representing a target for image data decoding.
+typedef struct SbDecodeTargetPrivate SbDecodeTargetPrivate;
+
+// A handle to a target for image data decoding.
+typedef SbDecodeTargetPrivate* SbDecodeTarget;
+
+// The list of all possible decoder target formats. An SbDecodeTarget consists
+// of one or more planes of data, each plane corresponding with a surface. For
+// some formats, different planes will be different sizes for the same
+// dimensions.
+//
+// NOTE: For enumeration entries with an alpha component, the alpha will always
+// be premultiplied unless otherwise explicitly specified.
+typedef enum SbDecodeTargetFormat {
+  // A decoder target format consisting of a single RGBA plane, in that channel
+  // order.
+  kSbDecodeTargetFormat1PlaneRGBA,
+
+  // A decoder target format consisting of a single BGRA plane, in that channel
+  // order.
+  kSbDecodeTargetFormat1PlaneBGRA,
+
+  // A decoder target format consisting of Y and interleaved UV planes, in that
+  // plane and channel order.
+  kSbDecodeTargetFormat2PlaneYUVNV12,
+
+  // A decoder target format consisting of Y, U, and V planes, in that order.
+  kSbDecodeTargetFormat3PlaneYUVI420,
+
+  // An invalid decode target format.
+  kSbDecodeTargetFormatInvalid,
+} SbDecodeTargetFormat;
+
+// All the planes supported by SbDecodeTarget.
+typedef enum SbDecodeTargetPlane {
+  // The RGBA plane for the RGBA format.
+  kSbDecodeTargetPlaneRGBA = 0,
+
+  // The BGRA plane for the BGRA format.
+  kSbDecodeTargetPlaneBGRA = 0,
+
+  // The Y plane for multi-plane YUV formats.
+  kSbDecodeTargetPlaneY = 0,
+
+  // The UV plane for 2-plane YUV formats.
+  kSbDecodeTargetPlaneUV = 1,
+
+  // The U plane for 3-plane YUV formats.
+  kSbDecodeTargetPlaneU = 1,
+
+  // The V plane for 3-plane YUV formats.
+  kSbDecodeTargetPlaneV = 2,
+} SbDecodeTargetPlane;
+
+// A function that can produce an SbDecodeTarget of the given |format|, |width|,
+// and |height|.
+typedef SbDecodeTarget (*SbDecodeTargetAcquireFunction)(
+    void* context,
+    SbDecodeTargetFormat format,
+    int width,
+    int height);
+
+// A function that can reclaim an SbDecodeTarget allocated with an
+// SbDecodeTargetAcquireFunction.
+typedef void (*SbDecodeTargetReleaseFunction)(void* context,
+                                              SbDecodeTarget decode_target);
+
+// An object that can acquire and release SbDecodeTarget instances.
+typedef struct SbDecodeTargetProvider {
+  // The function to acquire a new SbDecodeTarget from the provider.
+  SbDecodeTargetAcquireFunction acquire;
+
+  // The function to release an acquired SbDecodeTarget back to the provider.
+  SbDecodeTargetReleaseFunction release;
+} SbDecodeTargetProvider;
+
+// --- Constants -------------------------------------------------------------
+
+// Well-defined value for an invalid decode target handle.
+#define kSbDecodeTargetInvalid ((SbDecodeTarget)NULL)
+
+// --- Functions -------------------------------------------------------------
+
+// Returns whether the given file handle is valid.
+static SB_C_INLINE bool SbDecodeTargetIsValid(SbDecodeTarget handle) {
+  return handle != kSbDecodeTargetInvalid;
+}
+
+#if SB_HAS(BLITTER)
+// Creates a new SbBlitter-compatible SbDecodeTarget from one or more |planes|
+// created from |display|.
+//
+// |format|: The format of the decode target being created.
+// |planes|: An array of SbBlitterSurface handles to be bundled into an
+// SbDecodeTarget. Must not be NULL. Is expected to have the same number of
+// entries as the number of planes for |format|, in the order and size expected
+// by that format.
+SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat format,
+                                              SbBlitterSurface* planes);
+
+// Gets the surface that represents the given plane.
+SB_EXPORT SbBlitterSurface SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
+                                                  SbDecodeTargetPlane plane);
+#else   // SB_HAS(BLITTER)
+// Creates a new EGL/GLES2-compatible SbDecodeTarget from one or more |planes|
+// owned by |context|, created from |display|. Must be called from a thread
+// where |context| is current.
+//
+// |display|: The EGLDisplay being targeted.
+// |context|: The EGLContext used for this operation, or EGL_NO_CONTEXT if a
+// context is not required.
+// |format|: The format of the decode target being created.
+// |planes|: An array of GLES Texture handles to be bundled into an
+// SbDecodeTarget. Must not be NULL. Is expected to have the same number of
+// entries as the number of planes for |format|, in the order and size expected
+// by that format.
+SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(EGLDisplay display,
+                                              EGLContext context,
+                                              SbDecodeTargetFormat format,
+                                              GLuint* planes);
+
+// Gets the texture that represents the given plane.
+SB_EXPORT GLuint SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
+                                        SbDecodeTargetPlane plane);
+#endif  // SB_HAS(BLITTER)
+
+// Destroys the given SbDecodeTarget and all associated surfaces.
+SB_EXPORT void SbDecodeTargetDestroy(SbDecodeTarget decode_target);
+
+// Gets the format that |decode_target| was created with.
+SB_EXPORT SbDecodeTargetFormat
+SbDecodeTargetGetFormat(SbDecodeTarget decode_target);
+
+// Registers |provider| as the SbDecodeTargetProvider with the given |context|,
+// displacing any previous registered provider. The provider is expected to be
+// kept alive by the caller until unregistered, so this function is NOT passing
+// ownership in any way. |context| will be passed into every call to
+// SbDecodeTargetProvider::acquire or SbDecodeTargetProvider::release. May be
+// called from any thread.
+SB_EXPORT bool SbDecodeTargetRegisterProvider(SbDecodeTargetProvider* provider,
+                                              void* context);
+
+// Unregisters |provider| as the SbDecodeTargetProvider, with |context|, if it
+// is the current registered provider-context. This is checked by comparing the
+// pointer values of both |provider| and |context| to the currently registered
+// provider. May be called from any thread.
+SB_EXPORT void SbDecodeTargetUnregisterProvider(
+    SbDecodeTargetProvider* provider,
+    void* context);
+
+// Acquires a decode target from the registered SbDecodeTargetProvider,
+// returning NULL if none is registered. May be called from any thread.
+SB_EXPORT SbDecodeTarget
+SbDecodeTargetAcquireFromProvider(SbDecodeTargetFormat format,
+                                  int width,
+                                  int height);
+
+// Releases |decode_target| back to the registered SbDecodeTargetProvider. If no
+// provider is registered, just calls SbDecodeTargetDestroy on |decode_target|.
+// May be called from any thread.
+SB_EXPORT void SbDecodeTargetReleaseToProvider(SbDecodeTarget decode_target);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+#endif  // STARBOARD_DECODE_TARGET_H_
diff --git a/src/starboard/drm.h b/src/starboard/drm.h
index 26d99a9..f7f4544 100644
--- a/src/starboard/drm.h
+++ b/src/starboard/drm.h
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Definitions that allow for DRM support, common between Player and Decoder
-// interfaces.
+// Module Overview: Starboard DRM module
+//
+// Provides definitions that allow for DRM support, which are common
+// between Player and Decoder interfaces.
 
 #ifndef STARBOARD_DRM_H_
 #define STARBOARD_DRM_H_
@@ -104,26 +106,30 @@
 
 // --- Functions -------------------------------------------------------------
 
-// Returns whether the |drm_system| is a valid SbDrmSystem.
+// Indicates whether |drm_system| is a valid SbDrmSystem.
 static SB_C_FORCE_INLINE bool SbDrmSystemIsValid(SbDrmSystem drm) {
   return drm != kSbDrmSystemInvalid;
 }
 
-// Creates a new |key_system| DRM system that can be used when constructing an
-// SbPlayer or an SbDecoder.  |key_system| should be in fhe form of
-// "com.example.somesystem" as suggested by
-// https://w3c.github.io/encrypted-media/#key-system.  All letters in
-// |key_system| should be in lower case and will be matched exactly with known
-// DRM key systems of the platform.
-// |context| will be passed when any callback parameters of this function are
-// called.
-// |update_request_callback| is a callback that will be called every time after
-// SbDrmGenerateSessionUpdateRequest() is called.
-// |session_updated_callback| is a callback that will be called every time
-// after  SbDrmUpdateSession() is called.
-// Please refer to the document of SbDrmGenerateSessionUpdateRequest() and
+// Creates a new DRM system that can be used when constructing an SbPlayer
+// or an SbDecoder.
+//
+// This function returns kSbDrmSystemInvalid if |key_system| is unsupported.
+//
+// Also see the documentation of SbDrmGenerateSessionUpdateRequest() and
 // SbDrmUpdateSession() for more details.
-// Returns kSbDrmSystemInvalid if the |key_system| is unsupported.
+//
+// |key_system|: The DRM key system to be created. The value should be in the
+// form of "com.example.somesystem" as suggested by
+// https://w3c.github.io/encrypted-media/#key-system. All letters in the value
+// should be lowercase and will be matched exactly with known DRM key systems
+// of the platform.
+// |context|: A value passed when any of this function's callback parameters
+// are called.
+// |update_request_callback|: A function that is called every time after
+// SbDrmGenerateSessionUpdateRequest() is called.
+// |session_updated_callback|: A function that is called every time after
+// SbDrmUpdateSession() is called.
 SB_EXPORT SbDrmSystem
 SbDrmCreateSystem(const char* key_system,
                   void* context,
@@ -132,15 +138,28 @@
 
 // Asynchronously generates a session update request payload for
 // |initialization_data|, of |initialization_data_size|, in case sensitive
-// |type|, extracted from the media stream, in |drm_system|'s key system. Calls
-// |update_request_callback| with |context| and either a populated request, or
-// NULL |session_id| if an error occured.  |context| may be used to distinguish
-// callbacks from multiple concurrent calls to
-// SbDrmGenerateSessionUpdateRequest(), and/or to route callbacks back to an
-// object instance.
+// |type|, extracted from the media stream, in |drm_system|'s key system.
 //
-// Callbacks may called from another thread or from the current thread before
-// this function returns.
+// This function calls |drm_system|'s |update_request_callback| function,
+// which is defined when the DRM system is created by SbDrmCreateSystem. When
+// calling that function, this function either sends |context| (also from
+// |SbDrmCreateSystem|) and a populated request, or it sends NULL |session_id|
+// if an error occurred.
+//
+// |drm_system|'s |context| may be used to distinguish callbacks from
+// multiple concurrent calls to SbDrmGenerateSessionUpdateRequest(), and/or
+// to route callbacks back to an object instance.
+//
+// Callbacks may be called either from the current thread before this function
+// returns or from another thread.
+//
+// |drm_system|: The DRM system that defines the key system used for the
+// session update request payload as well as the callback function that is
+// called as a result of the function being called.
+// |type|: The case-sensitive type of the session update request payload.
+// |initialization_data|: The data for which the session update request payload
+// is created.
+// |initialization_data_size|: The size of the session update request payload.
 SB_EXPORT void SbDrmGenerateSessionUpdateRequest(
     SbDrmSystem drm_system,
     const char* type,
@@ -155,26 +174,28 @@
 // and/or to route callbacks back to an object instance.
 //
 // Once the session is successfully updated, an SbPlayer or SbDecoder associated
-// with that system will be able to decrypt samples encrypted.
+// with that DRM key system will be able to decrypt encrypted samples.
 //
-// |session_updated_callback| may called from another thread or from the current
-// thread before this function returns.
+// |drm_system|'s |session_updated_callback| may called either from the
+// current thread before this function returns or from another thread.
 SB_EXPORT void SbDrmUpdateSession(SbDrmSystem drm_system,
                                   const void* key,
                                   int key_size,
                                   const void* session_id,
                                   int session_id_size);
 
-// Clear any internal states/resources related to the particular |session_id|.
+// Clear any internal states/resources related to the specified |session_id|.
 SB_EXPORT void SbDrmCloseSession(SbDrmSystem drm_system,
                                  const void* session_id,
                                  int session_id_size);
 
-// Gets the number of keys installed in the given |drm_system| system.
+// Returns the number of keys installed in |drm_system|.
+//
+// |drm_system|: The system for which the number of installed keys is retrieved.
 SB_EXPORT int SbDrmGetKeyCount(SbDrmSystem drm_system);
 
-// Gets the |out_key|, |out_key_size|, and |out_status| for key with |index| in
-// the given |drm_system| system. Returns whether a key is installed at |index|.
+// Gets |out_key|, |out_key_size|, and |out_status| for the key with |index|
+// in |drm_system|. Returns whether a key is installed at |index|.
 // If not, the output parameters, which all must not be NULL, will not be
 // modified.
 SB_EXPORT bool SbDrmGetKeyStatus(SbDrmSystem drm_system,
@@ -186,17 +207,21 @@
                                  SbDrmKeyStatus* out_status);
 
 // Removes all installed keys for |drm_system|. Any outstanding session update
-// requests will also be invalidated.
+// requests are also invalidated.
+//
+// |drm_system|: The DRM system for which keys should be removed.
 SB_EXPORT void SbDrmRemoveAllKeys(SbDrmSystem drm_system);
 
-// Destroys |drm_system|, which implicitly removes all keys installed in it, and
+// Destroys |drm_system|, which implicitly removes all keys installed in it and
 // invalidates all outstanding session update requests. A DRM system cannot be
 // destroyed unless any associated SbPlayer or SbDecoder has first been
 // destroyed.
 //
-// All callbacks are guaranteed to be finished when this function returns.  So
-// calling this function from a callback passed to SbDrmCreateSystem() will
-// result in deadlock.
+// All callbacks are guaranteed to be finished when this function returns.
+// As a result, if this function is called from a callback that is passed
+// to SbDrmCreateSystem(), a deadlock will occur.
+//
+// |drm_system|: The DRM system to be destroyed.
 SB_EXPORT void SbDrmDestroySystem(SbDrmSystem drm_system);
 
 #ifdef __cplusplus
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index 20643e6..b374b00 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -23,8 +23,9 @@
 #ifndef STARBOARD_LINUX_SHARED_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_SHARED_CONFIGURATION_PUBLIC_H_
 
-// The API version implemented by this platform.
+#ifndef SB_API_VERSION
 #define SB_API_VERSION 1
+#endif
 
 // --- System Header Configuration -------------------------------------------
 
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/linux/x64directfb/future/atomic_public.h
similarity index 78%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/linux/x64directfb/future/atomic_public.h
index 3b48d2e..e4ef8fd 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/linux/x64directfb/future/atomic_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
+#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
 
 #include "starboard/linux/shared/atomic_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#endif  // STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/configuration_public.h b/src/starboard/linux/x64directfb/future/configuration_public.h
new file mode 100644
index 0000000..db7ef6e
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/configuration_public.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The Starboard configuration for Desktop X64 Linux configured for the future
+// starboard API.
+
+#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_
+
+// Include the X64DIRECTFB Linux configuration.
+#include "starboard/linux/x64directfb/configuration_public.h"
+
+// The API version implemented by this platform.
+#undef SB_API_VERSION
+#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION
+
+#endif  // STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/gyp_configuration.gypi b/src/starboard/linux/x64directfb/future/gyp_configuration.gypi
new file mode 100644
index 0000000..44b920d
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/gyp_configuration.gypi
@@ -0,0 +1,37 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'target_defaults': {
+    'default_configuration': 'linux-x64directfb-future_debug',
+    'configurations': {
+      'linux-x64directfb-future_debug': {
+        'inherit_from': ['debug_base'],
+      },
+      'linux-x64directfb-future_devel': {
+        'inherit_from': ['devel_base'],
+      },
+      'linux-x64directfb-future_qa': {
+        'inherit_from': ['qa_base'],
+      },
+      'linux-x64directfb-future_gold': {
+        'inherit_from': ['gold_base'],
+      },
+    }, # end of configurations
+  },
+
+  'includes': [
+    '../gyp_configuration.gypi',
+  ],
+}
diff --git a/src/starboard/linux/x64directfb/future/gyp_configuration.py b/src/starboard/linux/x64directfb/future/gyp_configuration.py
new file mode 100644
index 0000000..2f7fe1b
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/gyp_configuration.py
@@ -0,0 +1,33 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 DirectFB future platform configuration for gyp_cobalt."""
+
+import logging
+import os
+import sys
+
+# Import the shared Linux platform configuration.
+sys.path.append(
+    os.path.realpath(
+        os.path.join(
+            os.path.dirname(__file__), os.pardir, os.pardir, 'shared')))
+import gyp_configuration
+
+
+def CreatePlatformConfig():
+  try:
+    return gyp_configuration.PlatformConfig('linux-x64directfb-future')
+  except RuntimeError as e:
+    logging.critical(e)
+    return None
diff --git a/src/starboard/linux/x64directfb/future/starboard_platform.gyp b/src/starboard/linux/x64directfb/future/starboard_platform.gyp
new file mode 100644
index 0000000..0f82c74
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/starboard_platform.gyp
@@ -0,0 +1,40 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+  'targets': [
+    {
+      'target_name': 'starboard_platform',
+      'product_name': 'starboard_platform_future',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/starboard/shared/stub/decode_target_create_blitter.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_destroy.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_get_format.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_get_plane_blitter.cc',
+      ],
+      'defines': [
+        # This must be defined when building Starboard, and must not when
+        # building Starboard client code.
+        'STARBOARD_IMPLEMENTATION',
+      ],
+      'dependencies': [
+        '<(DEPTH)/starboard/common/common.gyp:common',
+        '<(DEPTH)/starboard/linux/x64directfb/starboard_platform.gyp:starboard_platform',
+        '<(DEPTH)/starboard/linux/x64directfb/starboard_platform.gyp:starboard_base_symbolize',
+        '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/linux/x64directfb/future/thread_types_public.h
similarity index 76%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/linux/x64directfb/future/thread_types_public.h
index ec9eedf..19803be 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/linux/x64directfb/future/thread_types_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_
 
 #include "starboard/linux/shared/thread_types_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/starboard_platform.gyp b/src/starboard/linux/x64directfb/starboard_platform.gyp
index f89ae85..2419586 100644
--- a/src/starboard/linux/x64directfb/starboard_platform.gyp
+++ b/src/starboard/linux/x64directfb/starboard_platform.gyp
@@ -265,8 +265,8 @@
         '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
         '<(DEPTH)/starboard/shared/starboard/new.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
diff --git a/src/starboard/linux/x64x11/configuration_public.h b/src/starboard/linux/x64x11/configuration_public.h
index 2291350..4aec879 100644
--- a/src/starboard/linux/x64x11/configuration_public.h
+++ b/src/starboard/linux/x64x11/configuration_public.h
@@ -22,6 +22,9 @@
 #ifndef STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_
 
+// The API version implemented by this platform.
+#define SB_API_VERSION 2
+
 // --- Architecture Configuration --------------------------------------------
 
 // Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
@@ -106,6 +109,8 @@
 // textures. These textures typically originate from video decoders.
 #define SB_HAS_NV12_TEXTURE_SUPPORT 1
 
+#define SB_HAS_MICROPHONE 0
+
 // Include the Linux configuration that's common between all Desktop Linuxes.
 #include "starboard/linux/shared/configuration_public.h"
 
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/linux/x64x11/future/atomic_public.h
similarity index 79%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/linux/x64x11/future/atomic_public.h
index 3b48d2e..e908f7d 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/linux/x64x11/future/atomic_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
+#define STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
 
 #include "starboard/linux/shared/atomic_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#endif  // STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/configuration_public.h b/src/starboard/linux/x64x11/future/configuration_public.h
new file mode 100644
index 0000000..91ab8df
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/configuration_public.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The Starboard configuration for Desktop X86 Linux configured for the future
+// starboard API.
+
+#ifndef STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_
+
+// Include the X64X11 Linux configuration.
+#include "starboard/linux/x64x11/configuration_public.h"
+
+// The API version implemented by this platform.
+#undef SB_API_VERSION
+#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION
+
+#endif  // STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/gyp_configuration.gypi b/src/starboard/linux/x64x11/future/gyp_configuration.gypi
new file mode 100644
index 0000000..0cb12cf
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/gyp_configuration.gypi
@@ -0,0 +1,37 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'target_defaults': {
+    'default_configuration': 'linux-x64x11-future_debug',
+    'configurations': {
+      'linux-x64x11-future_debug': {
+        'inherit_from': ['debug_base'],
+      },
+      'linux-x64x11-future_devel': {
+        'inherit_from': ['devel_base'],
+      },
+      'linux-x64x11-future_qa': {
+        'inherit_from': ['qa_base'],
+      },
+      'linux-x64x11-future_gold': {
+        'inherit_from': ['gold_base'],
+      },
+    }, # end of configurations
+  },
+
+  'includes': [
+    '../gyp_configuration.gypi',
+  ],
+}
diff --git a/src/starboard/linux/x64x11/future/gyp_configuration.py b/src/starboard/linux/x64x11/future/gyp_configuration.py
new file mode 100644
index 0000000..5f82ad7
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/gyp_configuration.py
@@ -0,0 +1,33 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 future platform configuration for gyp_cobalt."""
+
+import logging
+import os
+import sys
+
+# Import the shared Linux platform configuration.
+sys.path.append(
+    os.path.realpath(
+        os.path.join(
+            os.path.dirname(__file__), os.pardir, os.pardir, 'shared')))
+import gyp_configuration
+
+
+def CreatePlatformConfig():
+  try:
+    return gyp_configuration.PlatformConfig('linux-x64x11-future')
+  except RuntimeError as e:
+    logging.critical(e)
+    return None
diff --git a/src/starboard/linux/x64x11/future/starboard_platform.gyp b/src/starboard/linux/x64x11/future/starboard_platform.gyp
new file mode 100644
index 0000000..9648c9d
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/starboard_platform.gyp
@@ -0,0 +1,40 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+  'targets': [
+    {
+      'target_name': 'starboard_platform',
+      'product_name': 'starboard_platform_future',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/starboard/shared/stub/decode_target_create_egl.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_destroy.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_get_format.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_get_plane_egl.cc',
+      ],
+      'defines': [
+        # This must be defined when building Starboard, and must not when
+        # building Starboard client code.
+        'STARBOARD_IMPLEMENTATION',
+      ],
+      'dependencies': [
+        '<(DEPTH)/starboard/common/common.gyp:common',
+        '<(DEPTH)/starboard/linux/x64x11/starboard_platform.gyp:starboard_platform',
+        '<(DEPTH)/starboard/linux/x64x11/starboard_platform.gyp:starboard_base_symbolize',
+        '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/linux/x64x11/future/thread_types_public.h
similarity index 78%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/linux/x64x11/future/thread_types_public.h
index ec9eedf..9ae5da8 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/linux/x64x11/future/thread_types_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_
 
 #include "starboard/linux/shared/thread_types_public.h"
 
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/sanitizer_options.cc b/src/starboard/linux/x64x11/sanitizer_options.cc
new file mode 100644
index 0000000..91d1d26
--- /dev/null
+++ b/src/starboard/linux/x64x11/sanitizer_options.cc
@@ -0,0 +1,39 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Removes gallium leak warnings from x11 GL code, but defines it as a weak
+// symbol, so other code can override it if they want to.
+
+#if defined(ADDRESS_SANITIZER)
+
+// Functions returning default options are declared weak in the tools' runtime
+// libraries. To make the linker pick the strong replacements for those
+// functions from this module, we explicitly force its inclusion by passing
+// -Wl,-u_sanitizer_options_link_helper
+extern "C" void _sanitizer_options_link_helper() { }
+
+#define SANITIZER_HOOK_ATTRIBUTE          \
+  extern "C"                              \
+  __attribute__((no_sanitize_address))    \
+  __attribute__((no_sanitize_memory))     \
+  __attribute__((no_sanitize_thread))     \
+  __attribute__((visibility("default")))  \
+  __attribute__((weak))                   \
+  __attribute__((used))
+
+SANITIZER_HOOK_ATTRIBUTE const char* __lsan_default_suppressions() {
+  return "leak:egl_gallium.so\n";
+}
+
+#endif  // defined(ADDRESS_SANITIZER)
diff --git a/src/starboard/linux/x64x11/starboard_platform.gyp b/src/starboard/linux/x64x11/starboard_platform.gyp
index 6f9e3f8..228ceb0 100644
--- a/src/starboard/linux/x64x11/starboard_platform.gyp
+++ b/src/starboard/linux/x64x11/starboard_platform.gyp
@@ -32,6 +32,7 @@
         '<(DEPTH)/starboard/linux/shared/system_get_path.cc',
         '<(DEPTH)/starboard/linux/shared/system_has_capability.cc',
         '<(DEPTH)/starboard/linux/x64x11/main.cc',
+        '<(DEPTH)/starboard/linux/x64x11/sanitizer_options.cc',
         '<(DEPTH)/starboard/linux/x64x11/system_get_property.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc',
         '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
@@ -226,8 +227,8 @@
         '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
         '<(DEPTH)/starboard/shared/starboard/new.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
diff --git a/src/starboard/linux/x64x11/starboard_platform_tests.gyp b/src/starboard/linux/x64x11/starboard_platform_tests.gyp
new file mode 100644
index 0000000..0250125
--- /dev/null
+++ b/src/starboard/linux/x64x11/starboard_platform_tests.gyp
@@ -0,0 +1,41 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+  'targets': [
+    {
+      'target_name': 'starboard_platform_tests',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        '<(DEPTH)/starboard/common/test_main.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/starboard/starboard.gyp:starboard',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+    },
+    {
+      'target_name': 'starboard_platform_tests_deploy',
+      'type': 'none',
+      'dependencies': [
+        '<(DEPTH)/<(starboard_path)/starboard_platform_tests.gyp:starboard_platform_tests',
+      ],
+      'variables': {
+        'executable_name': 'starboard_platform_tests',
+      },
+      'includes': [ '../../build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/starboard/microphone.h b/src/starboard/microphone.h
index 9ca8816..b742f71 100644
--- a/src/starboard/microphone.h
+++ b/src/starboard/microphone.h
@@ -85,6 +85,9 @@
 
   // Microphone max supported sampling rate.
   int max_sample_rate_hz;
+
+  // The minimum read size required for each read from microphone.
+  int min_read_size;
 } SbMicrophoneInfo;
 
 // An opaque handle to an implementation-private structure representing a
@@ -102,9 +105,11 @@
 // Gets all currently-available microphone information and the results are
 // stored in |out_info_array|. |info_array_size| is the size of
 // |out_info_array|.
-// Return value is the number of the available microphones. A negative return
-// value indicates that either the |info_array_size| is too small or an internal
-// error is occurred.
+// Returns the number of the available microphones. A negative return
+// value indicates that an internal error has occurred. If the number of
+// available microphones is larger than |info_array_size|, |out_info_array| will
+// be filled up with as many available microphones as possible and the actual
+// number of available microphones will be returned.
 SB_EXPORT int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array,
                                        int info_array_size);
 
@@ -153,7 +158,9 @@
 // an error. This function should be called frequently, otherwise microphone
 // only buffers |buffer_size| bytes which is configured in |SbMicrophoneCreate|
 // and the new audio data will be thrown out. No audio data will be read from a
-// stopped microphone.
+// stopped microphone. If |audio_data_size| is smaller than |min_read_size| of
+// |SbMicrophoneInfo|, the extra audio data which is already read from device
+// will be discarded.
 SB_EXPORT int SbMicrophoneRead(SbMicrophone microphone,
                                void* out_audio_data,
                                int audio_data_size);
diff --git a/src/starboard/nplb/decode_target_create_test.cc b/src/starboard/nplb/decode_target_create_test.cc
new file mode 100644
index 0000000..9358c68
--- /dev/null
+++ b/src/starboard/nplb/decode_target_create_test.cc
@@ -0,0 +1,180 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This must come after gtest, because it includes GL, which can include X11,
+// which will define None to be 0L, which conflicts with gtest.
+#include "starboard/decode_target.h"  // NOLINT(build/include_order)
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+#if SB_HAS(BLITTER)
+#include "starboard/blitter.h"
+#include "starboard/nplb/blitter_helpers.h"
+#endif
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+#if SB_HAS(BLITTER)
+const int kWidth = 128;
+const int kHeight = 128;
+
+TEST(SbDecodeTargetTest, SunnyDayCreate) {
+  ContextTestEnvironment env(kWidth, kHeight);
+
+  ASSERT_TRUE(SbBlitterSetRenderTarget(env.context(), env.render_target()));
+
+  SbBlitterSurface surface =
+      CreateArbitraryRenderTargetSurface(env.device(), kWidth, kHeight);
+
+  SbDecodeTarget target =
+      SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA, &surface);
+  EXPECT_TRUE(SbDecodeTargetIsValid(target));
+  if (SbDecodeTargetIsValid(target)) {
+    SbBlitterSurface plane =
+        SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+    EXPECT_TRUE(SbBlitterIsSurfaceValid(plane));
+  }
+  SbDecodeTargetDestroy(target);
+  EXPECT_TRUE(SbBlitterDestroySurface(surface));
+}
+#else  // SB_HAS(BLITTER)
+// clang-format off
+EGLint const kAttributeList[] = {
+  EGL_RED_SIZE, 8,
+  EGL_GREEN_SIZE, 8,
+  EGL_BLUE_SIZE, 8,
+  EGL_ALPHA_SIZE, 8,
+  EGL_STENCIL_SIZE, 0,
+  EGL_BUFFER_SIZE, 32,
+  EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
+  EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
+  EGL_CONFORMANT, EGL_OPENGL_ES2_BIT,
+  EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+  EGL_NONE
+};
+// clang-format on
+
+class SbDecodeTargetTest : public testing::Test {
+ protected:
+  void SetUp() SB_OVERRIDE {
+    SbWindowOptions options;
+    SbWindowSetDefaultOptions(&options);
+    window_ = SbWindowCreate(&options);
+
+    display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    ASSERT_NE(EGL_NO_DISPLAY, display_);
+
+    eglInitialize(display_, NULL, NULL);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+    EGLint num_configs = 0;
+    eglChooseConfig(display_, kAttributeList, NULL, 0, &num_configs);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    ASSERT_NE(0, num_configs);
+
+    // Allocate space to receive the matching configs and retrieve them.
+    EGLConfig* configs = new EGLConfig[num_configs];
+    eglChooseConfig(display_, kAttributeList, configs, num_configs,
+                    &num_configs);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+    EGLNativeWindowType native_window =
+        (EGLNativeWindowType)SbWindowGetPlatformHandle(window_);
+    EGLConfig config;
+
+    // Find the first config that successfully allow a window surface to be
+    // created.
+    surface_ = EGL_NO_SURFACE;
+    for (int config_number = 0; config_number < num_configs; ++config_number) {
+      config = configs[config_number];
+      surface_ = eglCreateWindowSurface(display_, config, native_window, NULL);
+      if (EGL_SUCCESS == eglGetError())
+        break;
+    }
+    ASSERT_NE(EGL_NO_SURFACE, surface_);
+
+    delete[] configs;
+
+    // Create the GLES2 or GLEX3 Context.
+    context_ = EGL_NO_CONTEXT;
+    EGLint context_attrib_list[] = {
+        EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE,
+    };
+#if defined(GLES3_SUPPORTED)
+    // Attempt to create an OpenGL ES 3.0 context.
+    context_ =
+        eglCreateContext(display_, config, EGL_NO_CONTEXT, context_attrib_list);
+#endif
+    if (context_ == EGL_NO_CONTEXT) {
+      // Create an OpenGL ES 2.0 context.
+      context_attrib_list[1] = 2;
+      context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT,
+                                  context_attrib_list);
+    }
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    ASSERT_NE(EGL_NO_CONTEXT, context_);
+
+    // connect the context to the surface
+    eglMakeCurrent(display_, surface_, surface_, context_);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+  }
+
+  void TearDown() SB_OVERRIDE {
+    eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    EXPECT_EQ(EGL_SUCCESS, eglGetError());
+    eglDestroyContext(display_, context_);
+    EXPECT_EQ(EGL_SUCCESS, eglGetError());
+    eglDestroySurface(display_, surface_);
+    EXPECT_EQ(EGL_SUCCESS, eglGetError());
+    eglTerminate(display_);
+    EXPECT_EQ(EGL_SUCCESS, eglGetError());
+    SbWindowDestroy(window_);
+  }
+
+  EGLContext context_;
+  EGLDisplay display_;
+  EGLSurface surface_;
+  SbWindow window_;
+};
+
+TEST_F(SbDecodeTargetTest, SunnyDayCreate) {
+  // Generate a texture to put in the SbDecodeTarget.
+  GLuint texture_handle;
+  glGenTextures(1, &texture_handle);
+  glBindTexture(GL_TEXTURE_2D, texture_handle);
+  glTexImage2D(GL_TEXTURE_2D, 0 /*level*/, GL_RGBA, 256, 256, 0 /*border*/,
+               GL_RGBA8, GL_UNSIGNED_BYTE, NULL /*data*/);
+  glBindTexture(GL_TEXTURE_2D, 0);
+
+  SbDecodeTarget target = SbDecodeTargetCreate(
+      display_, context_, kSbDecodeTargetFormat1PlaneRGBA, &texture_handle);
+  GLuint plane = SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+  EXPECT_EQ(texture_handle, plane);
+  SbDecodeTargetDestroy(target);
+  glDeleteTextures(1, &texture_handle);
+}
+
+#endif  // SB_HAS(BLITTER)
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/decode_target_provider_test.cc b/src/starboard/nplb/decode_target_provider_test.cc
new file mode 100644
index 0000000..a5fece5
--- /dev/null
+++ b/src/starboard/nplb/decode_target_provider_test.cc
@@ -0,0 +1,174 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This must come after gtest, because it includes GL, which can include X11,
+// which will define None to be 0L, which conflicts with gtest.
+#include "starboard/decode_target.h"  // NOLINT(build/include_order)
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+struct ProviderContext {
+  static SbDecodeTarget Acquire(void* raw_context,
+                                SbDecodeTargetFormat format,
+                                int width,
+                                int height) {
+    ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context);
+    ++context->called_acquire;
+    return kSbDecodeTargetInvalid;
+  }
+
+  static void Release(void* raw_context, SbDecodeTarget target) {
+    ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context);
+    ++context->called_release;
+  }
+
+  int called_acquire;
+  int called_release;
+};
+
+template <typename T>
+SbDecodeTargetProvider MakeProvider() {
+  return {&T::Acquire, &T::Release};
+}
+
+void AcquireFalse() {
+  SbDecodeTarget target = SbDecodeTargetAcquireFromProvider(
+      kSbDecodeTargetFormat1PlaneRGBA, 16, 16);
+  bool valid = SbDecodeTargetIsValid(target);
+  EXPECT_FALSE(valid);
+  if (valid) {
+    SbDecodeTargetDestroy(target);
+  }
+}
+
+void ReleaseInvalid() {
+  SbDecodeTargetReleaseToProvider(kSbDecodeTargetInvalid);
+}
+
+TEST(SbDecodeTargetProviderTest, SunnyDayRegisterUnregister) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = MakeProvider<ProviderContext>();
+  ASSERT_TRUE(SbDecodeTargetRegisterProvider(&provider, &context));
+  SbDecodeTargetUnregisterProvider(&provider, &context);
+}
+
+TEST(SbDecodeTargetProviderTest, SunnyDayCallsThroughToProvider) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = MakeProvider<ProviderContext>();
+  ASSERT_TRUE(SbDecodeTargetRegisterProvider(&provider, &context));
+  EXPECT_EQ(0, context.called_acquire);
+  EXPECT_EQ(0, context.called_release);
+
+  AcquireFalse();
+  EXPECT_EQ(1, context.called_acquire);
+  AcquireFalse();
+  EXPECT_EQ(2, context.called_acquire);
+
+  ReleaseInvalid();
+  EXPECT_EQ(1, context.called_release);
+  ReleaseInvalid();
+  EXPECT_EQ(2, context.called_release);
+
+  SbDecodeTargetUnregisterProvider(&provider, &context);
+}
+
+TEST(SbDecodeTargetProviderTest, SunnyDayRegisterOver) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = MakeProvider<ProviderContext>();
+  ASSERT_TRUE(SbDecodeTargetRegisterProvider(&provider, &context));
+
+  ProviderContext context2 = {};
+  SbDecodeTargetProvider provider2 = MakeProvider<ProviderContext>();
+  ASSERT_TRUE(SbDecodeTargetRegisterProvider(&provider2, &context2));
+
+  AcquireFalse();
+  EXPECT_EQ(1, context2.called_acquire);
+  EXPECT_EQ(0, context.called_acquire);
+  AcquireFalse();
+  EXPECT_EQ(2, context2.called_acquire);
+  EXPECT_EQ(0, context.called_acquire);
+
+  ReleaseInvalid();
+  EXPECT_EQ(1, context2.called_release);
+  EXPECT_EQ(0, context.called_release);
+  ReleaseInvalid();
+  EXPECT_EQ(2, context2.called_release);
+  EXPECT_EQ(0, context.called_release);
+
+  SbDecodeTargetUnregisterProvider(&provider2, &context2);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayAcquireNoProvider) {
+  AcquireFalse();
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayReleaseNoProvider) {
+  ReleaseInvalid();
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayUnregisterUnregistered) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = MakeProvider<ProviderContext>();
+  SbDecodeTargetUnregisterProvider(&provider, &context);
+
+  ProviderContext context2 = {};
+  SbDecodeTargetProvider provider2 = MakeProvider<ProviderContext>();
+  ASSERT_TRUE(SbDecodeTargetRegisterProvider(&provider2, &context2));
+  SbDecodeTargetUnregisterProvider(&provider, &context);
+  SbDecodeTargetUnregisterProvider(&provider, &context2);
+  SbDecodeTargetUnregisterProvider(&provider2, &context);
+
+  AcquireFalse();
+  EXPECT_EQ(1, context2.called_acquire);
+  AcquireFalse();
+  EXPECT_EQ(2, context2.called_acquire);
+
+  ReleaseInvalid();
+  EXPECT_EQ(1, context2.called_release);
+  ReleaseInvalid();
+  EXPECT_EQ(2, context2.called_release);
+
+  SbDecodeTargetUnregisterProvider(&provider2, &context2);
+
+  AcquireFalse();
+  EXPECT_EQ(2, context2.called_acquire);
+  AcquireFalse();
+  EXPECT_EQ(2, context2.called_acquire);
+
+  ReleaseInvalid();
+  EXPECT_EQ(2, context2.called_release);
+  ReleaseInvalid();
+  EXPECT_EQ(2, context2.called_release);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayUnregisterNull) {
+  ProviderContext context = {};
+  SbDecodeTargetUnregisterProvider(NULL, &context);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayUnregisterNullNull) {
+  SbDecodeTargetUnregisterProvider(NULL, NULL);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/include_all.c b/src/starboard/nplb/include_all.c
index 74f0e84..5ac5aa1 100644
--- a/src/starboard/nplb/include_all.c
+++ b/src/starboard/nplb/include_all.c
@@ -21,6 +21,7 @@
 #include "starboard/character.h"
 #include "starboard/condition_variable.h"
 #include "starboard/configuration.h"
+#include "starboard/decode_target.h"
 #include "starboard/directory.h"
 #include "starboard/double.h"
 #include "starboard/drm.h"
diff --git a/src/starboard/nplb/microphone_get_available_test.cc b/src/starboard/nplb/microphone_get_available_test.cc
index 14a7ed0..232995b 100644
--- a/src/starboard/nplb/microphone_get_available_test.cc
+++ b/src/starboard/nplb/microphone_get_available_test.cc
@@ -32,14 +32,16 @@
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
   if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
     int available_microphones = SbMicrophoneGetAvailable(info_array, 0);
-    EXPECT_LE(available_microphones, 0);
+    EXPECT_GT(available_microphones, 0);
   }
 }
 
 TEST(SbMicrophoneGetAvailableTest, RainyDayNegativeNumberOfMicrophone) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
-  int available_microphones = SbMicrophoneGetAvailable(info_array, -10);
-  EXPECT_LT(available_microphones, 0);
+  if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
+    int available_microphones = SbMicrophoneGetAvailable(info_array, -10);
+    EXPECT_GT(available_microphones, 0);
+  }
 }
 
 TEST(SbMicrophoneGetAvailableTest, RainyDayNULLInfoArray) {
@@ -47,7 +49,7 @@
   if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
     int available_microphones =
         SbMicrophoneGetAvailable(NULL, kMaxNumberOfMicrophone);
-    EXPECT_LT(available_microphones, 0);
+    EXPECT_GT(available_microphones, 0);
   }
 }
 
diff --git a/src/starboard/nplb/microphone_read_test.cc b/src/starboard/nplb/microphone_read_test.cc
index 6f80656..7df0260 100644
--- a/src/starboard/nplb/microphone_read_test.cc
+++ b/src/starboard/nplb/microphone_read_test.cc
@@ -35,16 +35,41 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    bool success = SbMicrophoneOpen(microphone);
-    ASSERT_TRUE(success);
+    ASSERT_TRUE(SbMicrophoneOpen(microphone));
 
-    void* audio_data[1024];
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes, 0);
     int read_bytes =
-        SbMicrophoneRead(microphone, audio_data, sizeof(audio_data));
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
     EXPECT_GE(read_bytes, 0);
 
-    success = SbMicrophoneClose(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, SunnyDayReadIsLargerThanMinReadSize) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    ASSERT_TRUE(SbMicrophoneIsSampleRateSupported(
+        info_array[0].id, info_array[0].max_sample_rate_hz));
+    SbMicrophone microphone = SbMicrophoneCreate(
+        info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
+    ASSERT_TRUE(SbMicrophoneIsValid(microphone));
+
+    ASSERT_TRUE(SbMicrophoneOpen(microphone));
+
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes * 2, 0);
+    int read_bytes =
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
+    EXPECT_GE(read_bytes, 0);
+
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
     SbMicrophoneDestroy(microphone);
   }
 }
@@ -62,22 +87,19 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    bool success = SbMicrophoneOpen(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
 
     SbThreadSleep(50 * kSbTimeMillisecond);
 
-    success = SbMicrophoneClose(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
 
-    success = SbMicrophoneOpen(microphone);
-    EXPECT_TRUE(success);
-
-    void* audio_data[16 * 1024];
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes, 0);
     int read_bytes =
-        SbMicrophoneRead(microphone, audio_data, sizeof(audio_data));
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
     EXPECT_GE(read_bytes, 0);
-    EXPECT_LT(read_bytes, sizeof(audio_data));
+    EXPECT_LT(read_bytes, audio_data.size());
 
     SbMicrophoneDestroy(microphone);
   }
@@ -96,14 +118,12 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    bool success = SbMicrophoneOpen(microphone);
-    ASSERT_TRUE(success);
+    ASSERT_TRUE(SbMicrophoneOpen(microphone));
 
     int read_bytes = SbMicrophoneRead(microphone, NULL, 0);
     EXPECT_EQ(read_bytes, 0);
 
-    success = SbMicrophoneClose(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
     SbMicrophoneDestroy(microphone);
   }
 }
@@ -121,14 +141,38 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    bool success = SbMicrophoneOpen(microphone);
-    ASSERT_TRUE(success);
+    ASSERT_TRUE(SbMicrophoneOpen(microphone));
 
     int read_bytes = SbMicrophoneRead(microphone, NULL, 1024);
     EXPECT_LE(read_bytes, 0);
 
-    success = SbMicrophoneClose(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayAudioBufferSizeIsSmallerThanMinReadSize) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    ASSERT_TRUE(SbMicrophoneIsSampleRateSupported(
+        info_array[0].id, info_array[0].max_sample_rate_hz));
+    SbMicrophone microphone = SbMicrophoneCreate(
+        info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
+    ASSERT_TRUE(SbMicrophoneIsValid(microphone));
+
+    ASSERT_TRUE(SbMicrophoneOpen(microphone));
+
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes / 2, 0);
+    int read_bytes =
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
+    EXPECT_GE(read_bytes, 0);
+
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
     SbMicrophoneDestroy(microphone);
   }
 }
@@ -146,10 +190,12 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    void* audio_data[1024];
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes, 0);
     int read_bytes =
-        SbMicrophoneRead(microphone, audio_data, sizeof(audio_data));
-    EXPECT_EQ(read_bytes, 0);
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
+    // An error should have occurred because open was not called.
+    EXPECT_LT(read_bytes, 0);
 
     SbMicrophoneDestroy(microphone);
   }
@@ -168,26 +214,24 @@
         info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
     ASSERT_TRUE(SbMicrophoneIsValid(microphone));
 
-    bool success = SbMicrophoneOpen(microphone);
-    EXPECT_TRUE(success);
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
 
-    success = SbMicrophoneClose(microphone);
-    EXPECT_TRUE(success);
-
-    void* audio_data[1024];
+    int requested_bytes = info_array[0].min_read_size;
+    std::vector<uint8_t> audio_data(requested_bytes, 0);
     int read_bytes =
-        SbMicrophoneRead(microphone, audio_data, sizeof(audio_data));
-    // No data can be read.
-    EXPECT_EQ(read_bytes, 0);
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
+    // An error should have occurred because the microphone was closed.
+    EXPECT_LT(read_bytes, 0);
 
     SbMicrophoneDestroy(microphone);
   }
 }
 
 TEST(SbMicrophoneReadTest, RainyDayMicrophoneIsInvalid) {
-  void* audio_data[1024];
+  std::vector<uint8_t> audio_data(1024, 0);
   int read_bytes =
-      SbMicrophoneRead(kSbMicrophoneInvalid, audio_data, sizeof(audio_data));
+      SbMicrophoneRead(kSbMicrophoneInvalid, &audio_data[0], audio_data.size());
   EXPECT_LT(read_bytes, 0);
 }
 
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 437cc92..cf31d23 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -21,6 +21,7 @@
       'target_name': 'nplb',
       'type': '<(gtest_target_type)',
       'sources': [
+        '<(DEPTH)/starboard/common/test_main.cc',
         'atomic_test.cc',
         'audio_sink_create_test.cc',
         'audio_sink_destroy_test.cc',
@@ -78,6 +79,8 @@
         'condition_variable_wait_test.cc',
         'condition_variable_wait_timed_test.cc',
         'configuration_test.cc',
+        'decode_target_create_test.cc',
+        'decode_target_provider_test.cc',
         'directory_can_open_test.cc',
         'directory_close_test.cc',
         'directory_create_test.cc',
@@ -107,7 +110,6 @@
         'log_raw_dump_stack_test.cc',
         'log_raw_test.cc',
         'log_test.cc',
-        'main.cc',
         'memory_align_to_page_size_test.cc',
         'memory_allocate_aligned_test.cc',
         'memory_allocate_test.cc',
diff --git a/src/starboard/nplb/socket_helpers.h b/src/starboard/nplb/socket_helpers.h
index 5b4e5d7..a01c02d 100644
--- a/src/starboard/nplb/socket_helpers.h
+++ b/src/starboard/nplb/socket_helpers.h
@@ -23,7 +23,7 @@
 namespace starboard {
 namespace nplb {
 
-const SbTime kSocketTimeout = kSbTimeSecond / 8;
+const SbTime kSocketTimeout = kSbTimeSecond / 5;
 
 // Creates a plain TCP/IPv4 socket.
 static inline SbSocket CreateTcpIpv4Socket() {
diff --git a/src/starboard/nplb/socket_waiter_create_test.cc b/src/starboard/nplb/socket_waiter_create_test.cc
index b14882a..6190e64 100644
--- a/src/starboard/nplb/socket_waiter_create_test.cc
+++ b/src/starboard/nplb/socket_waiter_create_test.cc
@@ -26,7 +26,7 @@
 }
 
 TEST(SbSocketWaiterCreateTest, ATon) {
-  const int kATon = 4096;
+  const int kATon = 256;
   for (int i = 0; i < kATon; ++i) {
     SbSocketWaiter waiter = SbSocketWaiterCreate();
     EXPECT_TRUE(SbSocketWaiterIsValid(waiter));
diff --git a/src/starboard/raspi/1/starboard_platform.gyp b/src/starboard/raspi/1/starboard_platform.gyp
index b866ee7..c33f33d 100644
--- a/src/starboard/raspi/1/starboard_platform.gyp
+++ b/src/starboard/raspi/1/starboard_platform.gyp
@@ -33,6 +33,7 @@
         '<(DEPTH)/starboard/linux/shared/system_has_capability.cc',
         '<(DEPTH)/starboard/raspi/1/system_get_property.cc',
         '<(DEPTH)/starboard/raspi/shared/application_dispmanx.cc',
+        '<(DEPTH)/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc',
         '<(DEPTH)/starboard/raspi/shared/main.cc',
         '<(DEPTH)/starboard/raspi/shared/window_create.cc',
         '<(DEPTH)/starboard/raspi/shared/window_destroy.cc',
@@ -46,7 +47,6 @@
         '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc',
         '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc',
         '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc',
-        '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc',
         '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc',
@@ -233,8 +233,8 @@
         '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
         '<(DEPTH)/starboard/shared/starboard/new.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
diff --git a/src/starboard/raspi/directfb/README.md b/src/starboard/raspi/directfb/README.md
index 4d9d9be..a4b4ec7 100644
--- a/src/starboard/raspi/directfb/README.md
+++ b/src/starboard/raspi/directfb/README.md
@@ -31,7 +31,7 @@
 
 ## Modifications to /etc/directfbrc
 
-Open or create 'etc/directfbrc' (as root) and add the lines:
+Open or create '/etc/directfbrc' (as root) and add the lines:
 
     graphics-vt
     hardware
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
similarity index 74%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
index 3b48d2e..f5026b8 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#include "starboard/audio_sink.h"
 
-#include "starboard/linux/shared/atomic_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+bool SbAudioSinkIsAudioSampleTypeSupported(
+    SbMediaAudioSampleType audio_sample_type) {
+  return audio_sample_type == kSbMediaAudioSampleTypeInt16;
+}
diff --git a/src/starboard/shared/alsa/alsa_util.cc b/src/starboard/shared/alsa/alsa_util.cc
index c713015..0568156 100644
--- a/src/starboard/shared/alsa/alsa_util.cc
+++ b/src/starboard/shared/alsa/alsa_util.cc
@@ -35,6 +35,9 @@
 
 namespace {
 
+const snd_pcm_uframes_t kSilenceThresholdInFrames = 256U;
+const snd_pcm_uframes_t kStartThresholdInFrames = 1024U;
+
 template <typename T, typename CloseFunc>
 class AutoClose {
  public:
@@ -145,19 +148,20 @@
                                           frames_per_request);
   ALSA_CHECK(error, snd_pcm_sw_params_set_avail_min, NULL);
 
-  error =
-      snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params, 256U);
+  error = snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params,
+                                                  kSilenceThresholdInFrames);
   ALSA_CHECK(error, snd_pcm_sw_params_set_silence_threshold, NULL);
 
+  error = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,
+                                                kStartThresholdInFrames);
+  ALSA_CHECK(error, snd_pcm_sw_params_set_start_threshold, NULL);
+
   error = snd_pcm_sw_params(playback_handle, sw_params);
   ALSA_CHECK(error, snd_pcm_sw_params, NULL);
 
   error = snd_pcm_prepare(playback_handle);
   ALSA_CHECK(error, snd_pcm_prepare, NULL);
 
-  error = snd_pcm_start(playback_handle);
-  ALSA_CHECK(error, snd_pcm_start, NULL);
-
   return playback_handle.Detach();
 }
 
diff --git a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
index 9bfd9a7..0f417fd 100644
--- a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
+++ b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -16,10 +16,10 @@
 
 #include "starboard/character.h"
 #include "starboard/log.h"
-#include "starboard/shared/starboard/media/mime_parser.h"
+#include "starboard/shared/starboard/media/mime_type.h"
 #include "starboard/string.h"
 
-using starboard::shared::starboard::media::MimeParser;
+using starboard::shared::starboard::media::MimeType;
 
 SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime,
                                                   const char* key_system) {
@@ -37,27 +37,32 @@
       return kSbMediaSupportTypeNotSupported;
     }
   }
-  MimeParser parser(mime);
-  if (!parser.is_valid()) {
+  MimeType mime_type(mime);
+  if (!mime_type.is_valid()) {
     SB_DLOG(WARNING) << mime << " is not a valid mime type";
     return kSbMediaSupportTypeNotSupported;
   }
-  if (parser.mime() == "application/octet-stream") {
+  int codecs_index = mime_type.GetParamIndexByName("codecs");
+  if (codecs_index != MimeType::kInvalidParamIndex && codecs_index != 0) {
+    SB_DLOG(WARNING) << mime << " is not a valid mime type";
+    return kSbMediaSupportTypeNotSupported;
+  }
+  if (mime_type.type() == "application" &&
+      mime_type.subtype() == "octet-stream") {
     return kSbMediaSupportTypeProbably;
   }
-  if (parser.mime() == "audio/mp4") {
+  if (mime_type.type() == "audio" && mime_type.subtype() == "mp4") {
     return kSbMediaSupportTypeProbably;
   }
-  if (parser.mime() == "video/mp4") {
+  if (mime_type.type() == "video" && mime_type.subtype() == "mp4") {
     return kSbMediaSupportTypeProbably;
   }
 #if SB_HAS(MEDIA_WEBM_VP9_SUPPORT)
-  if (parser.mime() == "video/webm") {
-    if (!parser.HasParam("codecs")) {
+  if (mime_type.type() == "video" && mime_type.subtype() == "webm") {
+    if (codecs_index == MimeType::kInvalidParamIndex) {
       return kSbMediaSupportTypeMaybe;
     }
-    std::string codecs = parser.GetParam("codecs");
-    if (codecs == "vp9") {
+    if (mime_type.GetParamStringValue(0) == "vp9") {
       return kSbMediaSupportTypeProbably;
     }
     return kSbMediaSupportTypeNotSupported;
diff --git a/src/starboard/shared/starboard/media/mime_parser.cc b/src/starboard/shared/starboard/media/mime_parser.cc
deleted file mode 100644
index 7db734a..0000000
--- a/src/starboard/shared/starboard/media/mime_parser.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/shared/starboard/media/mime_parser.h"
-
-#include <vector>
-
-#include "starboard/character.h"
-#include "starboard/log.h"
-#include "starboard/string.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace media {
-
-namespace {
-
-void ToLower(std::string* string) {
-  SB_DCHECK(string);
-  for (size_t i = 0; i < string->size(); ++i) {
-    (*string)[i] = SbCharacterToLower((*string)[i]);
-  }
-}
-
-void Trim(std::string* string) {
-  SB_DCHECK(string);
-  while (!string->empty() && SbCharacterIsSpace(*string->rbegin())) {
-    string->resize(string->size() - 1);
-  }
-  while (!string->empty() && SbCharacterIsSpace(*string->begin())) {
-    string->erase(string->begin());
-  }
-}
-
-bool IsSeparator(char ch, std::string separator) {
-  if (separator.empty()) {
-    return SbCharacterIsSpace(ch);
-  }
-  return separator.find(ch) != separator.npos;
-}
-
-// For any given string, split it into substrings separated by any character in
-// the |separator| string.  If |separator| is empty string, treat any white
-// space as separators.
-std::vector<std::string> SplitString(std::string string,
-                                     std::string separator = "") {
-  std::vector<std::string> result;
-  while (!string.empty()) {
-    Trim(&string);
-    if (string.empty()) {
-      break;
-    }
-    bool added = false;
-    for (size_t i = 0; i < string.size(); ++i) {
-      if (IsSeparator(string[i], separator)) {
-        result.push_back(string.substr(0, i));
-        Trim(&result.back());
-        string = string.substr(i + 1);
-        added = true;
-        break;
-      }
-    }
-    if (!added) {
-      Trim(&string);
-      result.push_back(string);
-      break;
-    }
-  }
-  return result;
-}
-
-}  // namespace
-
-MimeParser::MimeParser(std::string mime_with_params) {
-  Trim(&mime_with_params);
-  ToLower(&mime_with_params);
-
-  std::vector<std::string> splits = SplitString(mime_with_params, ";");
-  if (splits.empty()) {
-    return;  // It is invalid, leave |mime_| as empty.
-  }
-  mime_ = splits[0];
-  // Mime is in the form of "video/mp4".
-  if (SplitString(mime_, "/").size() != 2) {
-    mime_.clear();
-    return;
-  }
-  splits.erase(splits.begin());
-
-  while (!splits.empty()) {
-    std::string params = *splits.begin();
-    splits.erase(splits.begin());
-
-    // Param is in the form of 'name=value' or 'name="value"'.
-    std::vector<std::string> name_and_value = SplitString(params, "=");
-    if (name_and_value.size() != 2) {
-      mime_.clear();
-      return;
-    }
-    std::string name = name_and_value[0];
-    std::string value = name_and_value[1];
-
-    if (value.size() >= 2 && value[0] == '\"' &&
-        value[value.size() - 1] == '\"') {
-      value.erase(value.begin());
-      value.resize(value.size() - 1);
-      Trim(&value);
-    }
-
-    params_[name] = value;
-  }
-}
-
-bool MimeParser::HasParam(const std::string& name) const {
-  return params_.find(name) != params_.end();
-}
-
-std::string MimeParser::GetParam(const std::string& name) const {
-  std::map<std::string, std::string>::const_iterator iter = params_.find(name);
-  if (iter == params_.end()) {
-    return "";
-  }
-  return iter->second;
-}
-
-}  // namespace media
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
diff --git a/src/starboard/shared/starboard/media/mime_parser.h b/src/starboard/shared/starboard/media/mime_parser.h
deleted file mode 100644
index b896b2d..0000000
--- a/src/starboard/shared/starboard/media/mime_parser.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_
-#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_
-
-#include <map>
-#include <string>
-
-#include "starboard/shared/internal_only.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace media {
-
-// This class can be used to parse media mime types with params.  For example,
-// the following mime type 'video/mp4; codecs="avc1.4d401e"; width=640' will be
-// parsed into:
-//   mime => video/mp4
-//     param: codecs => avc1.4d401e
-//     param: width  => 640
-class MimeParser {
- public:
-  explicit MimeParser(std::string mime_with_params);
-
-  bool is_valid() const { return !mime_.empty(); }
-  const std::string& mime() const { return mime_; }
-
-  bool HasParam(const std::string& name) const;
-  std::string GetParam(const std::string& name) const;
-
- private:
-  std::string mime_;
-  std::map<std::string, std::string> params_;
-};
-
-}  // namespace media
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_
diff --git a/src/starboard/shared/starboard/media/mime_type.cc b/src/starboard/shared/starboard/media/mime_type.cc
new file mode 100644
index 0000000..d77b2d5
--- /dev/null
+++ b/src/starboard/shared/starboard/media/mime_type.cc
@@ -0,0 +1,217 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/mime_type.h"
+
+#include "starboard/character.h"
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+typedef std::vector<std::string> Strings;
+
+MimeType::ParamType GetParamTypeByValue(const std::string& value) {
+  int count;
+  int i;
+  if (SbStringScanF(value.c_str(), "%d%n", &i, &count) == 1 &&
+      count == value.size()) {
+    return MimeType::kParamTypeInteger;
+  }
+  float f;
+  if (SbStringScanF(value.c_str(), "%g%n", &f, &count) == 1 &&
+      count == value.size()) {
+    return MimeType::kParamTypeFloat;
+  }
+  return MimeType::kParamTypeString;
+}
+
+bool ContainsSpace(const std::string& str) {
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (SbCharacterIsSpace(str[i])) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void Trim(std::string* str) {
+  while (!str->empty() && SbCharacterIsSpace(*str->begin())) {
+    str->erase(str->begin());
+  }
+  while (!str->empty() && SbCharacterIsSpace(*str->rbegin())) {
+    str->resize(str->size() - 1);
+  }
+}
+
+Strings SplitAndTrim(const std::string& str, char ch) {
+  Strings result;
+  size_t pos = 0;
+
+  for (;;) {
+    size_t next = str.find(ch, pos);
+    result.push_back(str.substr(pos, next - pos));
+    Trim(&result.back());
+    if (next == str.npos) {
+      break;
+    }
+    pos = next + 1;
+  }
+
+  return result;
+}
+
+}  // namespace
+
+const int MimeType::kInvalidParamIndex = -1;
+
+MimeType::MimeType(const std::string& content_type) : is_valid_(false) {
+  Strings components = SplitAndTrim(content_type, ';');
+
+  SB_DCHECK(!components.empty());
+
+  // 1. Verify if there is a valid type/subtype in the very beginning.
+  if (ContainsSpace(components.front())) {
+    return;
+  }
+
+  std::vector<std::string> type_and_container =
+      SplitAndTrim(components.front(), '/');
+  if (type_and_container.size() != 2 || type_and_container[0].empty() ||
+      type_and_container[1].empty()) {
+    return;
+  }
+  type_ = type_and_container[0];
+  subtype_ = type_and_container[1];
+  components.erase(components.begin());
+
+  // 2. Verify the parameters have valid formats, we want to be strict here.
+  for (Strings::iterator iter = components.begin(); iter != components.end();
+       ++iter) {
+    std::vector<std::string> name_and_value = SplitAndTrim(*iter, '=');
+    if (name_and_value.size() != 2 || name_and_value[0].empty() ||
+        name_and_value[1].empty()) {
+      return;
+    }
+    Param param;
+    if (name_and_value[1].size() > 2 && name_and_value[1][0] == '\"' &&
+        *name_and_value[1].rbegin() == '\"') {
+      param.type = kParamTypeString;
+      param.value = name_and_value[1].substr(1, name_and_value[1].size() - 2);
+    } else {
+      param.type = GetParamTypeByValue(name_and_value[1]);
+      param.value = name_and_value[1];
+    }
+    param.name = name_and_value[0];
+    params_.push_back(param);
+  }
+
+  is_valid_ = true;
+}
+
+int MimeType::GetParamCount() const {
+  SB_DCHECK(is_valid());
+
+  return static_cast<int>(params_.size());
+}
+
+MimeType::ParamType MimeType::GetParamType(int index) const {
+  SB_DCHECK(is_valid());
+  SB_DCHECK(index < GetParamCount());
+
+  return params_[index].type;
+}
+
+const std::string& MimeType::GetParamName(int index) const {
+  SB_DCHECK(is_valid());
+  SB_DCHECK(index < GetParamCount());
+
+  return params_[index].name;
+}
+
+int MimeType::GetParamIntValue(int index) const {
+  SB_DCHECK(is_valid());
+  SB_DCHECK(index < GetParamCount());
+  SB_DCHECK(GetParamType(index) == kParamTypeInteger);
+
+  int i;
+  SbStringScanF(params_[index].value.c_str(), "%d", &i);
+  return i;
+}
+
+float MimeType::GetParamFloatValue(int index) const {
+  SB_DCHECK(is_valid());
+  SB_DCHECK(index < GetParamCount());
+  SB_DCHECK(GetParamType(index) == kParamTypeInteger ||
+            GetParamType(index) == kParamTypeFloat);
+
+  float f;
+  SbStringScanF(params_[index].value.c_str(), "%g", &f);
+
+  return f;
+}
+
+const std::string& MimeType::GetParamStringValue(int index) const {
+  SB_DCHECK(is_valid());
+  SB_DCHECK(index < GetParamCount());
+
+  return params_[index].value;
+}
+
+int MimeType::GetParamIntValue(const char* name, int default_value) const {
+  int index = GetParamIndexByName(name);
+  if (index != kInvalidParamIndex) {
+    return GetParamIntValue(index);
+  }
+  return default_value;
+}
+
+float MimeType::GetParamFloatValue(const char* name,
+                                   float default_value) const {
+  int index = GetParamIndexByName(name);
+  if (index != kInvalidParamIndex) {
+    return GetParamFloatValue(index);
+  }
+  return default_value;
+}
+
+const std::string& MimeType::GetParamStringValue(
+    const char* name,
+    const std::string& default_value) const {
+  int index = GetParamIndexByName(name);
+  if (index != kInvalidParamIndex) {
+    return GetParamStringValue(index);
+  }
+  return default_value;
+}
+
+int MimeType::GetParamIndexByName(const char* name) const {
+  for (size_t i = 0; i < params_.size(); ++i) {
+    if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) {
+      return static_cast<int>(i);
+    }
+  }
+  return kInvalidParamIndex;
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/media/mime_type.h b/src/starboard/shared/starboard/media/mime_type.h
new file mode 100644
index 0000000..eb5b2f2
--- /dev/null
+++ b/src/starboard/shared/starboard/media/mime_type.h
@@ -0,0 +1,95 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_
+
+#include <string>
+#include <vector>
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+// This class can be used to parse a content type for media in the form of
+// "type/subtype; param1=value1; param2="value2".  For example, the content type
+// "video/webm; codecs="vp9"; width=1920; height=1080; framerate=59.96" will be
+// parsed into:
+//   type: video
+//   subtype: webm
+//   codecs: vp9
+//   width: 1920
+//   height: 1080
+//   framerate: 59.96
+//
+// The following are the restrictions on the components:
+// 1. Type/subtype has to be in the very beginning.
+// 2. String values may be double quoted.
+// 3. Numeric values cannot be double quoted.
+class MimeType {
+ public:
+  enum ParamType {
+    kParamTypeInteger,
+    kParamTypeFloat,
+    kParamTypeString,
+  };
+
+  static const int kInvalidParamIndex;
+
+  explicit MimeType(const std::string& content_type);
+
+  bool is_valid() const { return is_valid_; }
+
+  const std::string& type() const { return type_; }
+  const std::string& subtype() const { return subtype_; }
+
+  int GetParamIndexByName(const char* name) const;
+  int GetParamCount() const;
+  ParamType GetParamType(int index) const;
+  const std::string& GetParamName(int index) const;
+
+  int GetParamIntValue(int index) const;
+  float GetParamFloatValue(int index) const;
+  const std::string& GetParamStringValue(int index) const;
+
+  int GetParamIntValue(const char* name, int default_value) const;
+  float GetParamFloatValue(const char* name, float default_value) const;
+  const std::string& GetParamStringValue(
+      const char* name,
+      const std::string& default_value) const;
+
+ private:
+  struct Param {
+    ParamType type;
+    std::string name;
+    std::string value;
+  };
+
+  // Use std::vector as the number of components are usually small and we'd like
+  // to keep the order of components.
+  typedef std::vector<Param> Params;
+
+  bool is_valid_;
+  std::string type_;
+  std::string subtype_;
+  Params params_;
+};
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_
diff --git a/src/starboard/shared/starboard/media/mime_type_test.cc b/src/starboard/shared/starboard/media/mime_type_test.cc
new file mode 100644
index 0000000..44d0371
--- /dev/null
+++ b/src/starboard/shared/starboard/media/mime_type_test.cc
@@ -0,0 +1,269 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/mime_type.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+namespace {
+
+TEST(MimeTypeTest, EmptyString) {
+  MimeType mime_type("");
+  EXPECT_FALSE(mime_type.is_valid());
+}
+
+// Valid mime type must have a type/subtype without space in between.
+TEST(MimeTypeTest, InvalidType) {
+  {
+    MimeType mime_type("video");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("video/");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("/mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("video /mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("video/ mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("video / mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+}
+
+TEST(MimeTypeTest, ValidContentTypeWithTypeAndSubtypeOnly) {
+  {
+    MimeType mime_type("video/mp4");
+    EXPECT_TRUE(mime_type.is_valid());
+    EXPECT_EQ("video", mime_type.type());
+    EXPECT_EQ("mp4", mime_type.subtype());
+  }
+
+  {
+    MimeType mime_type("audio/mp4");
+    EXPECT_TRUE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("abc/xyz");
+    EXPECT_TRUE(mime_type.is_valid());
+  }
+}
+
+TEST(MimeTypeTest, TypeNotAtBeginning) {
+  {
+    MimeType mime_type(";video/mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("codecs=\"abc\"; audio/mp4");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+}
+
+TEST(MimeTypeTest, EmptyComponent) {
+  {
+    MimeType mime_type("video/mp4;");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("video/mp4;;");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("audio/mp4; codecs=\"abc\";");
+    EXPECT_FALSE(mime_type.is_valid());
+  }
+}
+
+TEST(MimeTypeTest, ValidContentTypeWithParams) {
+  {
+    MimeType mime_type("video/mp4; name=123");
+    EXPECT_TRUE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("audio/mp4;codecs=\"abc\"");
+    EXPECT_TRUE(mime_type.is_valid());
+  }
+
+  {
+    MimeType mime_type("  audio/mp4  ;  codecs   =  \"abc\"  ");
+    EXPECT_TRUE(mime_type.is_valid());
+  }
+}
+
+TEST(MimeTypeTest, GetParamIndexByName) {
+  MimeType mime_type("video/mp4; name=123");
+  EXPECT_EQ(MimeType::kInvalidParamIndex,
+            mime_type.GetParamIndexByName("video"));
+  EXPECT_EQ(MimeType::kInvalidParamIndex, mime_type.GetParamIndexByName("mp4"));
+  EXPECT_EQ(0, mime_type.GetParamIndexByName("name"));
+}
+
+TEST(MimeTypeTest, ParamCount) {
+  {
+    MimeType mime_type("video/mp4");
+    EXPECT_EQ(0, mime_type.GetParamCount());
+  }
+
+  {
+    MimeType mime_type("video/mp4; width=1920");
+    EXPECT_EQ(1, mime_type.GetParamCount());
+  }
+
+  {
+    MimeType mime_type("video/mp4; width=1920; height=1080");
+    EXPECT_EQ(2, mime_type.GetParamCount());
+  }
+}
+
+TEST(MimeTypeTest, GetParamType) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=1.2; name2=xyz");
+    EXPECT_EQ(MimeType::kParamTypeInteger, mime_type.GetParamType(0));
+    EXPECT_EQ(MimeType::kParamTypeFloat, mime_type.GetParamType(1));
+    EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(2));
+  }
+
+  {
+    MimeType mime_type("video/mp4; name0=\"123\"; name1=\"abc\"");
+    EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(0));
+    EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(1));
+  }
+
+  {
+    MimeType mime_type("video/mp4; name1=\" abc \"");
+    EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(0));
+  }
+}
+
+TEST(MimeTypeTest, GetParamName) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=1.2; name2=xyz");
+    EXPECT_EQ("name0", mime_type.GetParamName(0));
+    EXPECT_EQ("name1", mime_type.GetParamName(1));
+    EXPECT_EQ("name2", mime_type.GetParamName(2));
+  }
+}
+
+TEST(MimeTypeTest, GetParamIntValueWithIndex) {
+  {
+    MimeType mime_type("video/mp4; name=123");
+    EXPECT_EQ(123, mime_type.GetParamIntValue(0));
+  }
+
+  {
+    MimeType mime_type("video/mp4; width=1920; height=1080");
+    EXPECT_EQ(1920, mime_type.GetParamIntValue(0));
+    EXPECT_EQ(1080, mime_type.GetParamIntValue(1));
+  }
+
+  {
+    MimeType mime_type("audio/mp4; channels=6");
+    EXPECT_EQ(6, mime_type.GetParamIntValue(0));
+  }
+}
+
+TEST(MimeTypeTest, GetParamFloatValueWithIndex) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=123.4");
+    EXPECT_FLOAT_EQ(123., mime_type.GetParamFloatValue(0));
+    EXPECT_FLOAT_EQ(123.4, mime_type.GetParamFloatValue(1));
+  }
+}
+
+TEST(MimeTypeTest, GetParamStringValueWithIndex) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=abc; name2=\"xyz\"");
+    EXPECT_EQ("123", mime_type.GetParamStringValue(0));
+    EXPECT_EQ("abc", mime_type.GetParamStringValue(1));
+    EXPECT_EQ("xyz", mime_type.GetParamStringValue(2));
+  }
+
+  {
+    MimeType mime_type("video/mp4; name=\" xyz  \"");
+    EXPECT_EQ(" xyz  ", mime_type.GetParamStringValue(0));
+  }
+}
+
+TEST(MimeTypeTest, GetParamIntValueWithName) {
+  {
+    MimeType mime_type("video/mp4; name=123");
+    EXPECT_EQ(123, mime_type.GetParamIntValue("name", 0));
+    EXPECT_EQ(6, mime_type.GetParamIntValue("channels", 6));
+  }
+
+  {
+    MimeType mime_type("video/mp4; width=1920; height=1080");
+    EXPECT_EQ(1920, mime_type.GetParamIntValue("width", 0));
+    EXPECT_EQ(1080, mime_type.GetParamIntValue("height", 0));
+  }
+
+  {
+    MimeType mime_type("audio/mp4; channels=6");
+    EXPECT_EQ(6, mime_type.GetParamIntValue("channels", 0));
+  }
+}
+
+TEST(MimeTypeTest, GetParamFloatValueWithName) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=123.4");
+    EXPECT_FLOAT_EQ(123.f, mime_type.GetParamFloatValue("name0", 0.f));
+    EXPECT_FLOAT_EQ(123.4f, mime_type.GetParamFloatValue("name1", 0.f));
+    EXPECT_FLOAT_EQ(59.96f, mime_type.GetParamFloatValue("framerate", 59.96f));
+  }
+}
+
+TEST(MimeTypeTest, GetParamStringValueWithName) {
+  {
+    MimeType mime_type("video/mp4; name0=123; name1=abc; name2=\"xyz\"");
+    EXPECT_EQ("123", mime_type.GetParamStringValue("name0", ""));
+    EXPECT_EQ("abc", mime_type.GetParamStringValue("name1", ""));
+    EXPECT_EQ("xyz", mime_type.GetParamStringValue("name2", ""));
+    EXPECT_EQ("h263", mime_type.GetParamStringValue("codecs", "h263"));
+  }
+
+  {
+    MimeType mime_type("video/mp4; name=\" xyz  \"");
+    EXPECT_EQ(" xyz  ", mime_type.GetParamStringValue("name", ""));
+  }
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/shared/stub/decode_target_create_blitter.cc
similarity index 73%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/shared/stub/decode_target_create_blitter.cc
index 3b48d2e..019658d 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/shared/stub/decode_target_create_blitter.cc
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/atomic_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat /*format*/,
+                                    SbBlitterSurface* /*planes*/) {
+  return kSbDecodeTargetInvalid;
+}
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/shared/stub/decode_target_create_egl.cc
similarity index 65%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/shared/stub/decode_target_create_egl.cc
index ec9eedf..d43b91b 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/shared/stub/decode_target_create_egl.cc
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/thread_types_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+SbDecodeTarget SbDecodeTargetCreate(EGLDisplay /*display*/,
+                                    EGLContext /*context*/,
+                                    SbDecodeTargetFormat /*format*/,
+                                    GLuint* /*planes*/) {
+  return kSbDecodeTargetInvalid;
+}
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/shared/stub/decode_target_destroy.cc
similarity index 74%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/shared/stub/decode_target_destroy.cc
index 3b48d2e..6762123 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/shared/stub/decode_target_destroy.cc
@@ -12,9 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/atomic_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+void SbDecodeTargetDestroy(SbDecodeTarget /*decode_target*/) {}
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/shared/stub/decode_target_get_format.cc
similarity index 74%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/shared/stub/decode_target_get_format.cc
index 3b48d2e..e8f9ea7 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/shared/stub/decode_target_get_format.cc
@@ -12,9 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/atomic_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+SbDecodeTargetFormat SbDecodeTargetGetFormat(SbDecodeTarget /*decode_target*/) {
+  return kSbDecodeTargetFormatInvalid;
+}
diff --git a/src/starboard/creator/ci20/thread_types_public.h b/src/starboard/shared/stub/decode_target_get_plane_blitter.cc
similarity index 72%
copy from src/starboard/creator/ci20/thread_types_public.h
copy to src/starboard/shared/stub/decode_target_get_plane_blitter.cc
index ec9eedf..7b3a7f8 100644
--- a/src/starboard/creator/ci20/thread_types_public.h
+++ b/src/starboard/shared/stub/decode_target_get_plane_blitter.cc
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/thread_types_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_THREAD_TYPES_PUBLIC_H_
+SbBlitterSurface SbDecodeTargetGetPlane(SbDecodeTarget /*decode_target*/,
+                                        SbDecodeTargetPlane /*plane*/) {
+  return kSbBlitterInvalidSurface;
+}
diff --git a/src/starboard/creator/ci20/atomic_public.h b/src/starboard/shared/stub/decode_target_get_plane_egl.cc
similarity index 74%
copy from src/starboard/creator/ci20/atomic_public.h
copy to src/starboard/shared/stub/decode_target_get_plane_egl.cc
index 3b48d2e..655ac40 100644
--- a/src/starboard/creator/ci20/atomic_public.h
+++ b/src/starboard/shared/stub/decode_target_get_plane_egl.cc
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
-#define STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+#include "starboard/decode_target.h"
 
-#include "starboard/linux/shared/atomic_public.h"
-
-#endif  // STARBOARD_CREATOR_CI20_ATOMIC_PUBLIC_H_
+GLuint SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
+                              SbDecodeTargetPlane plane) {
+  return 0;
+}
diff --git a/src/starboard/starboard.gyp b/src/starboard/starboard.gyp
index 5677568..1084b16 100644
--- a/src/starboard/starboard.gyp
+++ b/src/starboard/starboard.gyp
@@ -29,6 +29,7 @@
         'character.h',
         'condition_variable.h',
         'configuration.h',
+        'decode_target.h',
         'directory.h',
         'double.h',
         'drm.h',
@@ -60,26 +61,13 @@
         'window.h',
       ],
       'dependencies': [
+        '<(DEPTH)/<(starboard_path)/starboard_platform.gyp:starboard_platform',
         'common/common.gyp:common',
       ],
+      'export_dependent_settings': [
+        '<(DEPTH)/<(starboard_path)/starboard_platform.gyp:starboard_platform',
+      ],
       'conditions': [
-        ['starboard_path == ""', {
-          # TODO: Make starboard_path required. This legacy condition is only
-          # here to support semi-starboard platforms while they still exist.
-          'dependencies': [
-            '<(DEPTH)/starboard/<(target_arch)/starboard_platform.gyp:starboard_platform',
-          ],
-          'export_dependent_settings': [
-            '<(DEPTH)/starboard/<(target_arch)/starboard_platform.gyp:starboard_platform',
-          ],
-        }, {
-          'dependencies': [
-            '<(DEPTH)/<(starboard_path)/starboard_platform.gyp:starboard_platform',
-          ],
-          'export_dependent_settings': [
-            '<(DEPTH)/<(starboard_path)/starboard_platform.gyp:starboard_platform',
-          ],
-        }],
         ['final_executable_type=="shared_library"', {
           'all_dependent_settings': {
             'target_conditions': [
diff --git a/src/starboard/starboard_all.gyp b/src/starboard/starboard_all.gyp
index b97230b..1eb3b02 100644
--- a/src/starboard/starboard_all.gyp
+++ b/src/starboard/starboard_all.gyp
@@ -16,6 +16,9 @@
 # Starboard project.
 
 {
+  'variables': {
+    'has_platform_tests%' : '<!(python -c "import os.path; print os.path.isfile(\'<(starboard_path)/starboard_platform_tests.gyp\') & 1 | 0")',
+  },
   'targets': [
     {
       # Note that this target must be in a separate GYP file from starboard.gyp,
@@ -31,6 +34,13 @@
         '<(DEPTH)/starboard/nplb/nplb.gyp:*',
         '<(DEPTH)/starboard/starboard.gyp:*',
       ],
+      'conditions': [
+        ['has_platform_tests==1', {
+          'dependencies': [
+            '<(DEPTH)/<(starboard_path)/starboard_platform_tests.gyp:starboard_platform_tests',
+          ],
+        }],
+      ],
     },
   ],
 }
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h
index 90b83b7..75b5256 100644
--- a/src/starboard/stub/configuration_public.h
+++ b/src/starboard/stub/configuration_public.h
@@ -22,6 +22,9 @@
 #ifndef STARBOARD_STUB_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_STUB_CONFIGURATION_PUBLIC_H_
 
+// The API version implemented by this platform.
+#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION
+
 // --- Architecture Configuration --------------------------------------------
 
 // Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
@@ -93,9 +96,6 @@
 // on the specifically pinned core.
 #define SB_HAS_CROSS_CORE_SCHEDULER 1
 
-// The API version implemented by this platform.
-#define SB_API_VERSION 2
-
 // --- System Header Configuration -------------------------------------------
 
 // Any system headers listed here that are not provided by the platform will be
diff --git a/src/starboard/time_zone.h b/src/starboard/time_zone.h
index ce67d09..25ac711 100644
--- a/src/starboard/time_zone.h
+++ b/src/starboard/time_zone.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Access to the system time zone information.
+// Module Overview: Starboard Time Zone module
+//
+// Provides access to the system time zone information.
 
 #ifndef STARBOARD_TIME_ZONE_H_
 #define STARBOARD_TIME_ZONE_H_
@@ -24,7 +26,7 @@
 extern "C" {
 #endif
 
-// The number of minutes west of the Greenwich Prime Meridian, NOT including any
+// The number of minutes west of the Greenwich Prime Meridian, NOT including
 // Daylight Savings Time adjustments.
 //
 // For example: PST/PDT is 480 minutes (28800 seconds, 8 hours).
@@ -34,7 +36,7 @@
 SB_EXPORT SbTimeZone SbTimeZoneGetCurrent();
 
 // Gets the three-letter code of the current timezone in standard time,
-// regardless of current DST status. (e.g. "PST")
+// regardless of current Daylight Savings Time status. (e.g. "PST")
 SB_EXPORT const char* SbTimeZoneGetName();
 
 // Gets the three-letter code of the current timezone in Daylight Savings Time,
diff --git a/src/third_party/libevent/compat/sys/_libevent_time.h b/src/third_party/libevent/compat/sys/_libevent_time.h
index 6dec7d7..602d8b8 100644
--- a/src/third_party/libevent/compat/sys/_libevent_time.h
+++ b/src/third_party/libevent/compat/sys/_libevent_time.h
@@ -37,7 +37,6 @@
 
 #ifndef STARBOARD
 #include <sys/types.h>
-#endif
 
 /*
  * Structure returned by gettimeofday(2) system call,
@@ -55,6 +54,7 @@
 	time_t	tv_sec;		/* seconds */
 	long	tv_nsec;	/* and nanoseconds */
 };
+#endif
 
 #define	TIMEVAL_TO_TIMESPEC(tv, ts) {					\
 	(ts)->tv_sec = (tv)->tv_sec;					\
diff --git a/src/third_party/libevent/event.c b/src/third_party/libevent/event.c
index 8564bed..aeda1c0 100644
--- a/src/third_party/libevent/event.c
+++ b/src/third_party/libevent/event.c
@@ -910,7 +910,7 @@
 	pev = base->timeheap.p;
 	size = base->timeheap.n;
 	for (; size-- > 0; ++pev) {
-		struct timeval *ev_tv = &(**pev).ev_timeout;
+		struct evtimeval *ev_tv = &(**pev).ev_timeout;
 		evutil_timersub(ev_tv, &off, ev_tv);
 	}
 	/* Now remember what the new time turned out to be. */
diff --git a/src/third_party/libevent/event.h b/src/third_party/libevent/event.h
index 2ab00d1..0c29651 100644
--- a/src/third_party/libevent/event.h
+++ b/src/third_party/libevent/event.h
@@ -202,6 +202,20 @@
 #define EV_SIGNAL	0x08
 #define EV_PERSIST	0x10	/* Persistant event */
 
+#ifdef STARBOARD
+struct evtimeval {
+#if SB_IS(64_BIT)
+	int64_t	tv_sec;		/* seconds */
+	int64_t	tv_usec;	/* and microseconds */
+#else
+	int32_t	tv_sec;		/* seconds */
+	int32_t	tv_usec;	/* and microseconds */
+#endif
+};
+#else
+typedef struct timeval evtimeval;
+#endif
+
 /* Fix so that ppl dont have to run with <sys/queue.h> */
 #ifndef TAILQ_ENTRY
 #define _EVENT_DEFINED_TQENTRY
@@ -227,7 +241,7 @@
 	short ev_ncalls;
 	short *ev_pncalls;	/* Allows deletes in callback */
 
-	struct timeval ev_timeout;
+	struct evtimeval ev_timeout;
 
 	int ev_pri;		/* smaller numbers are higher priority */
 
diff --git a/src/third_party/libjpeg/jmemnobs.c b/src/third_party/libjpeg/jmemnobs.c
index a360de8..935ef6e 100644
--- a/src/third_party/libjpeg/jmemnobs.c
+++ b/src/third_party/libjpeg/jmemnobs.c
@@ -23,7 +23,7 @@
 #if defined(NEED_STARBOARD_MEMORY)
 #include "starboard/memory.h"
 #define malloc SbMemoryAllocate
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #elif !defined(HAVE_STDLIB_H)	/* <stdlib.h> should declare malloc(),free() */
 extern void * malloc JPP((size_t size));
 extern void free JPP((void *ptr));
diff --git a/src/third_party/libpng/pngmem.c b/src/third_party/libpng/pngmem.c
index d19179d..1b4b88f 100644
--- a/src/third_party/libpng/pngmem.c
+++ b/src/third_party/libpng/pngmem.c
@@ -24,7 +24,7 @@
 #if defined(STARBOARD)
 #  include "starboard/memory.h"
 #  define malloc SbMemoryAllocate
-#  define free SbMemoryFree
+#  define free SbMemoryDeallocate
 #endif
 #if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED)
 
diff --git a/src/third_party/openssl/openssl/crypto/LPdir_starboard.c b/src/third_party/openssl/openssl/crypto/LPdir_starboard.c
index f663a95..5daa624 100644
--- a/src/third_party/openssl/openssl/crypto/LPdir_starboard.c
+++ b/src/third_party/openssl/openssl/crypto/LPdir_starboard.c
@@ -52,7 +52,7 @@
 
         (*ctx)->dir = SbDirectoryOpen(directory, NULL);
         if (!SbDirectoryIsValid((*ctx)->dir)) {
-            SbMemoryFree(*ctx);
+            SbMemoryDeallocate(*ctx);
             *ctx = NULL;
             return NULL;
         }
@@ -70,7 +70,7 @@
 {
     if (ctx != NULL && *ctx != NULL) {
         bool result = SbDirectoryClose((*ctx)->dir);
-        SbMemoryFree(*ctx);
+        SbMemoryDeallocate(*ctx);
         return (result ? 1 : 0);
     }
     return 0;
diff --git a/src/third_party/protobuf/src/google/protobuf/io/coded_stream.h b/src/third_party/protobuf/src/google/protobuf/io/coded_stream.h
index 9f2489a..d87a0a7 100644
--- a/src/third_party/protobuf/src/google/protobuf/io/coded_stream.h
+++ b/src/third_party/protobuf/src/google/protobuf/io/coded_stream.h
@@ -110,6 +110,13 @@
 #define GOOGLE_PROTOBUF_IO_CODED_STREAM_H__
 
 #include <string>
+#if defined(STARBOARD)
+#include "starboard/configuration.h"
+#if SB_IS(LITTLE_ENDIAN) && \
+    !defined(PROTOBUF_DISABLE_LITTLE_ENDIAN_OPT_FOR_TEST)
+#define PROTOBUF_LITTLE_ENDIAN 1
+#endif
+#else
 #ifdef _MSC_VER
   #if defined(_M_IX86) && \
       !defined(PROTOBUF_DISABLE_LITTLE_ENDIAN_OPT_FOR_TEST)
@@ -127,6 +134,7 @@
     #define PROTOBUF_LITTLE_ENDIAN 1
   #endif
 #endif
+#endif  // defined(STARBOARD)
 #include <google/protobuf/stubs/common.h>
 
 #include "starboard/client_porting/poem/string_poem.h"
diff --git a/src/third_party/protobuf/src/google/protobuf/stubs/atomicops.h b/src/third_party/protobuf/src/google/protobuf/stubs/atomicops.h
index 3ace767..dcec550 100644
--- a/src/third_party/protobuf/src/google/protobuf/stubs/atomicops.h
+++ b/src/third_party/protobuf/src/google/protobuf/stubs/atomicops.h
@@ -56,12 +56,22 @@
 // Don't include this file for people not concerned about thread safety.
 #ifndef GOOGLE_PROTOBUF_NO_THREADSAFETY
 
+#if defined(STARBOARD)
+#include "starboard/atomic.h"
+#endif  // defined(STARBOARD)
+
 #include <google/protobuf/stubs/platform_macros.h>
 
 namespace google {
 namespace protobuf {
 namespace internal {
 
+#if defined(STARBOARD)
+typedef SbAtomic32 Atomic32;
+#if defined(GOOGLE_PROTOBUF_ARCH_64_BIT)
+typedef SbAtomic64 Atomic64;
+#endif  // defined(GOOGLE_PROTOBUF_ARCH_64_BIT)
+#else   // defined(STARBOARD)
 typedef int32 Atomic32;
 #ifdef GOOGLE_PROTOBUF_ARCH_64_BIT
 // We need to be able to go between Atomic64 and AtomicWord implicitly.  This
@@ -74,6 +84,7 @@
 typedef intptr_t Atomic64;
 #endif
 #endif
+#endif  // defined(STARBOARD)
 
 // Use AtomicWord for a machine-sized pointer.  It will use the Atomic32 or
 // Atomic64 routines below, depending on your architecture.
@@ -160,6 +171,11 @@
 #define GOOGLE_PROTOBUF_ATOMICOPS_ERROR \
 #error "Atomic operations are not supported on your platform"
 
+// Starboard.
+#if defined(STARBOARD)
+#include "third_party/protobuf/src/google/protobuf/stubs/atomicops_internals_starboard.h"
+#else
+
 // MSVC.
 #if defined(_MSC_VER)
 #if defined(GOOGLE_PROTOBUF_ARCH_IA32) || defined(GOOGLE_PROTOBUF_ARCH_X64)
@@ -195,6 +211,8 @@
 GOOGLE_PROTOBUF_ATOMICOPS_ERROR
 #endif
 
+#endif  // defined(STARBOARD)
+
 // On some platforms we need additional declarations to make AtomicWord
 // compatible with our other Atomic* types.
 #if defined(GOOGLE_PROTOBUF_OS_APPLE)
diff --git a/src/third_party/protobuf/src/google/protobuf/stubs/atomicops_internals_starboard.h b/src/third_party/protobuf/src/google/protobuf/stubs/atomicops_internals_starboard.h
new file mode 100644
index 0000000..5d87cdf
--- /dev/null
+++ b/src/third_party/protobuf/src/google/protobuf/stubs/atomicops_internals_starboard.h
@@ -0,0 +1,153 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file is an internal atomic implementation, use base/atomicops.h instead.
+
+// This is the Starboard implementation, which defers all specific
+// implementation decisions for atomic operations to the Starboard port.
+
+#ifndef GOOGLE_PROTOBUF_ATOMICOPS_INTERNALS_STARBOARD_H_
+#define GOOGLE_PROTOBUF_ATOMICOPS_INTERNALS_STARBOARD_H_
+
+#include "starboard/atomic.h"
+
+namespace google {
+namespace protobuf {
+namespace internal {
+
+inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
+                                         Atomic32 old_value,
+                                         Atomic32 new_value) {
+  return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
+}
+
+inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr,
+                                         Atomic32 new_value) {
+  return SbAtomicNoBarrier_Exchange(ptr, new_value);
+}
+
+inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
+                                          Atomic32 increment) {
+  return SbAtomicNoBarrier_Increment(ptr, increment);
+}
+
+inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
+                                        Atomic32 increment) {
+  return SbAtomicBarrier_Increment(ptr, increment);
+}
+
+inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
+                                       Atomic32 old_value,
+                                       Atomic32 new_value) {
+  return SbAtomicAcquire_CompareAndSwap(ptr, old_value, new_value);
+}
+
+inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
+                                       Atomic32 old_value,
+                                       Atomic32 new_value) {
+  return SbAtomicRelease_CompareAndSwap(ptr, old_value, new_value);
+}
+
+inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
+  SbAtomicNoBarrier_Store(ptr, value);
+}
+
+inline void MemoryBarrier() {
+  SbAtomicMemoryBarrier();
+}
+
+inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
+  SbAtomicAcquire_Store(ptr, value);
+}
+
+inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
+  SbAtomicRelease_Store(ptr, value);
+}
+
+inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
+  return SbAtomicNoBarrier_Load(ptr);
+}
+
+inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
+  return SbAtomicAcquire_Load(ptr);
+}
+
+inline Atomic32 Release_Load(volatile const Atomic32* ptr) {
+  return SbAtomicRelease_Load(ptr);
+}
+
+#if SB_IS(64_BIT)
+inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
+                                         Atomic64 old_value,
+                                         Atomic64 new_value) {
+  return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
+}
+
+inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr,
+                                         Atomic64 new_value) {
+  return SbAtomicNoBarrier_Exchange64(ptr, new_value);
+}
+
+inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr,
+                                          Atomic64 increment) {
+  return SbAtomicNoBarrier_Increment64(ptr, increment);
+}
+
+inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr,
+                                        Atomic64 increment) {
+  return SbAtomicBarrier_Increment64(ptr, increment);
+}
+
+inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr,
+                                       Atomic64 old_value,
+                                       Atomic64 new_value) {
+  return SbAtomicAcquire_CompareAndSwap64(ptr, old_value, new_value);
+}
+
+inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr,
+                                       Atomic64 old_value,
+                                       Atomic64 new_value) {
+  return SbAtomicRelease_CompareAndSwap64(ptr, old_value, new_value);
+}
+
+inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) {
+  SbAtomicNoBarrier_Store64(ptr, value);
+}
+
+inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) {
+  SbAtomicAcquire_Store64(ptr, value);
+}
+
+inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) {
+  SbAtomicRelease_Store64(ptr, value);
+}
+
+inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) {
+  return SbAtomicNoBarrier_Load64(ptr);
+}
+
+inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) {
+  return SbAtomicAcquire_Load64(ptr);
+}
+
+inline Atomic64 Release_Load(volatile const Atomic64* ptr) {
+  return SbAtomicRelease_Load64(ptr);
+}
+#endif  // SB_IS(64_BIT)
+
+}  // namespace internal
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_ATOMICOPS_INTERNALS_STARBOARD_H_
diff --git a/src/third_party/protobuf/src/google/protobuf/stubs/once.cc b/src/third_party/protobuf/src/google/protobuf/stubs/once.cc
index 1e24b85..a58085b 100644
--- a/src/third_party/protobuf/src/google/protobuf/stubs/once.cc
+++ b/src/third_party/protobuf/src/google/protobuf/stubs/once.cc
@@ -39,11 +39,13 @@
 
 #ifndef GOOGLE_PROTOBUF_NO_THREAD_SAFETY
 
-#ifdef _WIN32
+#if defined(STARBOARD)
+#include "starboard/thread.h"
+#elif defined(_WIN32)
 #include <windows.h>
 #else
 #include <sched.h>
-#endif
+#endif  // defined(STARBOARD)
 
 #include <google/protobuf/stubs/atomicops.h>
 
@@ -53,7 +55,9 @@
 namespace {
 
 void SchedYield() {
-#ifdef _WIN32
+#if defined(STARBOARD)
+  SbThreadYield();
+#elif defined(_WIN32)
   Sleep(0);
 #else  // POSIX
   sched_yield();
diff --git a/src/third_party/protobuf/src/google/protobuf/stubs/platform_macros.h b/src/third_party/protobuf/src/google/protobuf/stubs/platform_macros.h
index 495ed19..5ebc9e1 100644
--- a/src/third_party/protobuf/src/google/protobuf/stubs/platform_macros.h
+++ b/src/third_party/protobuf/src/google/protobuf/stubs/platform_macros.h
@@ -37,6 +37,14 @@
 //   http://msdn.microsoft.com/en-us/library/b0084kay.aspx
 //   http://www.agner.org/optimize/calling_conventions.pdf
 //   or with gcc, run: "echo | gcc -E -dM -"
+#if defined(STARBOARD)
+#include "starboard/configuration.h"
+#if SB_IS(32_BIT)
+#define GOOGLE_PROTOBUF_ARCH_32_BIT 1
+#elif SB_IS(64_BIT)
+#define GOOGLE_PROTOBUF_ARCH_64_BIT 1
+#endif  // SB_IS(32_BIT)
+#else   // defined(STARBOARD)
 #if defined(_M_X64) || defined(__x86_64__)
 #define GOOGLE_PROTOBUF_ARCH_X64 1
 #define GOOGLE_PROTOBUF_ARCH_64_BIT 1
@@ -57,6 +65,7 @@
 #else
 #error Host architecture was not detected as supported by protobuf
 #endif
+#endif  // defined(STARBOARD)
 
 #if defined(__APPLE__)
 #define GOOGLE_PROTOBUF_OS_APPLE
diff --git a/src/third_party/sqlite/amalgamation/sqlite3.c b/src/third_party/sqlite/amalgamation/sqlite3.c
index 25aa93a..5a56cc5 100644
--- a/src/third_party/sqlite/amalgamation/sqlite3.c
+++ b/src/third_party/sqlite/amalgamation/sqlite3.c
@@ -14473,7 +14473,7 @@
 #if defined(STARBOARD)
 #include "starboard/memory.h"
 #define malloc SbMemoryAllocate
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #define realloc SbMemoryReallocate
 #endif
 
diff --git a/src/third_party/zlib/zutil.c b/src/third_party/zlib/zutil.c
index bf2e3cc..fd3a67b 100644
--- a/src/third_party/zlib/zutil.c
+++ b/src/third_party/zlib/zutil.c
@@ -301,7 +301,7 @@
 void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
 {
     SB_UNREFERENCED_PARAMETER(opaque);
-    SbMemoryFree(ptr);
+    SbMemoryDeallocate(ptr);
 }
 
 #endif /* STARBOARD */