Import Cobalt 6.16000

Change-Id: If1f4d0d72e803dd65b8405737d248c3a7ccad77b
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/base.gypi b/src/base/base.gypi
index fde1a96..c1a5fff 100644
--- a/src/base/base.gypi
+++ b/src/base/base.gypi
@@ -589,11 +589,6 @@
               ['include', 'sys_string_conversions_linux.cc'],
             ],
           }],
-          [ 'OS=="lb_shell" and target_arch!="linux" and target_arch!="android"', {
-            'sources!': [
-              'string16.cc',  # wchar_t is 2-bytes wide, string16 == wstring here.
-            ],
-          }],
           [ 'OS=="lb_shell" and target_arch=="android"', {
             'sources/' : [
               ['exclude', 'message_pump_shell.cc'],
@@ -660,14 +655,6 @@
               # We use thread_checker_impl_atomic.cc instead.
               'threading/thread_checker_impl.cc',
             ],
-            'conditions': [
-              ['target_os!="linux" and target_arch!="android"', {
-                'sources!': [
-                  # Since wchar_t is 2-bytes wide, string16 == wstring here.
-                  'string16.cc',
-                ],
-              }],
-            ],
           }],  # OS == "starboard"
           [ 'OS=="lb_shell" or OS=="starboard"', {
             'sources!': [
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/circular_buffer_shell.cc b/src/base/circular_buffer_shell.cc
index ae58e4b..ce64f42 100644
--- a/src/base/circular_buffer_shell.cc
+++ b/src/base/circular_buffer_shell.cc
@@ -15,7 +15,7 @@
 #include "starboard/memory.h"
 #define malloc SbMemoryAllocate
 #define realloc SbMemoryReallocate
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #endif
 
 static inline void* add_to_pointer(void* pointer, size_t amount) {
diff --git a/src/base/debug/trace_event_impl.cc b/src/base/debug/trace_event_impl.cc
index 5af61e5..ac641c3 100644
--- a/src/base/debug/trace_event_impl.cc
+++ b/src/base/debug/trace_event_impl.cc
@@ -114,6 +114,7 @@
 
 TraceEvent::TraceEvent(int thread_id,
                        TimeTicks timestamp,
+                       TimeTicks thread_timestamp,
                        char phase,
                        const unsigned char* category_enabled,
                        const char* name,
@@ -124,6 +125,7 @@
                        const unsigned long long* arg_values,
                        unsigned char flags)
     : timestamp_(timestamp),
+      thread_timestamp_(thread_timestamp),
       id_(id),
       category_enabled_(category_enabled),
       name_(name),
@@ -242,6 +244,7 @@
 
 void TraceEvent::AppendAsJSON(std::string* out) const {
   int64 time_int64 = timestamp_.ToInternalValue();
+  int64 thread_time_int64 = thread_timestamp_.ToInternalValue();
   int process_id = TraceLog::GetInstance()->process_id();
   // Category name checked at category creation time.
   DCHECK(!strchr(name_, '"'));
@@ -266,6 +269,11 @@
   }
   *out += "}";
 
+  // add thread timestamp only if it was available
+  if (TimeTicks::HasThreadNow()) {
+    StringAppendF(out, ",\"tts\":%" PRId64, thread_time_int64);
+  }
+
   // If id_ is set, print it out as a hex string so we don't loose any
   // bits (it might be a 64-bit pointer).
   if (flags_ & TRACE_EVENT_FLAG_HAS_ID)
@@ -667,6 +675,7 @@
 #endif
 
   TimeTicks now = TimeTicks::NowFromSystemTraceTime() - time_offset_;
+  TimeTicks thread_now = TimeTicks::ThreadNow();
   NotificationHelper notifier(this);
   {
     AutoLock lock(lock_);
@@ -710,7 +719,7 @@
 
     logged_events_.push_back(
         TraceEvent(thread_id,
-                   now, phase, category_enabled, name, id,
+                   now, thread_now, phase, category_enabled, name, id,
                    num_args, arg_names, arg_types, arg_values,
                    flags));
 
@@ -795,7 +804,7 @@
       trace_event_internal::SetTraceValue(it->second, &arg_type, &arg_value);
       logged_events_.push_back(
           TraceEvent(it->first,
-                     TimeTicks(), TRACE_EVENT_PHASE_METADATA,
+                     TimeTicks(), TimeTicks(), TRACE_EVENT_PHASE_METADATA,
                      &g_category_enabled[g_category_metadata],
                      "thread_name", trace_event_internal::kNoEventId,
                      num_args, &arg_name, &arg_type, &arg_value,
diff --git a/src/base/debug/trace_event_impl.h b/src/base/debug/trace_event_impl.h
index b8b4110..6d373c7 100644
--- a/src/base/debug/trace_event_impl.h
+++ b/src/base/debug/trace_event_impl.h
@@ -64,6 +64,7 @@
   TraceEvent();
   TraceEvent(int thread_id,
              TimeTicks timestamp,
+             TimeTicks thread_timestamp,
              char phase,
              const unsigned char* category_enabled,
              const char* name,
@@ -87,6 +88,7 @@
                                 std::string* out);
 
   TimeTicks timestamp() const { return timestamp_; }
+  TimeTicks thread_timestamp() const { return thread_timestamp_; }
 
   // Exposed for unittesting:
 
@@ -107,6 +109,7 @@
  private:
   // Note: these are ordered by size (largest first) for optimal packing.
   TimeTicks timestamp_;
+  TimeTicks thread_timestamp_;
   // id_ can be used to store phase-specific data.
   unsigned long long id_;
   TraceValue arg_values_[kTraceMaxNumArgs];
diff --git a/src/base/file_util_proxy_unittest.cc b/src/base/file_util_proxy_unittest.cc
index e238b32..dcd5ad8 100644
--- a/src/base/file_util_proxy_unittest.cc
+++ b/src/base/file_util_proxy_unittest.cc
@@ -39,10 +39,18 @@
   }
 
   void DidFinish(PlatformFileError error) {
-    error_ = error;
+    if (error_ == PLATFORM_FILE_OK) {
+      error_ = error;
+    }
     MessageLoop::current()->Quit();
   }
 
+  void NeedsMoreWork(PlatformFileError error) {
+    if (error_ == PLATFORM_FILE_OK) {
+      error_ = error;
+    }
+  }
+
   void DidCreateOrOpen(PlatformFileError error,
                        PassPlatformFile file,
                        bool created) {
@@ -376,10 +384,14 @@
   ASSERT_EQ(10, info.size);
 
   // Run.
+  PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN |
+      PLATFORM_FILE_WRITE);
   FileUtilProxy::Truncate(
+      file_task_runner(), file, 7,
+      Bind(&FileUtilProxyTest::NeedsMoreWork, weak_factory_.GetWeakPtr()));
+  FileUtilProxy::Flush(
       file_task_runner(),
-      GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE),
-      7,
+      file,
       Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
   MessageLoop::current()->Run();
 
@@ -409,10 +421,14 @@
   ASSERT_EQ(10, info.size);
 
   // Run.
+  PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN |
+      PLATFORM_FILE_WRITE);
   FileUtilProxy::Truncate(
+      file_task_runner(), file, 53,
+      Bind(&FileUtilProxyTest::NeedsMoreWork, weak_factory_.GetWeakPtr()));
+  FileUtilProxy::Flush(
       file_task_runner(),
-      GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE),
-      53,
+      file,
       Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr()));
   MessageLoop::current()->Run();
 
diff --git a/src/base/file_util_starboard.cc b/src/base/file_util_starboard.cc
index 0c6511a..a2e9b2b 100644
--- a/src/base/file_util_starboard.cc
+++ b/src/base/file_util_starboard.cc
@@ -193,8 +193,8 @@
   }
 
   base::PlatformFile destination_file = base::CreatePlatformFileUnsafe(
-      to_path, base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, NULL,
-      NULL);
+      to_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
+      NULL, NULL);
   if (destination_file == base::kInvalidPlatformFileValue) {
     DPLOG(ERROR) << "CopyFile(): Unable to open destination file: "
                  << to_path.value();
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/logging.cc b/src/base/logging.cc
index 6a135ea..d3a97e5 100644
--- a/src/base/logging.cc
+++ b/src/base/logging.cc
@@ -406,6 +406,25 @@
   return true;
 }
 
+#if defined(OS_STARBOARD)
+SbLogPriority LogLevelToStarboardLogPriority(int level) {
+  switch (level) {
+    case LOG_INFO:
+      return kSbLogPriorityInfo;
+    case LOG_WARNING:
+      return kSbLogPriorityWarning;
+    case LOG_ERROR:
+    case LOG_ERROR_REPORT:
+      return kSbLogPriorityError;
+    case LOG_FATAL:
+      return kSbLogPriorityFatal;
+    default:
+      NOTREACHED() << "Unrecognized log level.";
+      return kSbLogPriorityInfo;
+  }
+}
+#endif  // defined(OS_STARBOARD)
+
 }  // namespace
 
 
@@ -467,6 +486,11 @@
 
 void SetMinLogLevel(int level) {
   min_log_level = std::min(LOG_ERROR_REPORT, level);
+
+#if defined(OS_STARBOARD)
+  starboard::logging::SetMinLogLevel(
+      LogLevelToStarboardLogPriority(std::min(LOG_FATAL, level)));
+#endif
 }
 
 int GetMinLogLevel() {
@@ -650,23 +674,7 @@
   if (logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG ||
       logging_destination == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) {
 #if defined(OS_STARBOARD)
-    SbLogPriority priority = kSbLogPriorityUnknown;
-    switch (severity_) {
-      case LOG_INFO:
-        priority = kSbLogPriorityInfo;
-        break;
-      case LOG_WARNING:
-        priority = kSbLogPriorityWarning;
-        break;
-      case LOG_ERROR:
-      case LOG_ERROR_REPORT:
-        priority = kSbLogPriorityError;
-        break;
-      case LOG_FATAL:
-        priority = kSbLogPriorityFatal;
-        break;
-    }
-    SbLog(priority, str_newline.c_str());
+    SbLog(LogLevelToStarboardLogPriority(severity_), str_newline.c_str());
 #elif defined(OS_WIN) || defined(COBALT_WIN)
     OutputDebugStringA(str_newline.c_str());
 #elif defined(OS_ANDROID) || defined(__LB_ANDROID__)
diff --git a/src/base/memory/aligned_memory.h b/src/base/memory/aligned_memory.h
index 040b56d..8ea96b7 100644
--- a/src/base/memory/aligned_memory.h
+++ b/src/base/memory/aligned_memory.h
@@ -99,7 +99,7 @@
 #if defined(COMPILER_MSVC)
   _aligned_free(ptr);
 #elif defined(OS_STARBOARD)
-  SbMemoryFreeAligned(ptr);
+  SbMemoryDeallocateAligned(ptr);
 #else
   free(ptr);
 #endif
diff --git a/src/base/memory/scoped_ptr.h b/src/base/memory/scoped_ptr.h
index 9ee780e..7f2d7dc 100644
--- a/src/base/memory/scoped_ptr.h
+++ b/src/base/memory/scoped_ptr.h
@@ -395,7 +395,7 @@
  public:
   inline void operator()(void* x) const {
 #if defined(OS_STARBOARD)
-    SbMemoryFree(x);
+    SbMemoryDeallocate(x);
 #else
     free(x);
 #endif
diff --git a/src/base/pickle.cc b/src/base/pickle.cc
index 43121c9..e5050b9 100644
--- a/src/base/pickle.cc
+++ b/src/base/pickle.cc
@@ -10,7 +10,7 @@
 #if defined(OS_STARBOARD)
 #include "starboard/memory.h"
 #define realloc SbMemoryReallocate
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #define memcpy SbMemoryCopy
 #else
 #include <stdlib.h>
diff --git a/src/base/platform_file_starboard.cc b/src/base/platform_file_starboard.cc
index c28fd65..cc116c6 100644
--- a/src/base/platform_file_starboard.cc
+++ b/src/base/platform_file_starboard.cc
@@ -161,21 +161,7 @@
 
 int ReadPlatformFileAtCurrentPos(PlatformFile file, char *data, int size) {
   base::ThreadRestrictions::AssertIOAllowed();
-  if (file < 0 || size < 0)
-    return -1;
-
-  int bytes_read = 0;
-  int rv;
-  do {
-    rv = SbFileRead(file, data, size);
-    if (rv <= 0) {
-      break;
-    }
-
-    bytes_read += rv;
-  } while (bytes_read < size);
-
-  return bytes_read ? bytes_read : rv;
+  return SbFileReadAll(file, data, size);
 }
 
 int ReadPlatformFileNoBestEffort(PlatformFile file,
@@ -251,22 +237,7 @@
                                   const char* data,
                                   int size) {
   base::ThreadRestrictions::AssertIOAllowed();
-  if (size < 0) {
-    return -1;
-  }
-
-  int bytes_written = 0;
-  int result;
-  do {
-    result = SbFileWrite(file, data, size);
-    if (result <= 0) {
-      break;
-    }
-
-    bytes_written += result;
-  } while (bytes_written < size);
-
-  return bytes_written ? bytes_written : result;
+  return SbFileWriteAll(file, data, size);
 }
 
 int WritePlatformFileCurPosNoBestEffort(PlatformFile file,
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/shared_memory.h b/src/base/shared_memory.h
index bf77788..8589779 100644
--- a/src/base/shared_memory.h
+++ b/src/base/shared_memory.h
@@ -67,7 +67,7 @@
   }
   ~RefCountedMem() {
 #if defined(OS_STARBOARD)
-    SbMemoryFree(memory_);
+    SbMemoryDeallocate(memory_);
 #else
     free(memory_);
 #endif
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/string16.cc b/src/base/string16.cc
index 930e09f..9c5acb7 100644
--- a/src/base/string16.cc
+++ b/src/base/string16.cc
@@ -4,14 +4,9 @@
 
 #include "base/string16.h"
 
-#if defined(WCHAR_T_IS_UTF16)
 
-#error This file should not be used on 2-byte wchar_t systems
-// If this winds up being needed on 2-byte wchar_t systems, either the
-// definitions below can be used, or the host system's wide character
-// functions like wmemcmp can be wrapped.
-
-#elif defined(WCHAR_T_IS_UTF32)
+// See discussion in string16.h: This is only needed on 32-bit wchar_t systems.
+#if defined(WCHAR_T_IS_UTF32)
 
 #include <ostream>
 
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/dmg_fp/dtoa.cc b/src/base/third_party/dmg_fp/dtoa.cc
index 1339d3f..3c5d5a5 100644
--- a/src/base/third_party/dmg_fp/dtoa.cc
+++ b/src/base/third_party/dmg_fp/dtoa.cc
@@ -205,7 +205,7 @@
 #if defined(STARBOARD)
 #include "starboard/memory.h"
 #define MALLOC SbMemoryAllocate
-#define FREE SbMemoryFree
+#define FREE SbMemoryDeallocate
 #else
 #include "stdlib.h"
 #include "string.h"
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/base/threading/thread_local.h b/src/base/threading/thread_local.h
index 762d77b..fb6dfb2 100644
--- a/src/base/threading/thread_local.h
+++ b/src/base/threading/thread_local.h
@@ -73,7 +73,7 @@
 
   static void AllocateSlot(SlotType& slot);
   static void FreeSlot(SlotType& slot);
-  static void* GetValueFromSlot(SlotType& slot);
+  static void* GetValueFromSlot(const SlotType& slot);
   static void SetValueInSlot(SlotType& slot, void* value);
 };
 
@@ -90,7 +90,7 @@
     internal::ThreadLocalPlatform::FreeSlot(slot_);
   }
 
-  Type* Get() {
+  Type* Get() const {
     return static_cast<Type*>(
         internal::ThreadLocalPlatform::GetValueFromSlot(slot_));
   }
@@ -113,7 +113,7 @@
   ThreadLocalBoolean() { }
   ~ThreadLocalBoolean() { }
 
-  bool Get() {
+  bool Get() const {
     return tlp_.Get() != NULL;
   }
 
diff --git a/src/base/threading/thread_local_posix.cc b/src/base/threading/thread_local_posix.cc
index 4951006..1b381c2 100644
--- a/src/base/threading/thread_local_posix.cc
+++ b/src/base/threading/thread_local_posix.cc
@@ -25,7 +25,7 @@
 }
 
 // static
-void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) {
+void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) {
   return pthread_getspecific(slot);
 }
 
diff --git a/src/base/threading/thread_local_starboard.cc b/src/base/threading/thread_local_starboard.cc
index e3a96d6..1234425 100644
--- a/src/base/threading/thread_local_starboard.cc
+++ b/src/base/threading/thread_local_starboard.cc
@@ -33,7 +33,7 @@
 }
 
 // static
-void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) {
+void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) {
   return SbThreadGetLocalValue(slot);
 }
 
diff --git a/src/base/threading/thread_local_win.cc b/src/base/threading/thread_local_win.cc
index 56d3a3a..7e82c90 100644
--- a/src/base/threading/thread_local_win.cc
+++ b/src/base/threading/thread_local_win.cc
@@ -26,7 +26,7 @@
 }
 
 // static
-void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) {
+void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) {
   return TlsGetValue(slot);
 }
 
diff --git a/src/base/time.h b/src/base/time.h
index 38bc53a..9fde0e4 100644
--- a/src/base/time.h
+++ b/src/base/time.h
@@ -593,6 +593,15 @@
   // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED.
   static TimeTicks HighResNow();
 
+  // Returns the amount of time the current thread has spent in the execution
+  // state (i.e. not pre-empted or waiting on some event). If this is not
+  // available, then HighResNow() will be returned.
+  static TimeTicks ThreadNow();
+
+  // Returns whether ThreadNow() is implemented to report thread time (as
+  // opposed to HighResNow()).
+  static bool HasThreadNow();
+
   // Returns the current system trace time or, if none is defined, the current
   // high-res time (i.e. HighResNow()). On systems where a global trace clock
   // is defined, timestamping TraceEvents's with this value guarantees
diff --git a/src/base/time_mac.cc b/src/base/time_mac.cc
index 883a35b..327f9e6 100644
--- a/src/base/time_mac.cc
+++ b/src/base/time_mac.cc
@@ -192,6 +192,16 @@
 }
 
 // static
+TimeTicks TimeTicks::ThreadNow() {
+  return HighResNow();
+}
+
+// static
+bool TimeTicks::HasThreadNow() {
+  return false;
+}
+
+// static
 TimeTicks TimeTicks::NowFromSystemTraceTime() {
   return HighResNow();
 }
diff --git a/src/base/time_posix.cc b/src/base/time_posix.cc
index 80b1c4e..d053ff2 100644
--- a/src/base/time_posix.cc
+++ b/src/base/time_posix.cc
@@ -267,6 +267,27 @@
   return Now();
 }
 
+// static
+TimeTicks TimeTicks::ThreadNow() {
+  uint64_t absolute_micro;
+
+  struct timespec ts;
+  if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) {
+    return HighResNow();
+  }
+
+  absolute_micro =
+      (static_cast<int64>(ts.tv_sec) * Time::kMicrosecondsPerSecond) +
+      (static_cast<int64>(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond);
+
+  return TimeTicks(absolute_micro);
+}
+
+// static
+bool TimeTicks::HasThreadNow() {
+  return true;
+}
+
 #if defined(OS_CHROMEOS)
 // Force definition of the system trace clock; it is a chromeos-only api
 // at the moment and surfacing it in the right place requires mucking
diff --git a/src/base/time_starboard.cc b/src/base/time_starboard.cc
index 2e7785e..0327166 100644
--- a/src/base/time_starboard.cc
+++ b/src/base/time_starboard.cc
@@ -97,6 +97,24 @@
 }
 
 // static
+TimeTicks TimeTicks::ThreadNow() {
+#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+  return TimeTicks(SbTimeGetMonotonicThreadNow());
+#else
+  return HighResNow();
+#endif
+}
+
+// static
+bool TimeTicks::HasThreadNow() {
+#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+  return true;
+#else
+  return false;
+#endif
+}
+
+// static
 TimeTicks TimeTicks::NowFromSystemTraceTime() {
   return HighResNow();
 }
diff --git a/src/base/time_win.cc b/src/base/time_win.cc
index 6d8e432..294a564 100644
--- a/src/base/time_win.cc
+++ b/src/base/time_win.cc
@@ -460,6 +460,16 @@
 }
 
 // static
+TimeTicks TimeTicks::ThreadNow() {
+  return HighResNow();
+}
+
+// static
+bool TimeTicks::HasThreadNow() {
+  return false;
+}
+
+// static
 TimeTicks TimeTicks::NowFromSystemTraceTime() {
   return HighResNow();
 }
diff --git a/src/base/tools_sanity_unittest.cc b/src/base/tools_sanity_unittest.cc
index a901f59..074a13e 100644
--- a/src/base/tools_sanity_unittest.cc
+++ b/src/base/tools_sanity_unittest.cc
@@ -15,7 +15,7 @@
 
 #if defined(OS_STARBOARD)
 #include "starboard/memory.h"
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #define malloc SbMemoryAllocate
 #endif
 
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index cb23a87..2aa7a62 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 {
@@ -30,15 +31,17 @@
 void Decode(
     const uint8* audio_data, size_t size,
     const AsyncAudioDecoder::DecodeFinishCallback& decode_finish_callback) {
-  scoped_ptr<AudioFileReader> reader(
-      AudioFileReader::TryCreate(audio_data, size));
+  scoped_ptr<AudioFileReader> reader(AudioFileReader::TryCreate(
+      audio_data, size, GetPreferredOutputSampleType()));
 
   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.cc b/src/cobalt/audio/audio_destination_node.cc
index 59d80df..e0eda2b 100644
--- a/src/cobalt/audio/audio_destination_node.cc
+++ b/src/cobalt/audio/audio_destination_node.cc
@@ -38,8 +38,6 @@
     : AudioNode(context), max_channel_count_(kMaxChannelCount) {
   AudioLock::AutoLock lock(audio_lock());
 
-  audio_device_.reset(
-      new AudioDevice(static_cast<int>(channel_count(NULL)), this));
   AddInput(new AudioNodeInput(this));
 }
 
@@ -54,6 +52,15 @@
   RemoveAllInputs();
 }
 
+void AudioDestinationNode::OnInputNodeConnected() {
+  audio_lock()->AssertLocked();
+
+  if (!audio_device_) {
+    audio_device_.reset(
+        new AudioDevice(static_cast<int>(channel_count(NULL)), this));
+  }
+}
+
 void AudioDestinationNode::FillAudioBus(ShellAudioBus* audio_bus,
                                         bool* silence) {
   // This is called by Audio thread.
diff --git a/src/cobalt/audio/audio_destination_node.h b/src/cobalt/audio/audio_destination_node.h
index 7a760a0..50bfe7e 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,9 @@
   uint32 max_channel_count() const { return max_channel_count_; }
 
   // From AudioNode.
-  scoped_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 /*number_of_frames*/) OVERRIDE {
+  void OnInputNodeConnected() 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.cc b/src/cobalt/audio/audio_file_reader.cc
index ed61830..a858789 100644
--- a/src/cobalt/audio/audio_file_reader.cc
+++ b/src/cobalt/audio/audio_file_reader.cc
@@ -23,9 +23,10 @@
 
 // static
 scoped_ptr<AudioFileReader> AudioFileReader::TryCreate(const uint8* data,
-                                                       size_t size) {
+                                                       size_t size,
+                                                       SampleType sample_type) {
   // Try to create other type of audio file reader.
-  return AudioFileReaderWAV::TryCreate(data, size).Pass();
+  return AudioFileReaderWAV::TryCreate(data, size, sample_type).Pass();
 }
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader.h b/src/cobalt/audio/audio_file_reader.h
index 4897878..2d35a25 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 {
 
@@ -26,7 +28,8 @@
  public:
   virtual ~AudioFileReader() {}
 
-  static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size);
+  static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size,
+                                               SampleType sample_type);
 
   // Returns the sample data stored as float sample in planar form.  Note that
   // this function transfers the ownership of the data to the caller so it can
@@ -35,6 +38,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..d0875f2 100644
--- a/src/cobalt/audio/audio_file_reader_wav.cc
+++ b/src/cobalt/audio/audio_file_reader_wav.cc
@@ -46,15 +46,15 @@
 }  // namespace
 
 // static
-scoped_ptr<AudioFileReader> AudioFileReaderWAV::TryCreate(const uint8* data,
-                                                          size_t size) {
+scoped_ptr<AudioFileReader> AudioFileReaderWAV::TryCreate(
+    const uint8* data, size_t size, SampleType sample_type) {
   // Need at least the |kWAVChunkSize| bytes for this to be a WAV.
   if (size < kWAVChunkSize) {
     return scoped_ptr<AudioFileReader>();
   }
 
   scoped_ptr<AudioFileReaderWAV> audio_file_reader_wav(
-      new AudioFileReaderWAV(data, size));
+      new AudioFileReaderWAV(data, size, sample_type));
 
   if (!audio_file_reader_wav->is_valid()) {
     return scoped_ptr<AudioFileReader>();
@@ -63,8 +63,12 @@
   return make_scoped_ptr<AudioFileReader>(audio_file_reader_wav.release());
 }
 
-AudioFileReaderWAV::AudioFileReaderWAV(const uint8* data, size_t size)
-    : sample_rate_(0.f), number_of_frames_(0), number_of_channels_(0) {
+AudioFileReaderWAV::AudioFileReaderWAV(const uint8* data, size_t size,
+                                       SampleType sample_type)
+    : sample_rate_(0.f),
+      number_of_frames_(0),
+      number_of_channels_(0),
+      sample_type_(sample_type) {
   DCHECK_GE(size, kWAVRIFFChunkHeaderSize);
 
   if (ParseRIFFHeader(data, size)) {
@@ -149,6 +153,10 @@
 
   // Load channel count.
   number_of_channels_ = load_uint16_little_endian(data + offset + 2);
+  if (number_of_channels_ == 0) {
+    DLOG(ERROR) << "No channel on WAV.";
+    return false;
+  }
 
   // Load sample rate.
   sample_rate_ =
@@ -175,32 +183,51 @@
   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_));
+  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..dc3699a 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 {
@@ -26,15 +27,17 @@
 //   http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
 class AudioFileReaderWAV : public AudioFileReader {
  public:
-  static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size);
+  static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size,
+                                               SampleType sample_type);
 
   scoped_array<uint8> sample_data() OVERRIDE { return sample_data_.Pass(); }
   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);
+  AudioFileReaderWAV(const uint8* data, size_t size, SampleType sample_type);
 
   bool ParseRIFFHeader(const uint8* data, size_t size);
   void ParseChunks(const uint8* data, size_t size);
@@ -49,6 +52,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..01a578d 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"
@@ -110,9 +111,12 @@
   // Disconnects an AudioNode's output.
   void Disconnect(uint32 output, script::ExceptionState* exception_state);
 
+  // Called when a new input node has been connected.
+  virtual void OnInputNodeConnected() {}
+
   // 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 2f42724..b7ac086 100644
--- a/src/cobalt/audio/audio_node_input.cc
+++ b/src/cobalt/audio/audio_node_input.cc
@@ -197,6 +197,7 @@
 
   output->AddInput(this);
   outputs_.insert(output);
+  owner_node_->OnInputNodeConnected();
 }
 
 void AudioNodeInput::Disconnect(AudioNodeOutput* output) {
@@ -232,13 +233,19 @@
   // TODO: Consider computing computedNumberOfChannels and do up-mix or
   // down-mix base on computedNumberOfChannels. The current implementation
   // is based on the fact that the channelCountMode is max.
-  DCHECK_EQ(owner_node_->channel_count_mode(), AudioNode::kMax);
+  if (owner_node_->channel_count_mode() != AudioNode::kMax) {
+    DLOG(ERROR) << "Unsupported channel count mode: "
+                << owner_node_->channel_count_mode();
+    return;
+  }
+
   // Pull audio buffer from connected audio input. When an input is connected
   // from one or more AudioNode outputs. Fan-in is supported.
   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/base/base.gyp b/src/cobalt/base/base.gyp
index 19652ec..29ad3e2 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -49,7 +49,6 @@
         'poller.h',
         'polymorphic_downcast.h',
         'polymorphic_equatable.h',
-        'sanitizer_options.cc',
         'source_location.h',
         'stop_watch.cc',
         'stop_watch.h',
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index c4bcc0c..7331781 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -249,6 +249,7 @@
       {10LL * 1024LL * 1024LL * 1024LL, 1024LL * 1024LL * 1024LL, "GB"},
       {10LL * 1024LL * 1024LL, 1024LL * 1024LL, "MB"},
       {10LL * 1024LL, 1024LL, "KB"},
+      {0LL, 1L, "B"},
   };
   return ThresholdList<cval::SizeInBytes>(thresholds, arraysize(thresholds));
 }
@@ -291,21 +292,23 @@
   if (negative) {
     oss << "-";
   }
+
+  oss << std::fixed << std::setprecision(1) << std::setfill('0');
   if (value_in_us > kHour) {
-    oss << value_in_us / kHour << ":" << std::setfill('0') << std::setw(2)
-        << (value_in_us % kHour) / kMinute << ":" << std::setfill('0')
+    oss << value_in_us / kHour << ":" << std::setw(2)
+        << (value_in_us % kHour) / kMinute << ":"
         << std::setw(2) << (value_in_us % kMinute) / kSecond << "h";
   } else if (value_in_us > kMinute) {
-    oss << value_in_us / kMinute << ":" << std::setfill('0') << std::setw(2)
+    oss << value_in_us / kMinute << ":" << std::setw(2)
         << (value_in_us % kMinute) / kSecond << "m";
-  } else if (value_in_us > kSecond * 10) {
-    oss << value_in_us / kSecond << "s";
-  } else if (value_in_us > kMillisecond * 2) {
-    oss << value_in_us / kMillisecond << "ms";
-  } else if (value_in_us > 0) {
-    oss << value_in_us << "us";
+  } else if (value_in_us > kSecond) {
+    oss << std::setw(1)
+        << static_cast<double>(value_in_us) / kSecond << "s";
+  } else if (value_in_us > kMillisecond) {
+    oss << std::setw(1)
+        << static_cast<double>(value_in_us) / kMillisecond << "ms";
   } else {
-    oss << value_in_us;
+    oss << value_in_us << "us";
   }
 
   return oss.str();
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc
index 9751266..c1941d3 100644
--- a/src/cobalt/base/c_val_test.cc
+++ b/src/cobalt/base/c_val_test.cc
@@ -16,6 +16,7 @@
 
 #include <limits>
 
+#include "base/time.h"
 #include "cobalt/base/c_val.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -64,7 +65,27 @@
   base::CValManager* cvm = base::CValManager::GetInstance();
   base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
   EXPECT_TRUE(result);
-  EXPECT_EQ(*result, "4095MB");
+  EXPECT_EQ(*result, "4294M");
+}
+
+TEST(CValTest, RegisterAndPrintU32Zero) {
+  const std::string cval_name = "32-bit unsigned int";
+  const uint32_t cval_value = 0;
+  base::CVal<uint32_t> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0");
+}
+
+TEST(CValTest, RegisterAndPrintU32K) {
+  const std::string cval_name = "32-bit unsigned int";
+  const uint32_t cval_value = 50000;
+  base::CVal<uint32_t> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50K");
 }
 
 TEST(CValTest, RegisterAndPrintU64) {
@@ -74,7 +95,7 @@
   base::CValManager* cvm = base::CValManager::GetInstance();
   base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
   EXPECT_TRUE(result);
-  EXPECT_EQ(*result, "17592186044415MB");
+  EXPECT_EQ(*result, "18446744073709M");
 }
 
 TEST(CValTest, RegisterAndPrintS32) {
@@ -97,6 +118,266 @@
   EXPECT_EQ(*result, "-9223372036854775808");
 }
 
+TEST(CValTest, RegisterAndPrintSizeInBytesB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(500);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "500B");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesZero) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(0);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0B");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesKB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "48KB");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesMB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "47MB");
+}
+
+TEST(CValTest, RegisterAndPrintSizeInBytesGB) {
+  const std::string cval_name = "SizeInBytes";
+  cval::SizeInBytes cval_value(50000000000UL);
+  base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "46GB");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMicroseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaZero) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(0));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "0us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMilliseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsFraction) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(5500));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.5ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSecondsSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaSecondsFraction) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5500));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5.5s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutesSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaMinutesAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHours) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(50));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "50:00:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursSingleDigit) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(5));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "5:00:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(92));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32:00h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutesAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92 * 60 + 32));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:32:32h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(60 * 60 + 32));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "1:00:32h");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMicroseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3us");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMilliseconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3.0ms");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeSeconds) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromSeconds(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3.0s");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMinutes) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromMinutes(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3:00m");
+}
+
+TEST(CValTest, RegisterAndPrintTimeDeltaNegativeHours) {
+  const std::string cval_name = "TimeDelta";
+  base::TimeDelta cval_value(base::TimeDelta::FromHours(-3));
+  base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description.");
+  base::CValManager* cvm = base::CValManager::GetInstance();
+  base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name);
+  EXPECT_TRUE(result);
+  EXPECT_EQ(*result, "-3:00:00h");
+}
+
 TEST(CValTest, RegisterAndPrintFloat) {
   const std::string cval_name = "float";
   const float cval_value = 3.14159f;
diff --git a/src/cobalt/base/console_commands.cc b/src/cobalt/base/console_commands.cc
index eecf31a..73e4f1b 100644
--- a/src/cobalt/base/console_commands.cc
+++ b/src/cobalt/base/console_commands.cc
@@ -50,10 +50,15 @@
                                           const std::string& message) const {
   DCHECK_GT(channel.length(), size_t(0));
   base::AutoLock auto_lock(lock_);
-  CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel);
-  if (iter != command_channel_map_.end()) {
+  CommandHandlerMap::const_iterator iter =
+      command_channel_map_.lower_bound(channel);
+  bool handler_found = false;
+  while (iter != command_channel_map_.end() && iter->first == channel) {
+    handler_found = true;
     iter->second->callback().Run(message);
-  } else {
+    ++iter;
+  }
+  if (!handler_found) {
     DLOG(WARNING) << "No command handler registered for channel: " << channel;
   }
 }
@@ -71,8 +76,9 @@
 std::string ConsoleCommandManager::GetShortHelp(
     const std::string& channel) const {
   base::AutoLock auto_lock(lock_);
-  CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel);
-  if (iter != command_channel_map_.end()) {
+  for (CommandHandlerMap::const_iterator iter =
+           command_channel_map_.lower_bound(channel);
+       iter != command_channel_map_.end() && iter->first == channel; ++iter) {
     return iter->second->short_help();
   }
   return "No help available for unregistered channel: " + channel;
@@ -81,8 +87,9 @@
 std::string ConsoleCommandManager::GetLongHelp(
     const std::string& channel) const {
   base::AutoLock auto_lock(lock_);
-  CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel);
-  if (iter != command_channel_map_.end()) {
+  for (CommandHandlerMap::const_iterator iter =
+           command_channel_map_.lower_bound(channel);
+       iter != command_channel_map_.end() && iter->first == channel; ++iter) {
     return iter->second->long_help();
   }
   return "No help available for unregistered channel: " + channel;
@@ -92,14 +99,22 @@
     const CommandHandler* handler) {
   DCHECK_GT(handler->channel().length(), size_t(0));
   base::AutoLock auto_lock(lock_);
-  command_channel_map_[handler->channel()] = handler;
+  command_channel_map_.insert(std::make_pair(handler->channel(), handler));
 }
 
 void ConsoleCommandManager::UnregisterCommandHandler(
     const CommandHandler* handler) {
-  DCHECK_GT(handler->channel().length(), size_t(0));
+  const std::string& channel = handler->channel();
+  DCHECK_GT(channel.length(), size_t(0));
   base::AutoLock auto_lock(lock_);
-  command_channel_map_.erase(handler->channel());
+  for (CommandHandlerMap::iterator iter =
+           command_channel_map_.lower_bound(channel);
+       iter != command_channel_map_.end() && iter->first == channel; ++iter) {
+    if (iter->second == handler) {
+      command_channel_map_.erase(iter);
+      break;
+    }
+  }
 }
 
 #else   // ENABLE_DEBUG_CONSOLE
diff --git a/src/cobalt/base/console_commands.h b/src/cobalt/base/console_commands.h
index 47144d6..f12113e 100644
--- a/src/cobalt/base/console_commands.h
+++ b/src/cobalt/base/console_commands.h
@@ -89,7 +89,7 @@
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   // Command handler map type.
-  typedef std::map<std::string, const CommandHandler*> CommandHandlerMap;
+  typedef std::multimap<std::string, const CommandHandler*> CommandHandlerMap;
 
   // Methods to register/unregister command handlers.
   // These are intended only to be called from the command handler objects.
diff --git a/src/cobalt/base/deep_link_event.h b/src/cobalt/base/deep_link_event.h
index 5e3df4b..b5c2afa 100644
--- a/src/cobalt/base/deep_link_event.h
+++ b/src/cobalt/base/deep_link_event.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "base/compiler_specific.h"
+#include "base/string_util.h"
 #include "cobalt/base/event.h"
 
 namespace base {
@@ -28,6 +29,9 @@
  public:
   explicit DeepLinkEvent(const std::string& link) : link_(link) {}
   const std::string& link() const { return link_; }
+  bool IsH5vccLink() const {
+    return StartsWithASCII(link_, "h5vcc", true);
+  }
 
   BASE_EVENT_SUBCLASS(DeepLinkEvent);
 
diff --git a/src/cobalt/base/sanitizer_options.cc b/src/cobalt/base/sanitizer_options.cc
deleted file mode 100644
index b5311a5..0000000
--- a/src/cobalt/base/sanitizer_options.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-#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() { }
-
-// The callbacks we define here will be called from the sanitizer runtime, but
-// aren't referenced from the executable. We must ensure that those
-// callbacks are not sanitizer-instrumented, and that they aren't stripped by
-// the linker.
-#define SANITIZER_HOOK_ATTRIBUTE          \
-  extern "C"                              \
-  __attribute__((no_sanitize_address))    \
-  __attribute__((no_sanitize_memory))     \
-  __attribute__((no_sanitize_thread))     \
-  __attribute__((visibility("default")))  \
-  __attribute__((used))
-
-extern "C" char kLSanDefaultSuppressions[];
-
-char kLSanDefaultSuppressions[] =
-    // 16-byte leak from a call to calloc() in this library.
-    "leak:egl_gallium.so\n";
-
-SANITIZER_HOOK_ATTRIBUTE const char *__lsan_default_suppressions() {
-  return kLSanDefaultSuppressions;
-}
-
-#endif  // defined(ADDRESS_SANITIZER)
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index b2e24cd..cae31dd 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -83,6 +83,7 @@
     MacroOpWithNameAndValue(class_selector_prefix, ".")                 \
     MacroOpWithNameAndValue(comment_node_name, "#comment")              \
     MacroOpWithNameAndValue(document_name, "#document")                 \
+    MacroOpWithNameAndValue(domcontentloaded, "DOMContentLoaded")       \
     MacroOpWithNameAndValue(empty_pseudo_class_selector, "empty")       \
     MacroOpWithNameAndValue(focus_pseudo_class_selector, "focus")       \
     MacroOpWithNameAndValue(hover_pseudo_class_selector, "hover")       \
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
index be7915c..9a486e9 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
@@ -280,6 +280,15 @@
 JSBool get_length(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<AnonymousIndexedGetterInterface>())) {
+    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/MozjsAnonymousNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
index 388c186..0d132c4 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
@@ -371,6 +371,15 @@
 JSBool get_length(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<AnonymousNamedIndexedGetterInterface>())) {
+    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/MozjsArbitraryInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
index 71b31f2..38699b2 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_arbitraryProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -213,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);
 
@@ -247,6 +265,15 @@
     NOTREACHED();
     return false;
   }
+  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/MozjsBaseInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc
index bf4b5a3..c6f9524 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_baseAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<BaseInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -225,6 +234,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<BaseInterface>())) {
+    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 1796c62..2599232 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
@@ -189,6 +189,15 @@
 JSBool get_booleanProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -211,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);
 
@@ -245,6 +263,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -291,6 +318,15 @@
     NOTREACHED();
     return false;
   }
+  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 0b7e907..fce394f 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
@@ -193,6 +193,15 @@
 JSBool get_callbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -215,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);
 
@@ -237,6 +255,15 @@
 JSBool get_nullableCallbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -259,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);
 
@@ -293,6 +329,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -339,6 +384,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -385,6 +439,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -431,6 +494,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -477,6 +549,15 @@
     NOTREACHED();
     return false;
   }
+  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 d4321ff..468faf3 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
@@ -193,6 +193,15 @@
 JSBool get_callbackAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -215,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);
 
@@ -249,6 +267,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -295,6 +322,15 @@
     NOTREACHED();
     return false;
   }
+  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 e8e5a7d..2610e00 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
@@ -192,6 +192,15 @@
 JSBool get_enabledAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -214,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);
 
@@ -238,6 +256,15 @@
 JSBool get_disabledAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -260,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);
 
@@ -296,6 +332,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -326,6 +371,15 @@
     NOTREACHED();
     return false;
   }
+  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/MozjsConstructorWithArgumentsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc
index 09ccf2a..756c696 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_longArg(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<ConstructorWithArgumentsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -213,6 +222,15 @@
 JSBool get_booleanArg(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<ConstructorWithArgumentsInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -235,6 +253,15 @@
 JSBool get_stringArg(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<ConstructorWithArgumentsInterface>())) {
+    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 7fb410b..7092da5 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
@@ -189,6 +189,15 @@
 JSBool get_property(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -211,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);
 
@@ -233,6 +251,15 @@
 JSBool get_readOnlyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -255,6 +282,15 @@
 JSBool get_readOnlyTokenProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -277,6 +313,15 @@
 JSBool get_nullIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -299,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);
 
@@ -321,6 +375,15 @@
 JSBool get_undefinedIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -343,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);
 
@@ -365,6 +437,15 @@
 JSBool get_nullableUndefinedIsEmptyProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -387,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 9d7f281..7c48cfc 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
@@ -371,6 +371,15 @@
 JSBool get_length(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -393,6 +402,15 @@
 JSBool get_propertyOnDerivedClass(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -415,6 +433,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);
 
@@ -449,6 +476,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -501,6 +537,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -559,6 +604,15 @@
     NOTREACHED();
     return false;
   }
+  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/MozjsDerivedInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc
index 8d64be2..5e26084 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_derivedAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<DerivedInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -225,6 +234,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DerivedInterface>())) {
+    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 3f3d4a7..5e3f995 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_disabledProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -213,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);
 
@@ -247,6 +265,15 @@
     NOTREACHED();
     return false;
   }
+  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 c54bde2..38c99f1 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
@@ -200,6 +200,15 @@
 JSBool get_enumProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -222,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/MozjsExceptionObjectInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc
index 64e3ca6..d9f10ef 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc
@@ -190,6 +190,15 @@
 JSBool get_error(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<ExceptionObjectInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -212,6 +221,15 @@
 JSBool get_message(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<ExceptionObjectInterface>())) {
+    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 5f1a396..6ab9aab 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
@@ -191,6 +191,15 @@
 JSBool get_attributeThrowsException(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -213,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);
 
@@ -247,6 +265,15 @@
     NOTREACHED();
     return false;
   }
+  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/MozjsExtendedIDLAttributesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc
index 07cceda..e197c87 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc
@@ -201,6 +201,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<ExtendedIDLAttributesInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -208,10 +217,10 @@
       WrapperPrivate::GetFromObject(context, object);
   ExtendedIDLAttributesInterface* impl =
       wrapper_private->wrappable<ExtendedIDLAttributesInterface>().get();
-  MozjsGlobalEnvironment* global_environment =
+  MozjsGlobalEnvironment* callwith_global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
 
-  impl->CallWithSettings(global_environment->GetEnvironmentSettings());
+  impl->CallWithSettings(callwith_global_environment->GetEnvironmentSettings());
   result_value.set(JS::UndefinedHandleValue);
   return !exception_state.is_exception_set();
 }
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
index d2cfb40..9ba778e 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
@@ -202,6 +202,15 @@
 JSBool get_previous(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -224,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);
 
@@ -246,6 +264,15 @@
 JSBool get_next(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -268,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/MozjsGlobalInterfaceParent.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc
index d742500..a0ddac4 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc
@@ -201,6 +201,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<GlobalInterfaceParent>())) {
+    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/MozjsIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
index 752334e..66f6497 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
@@ -295,6 +295,15 @@
 JSBool get_length(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<IndexedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -329,6 +338,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<IndexedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -375,6 +393,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<IndexedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -427,6 +454,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<IndexedGetterInterface>())) {
+    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/MozjsInterfaceWithUnsupportedProperties.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc
index a578e9c..56ad080 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc
@@ -189,6 +189,15 @@
 JSBool get_supportedAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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<InterfaceWithUnsupportedProperties>())) {
+    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/MozjsNamedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
index 918f252..51f1f54 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
@@ -307,6 +307,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NamedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -353,6 +362,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NamedGetterInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -405,6 +423,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<NamedGetterInterface>())) {
+    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 2965f0f..2715fd9 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
@@ -371,6 +371,15 @@
 JSBool get_length(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -393,6 +402,15 @@
 JSBool get_propertyOnBaseClass(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -415,6 +433,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);
 
@@ -449,6 +476,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -501,6 +537,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -559,6 +604,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -611,6 +665,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -669,6 +732,15 @@
     NOTREACHED();
     return false;
   }
+  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 69fab04..9b732fe 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
@@ -193,6 +193,15 @@
 JSBool get_nestedForwardingAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -215,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 c0010f4..e7533d8 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
@@ -193,6 +193,15 @@
 JSBool get_nullableBooleanProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -215,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);
 
@@ -237,6 +255,15 @@
 JSBool get_nullableNumericProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -259,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);
 
@@ -281,6 +317,15 @@
 JSBool get_nullableStringProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -303,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);
 
@@ -325,6 +379,15 @@
 JSBool get_nullableObjectProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -347,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);
 
@@ -381,6 +453,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -427,6 +508,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -461,6 +551,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -507,6 +606,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -541,6 +649,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -587,6 +704,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -621,6 +747,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -667,6 +802,15 @@
     NOTREACHED();
     return false;
   }
+  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 4bf1c6e..eb604d8 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
@@ -189,6 +189,15 @@
 JSBool get_byteProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -211,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);
 
@@ -233,6 +251,15 @@
 JSBool get_octetProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -255,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);
 
@@ -277,6 +313,15 @@
 JSBool get_shortProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -299,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);
 
@@ -321,6 +375,15 @@
 JSBool get_unsignedShortProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -343,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);
 
@@ -365,6 +437,15 @@
 JSBool get_longProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -387,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);
 
@@ -409,6 +499,15 @@
 JSBool get_unsignedLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -431,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);
 
@@ -453,6 +561,15 @@
 JSBool get_longLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -475,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);
 
@@ -497,6 +623,15 @@
 JSBool get_unsignedLongLongProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -519,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);
 
@@ -541,6 +685,15 @@
 JSBool get_doubleProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -563,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);
 
@@ -585,6 +747,15 @@
 JSBool get_unrestrictedDoubleProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -607,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);
 
@@ -641,6 +821,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -687,6 +876,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -721,6 +919,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -767,6 +974,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -801,6 +1017,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -847,6 +1072,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -893,6 +1127,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -927,6 +1170,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -961,6 +1213,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1007,6 +1268,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1041,6 +1311,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1087,6 +1366,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1121,6 +1409,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1167,6 +1464,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1201,6 +1507,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1247,6 +1562,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1293,6 +1617,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1327,6 +1660,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1361,6 +1703,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -1407,6 +1758,15 @@
     NOTREACHED();
     return false;
   }
+  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 1b20995..77631f6 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
@@ -201,6 +201,15 @@
 JSBool get_arbitraryObject(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -223,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);
 
@@ -245,6 +263,15 @@
 JSBool get_baseInterface(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -267,6 +294,15 @@
 JSBool get_derivedInterface(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -289,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);
 
@@ -311,6 +356,15 @@
 JSBool get_objectProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -333,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/MozjsOperationsTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc
index b30eecb..293e7cd 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc
@@ -205,6 +205,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -239,6 +248,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -273,6 +291,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -317,6 +344,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -415,6 +451,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -473,6 +518,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -501,6 +555,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -547,6 +610,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -593,6 +665,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -663,6 +744,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -738,6 +828,10 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
       if (arg.isNumber()) {
         return fcn_overloadedFunction2(
                   context, argc, vp);
@@ -759,8 +853,12 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
       if (arg.isObject() ? wrapper_factory->DoesObjectImplementInterface(
-              JSVAL_TO_OBJECT(arg), base::GetTypeId<ArbitraryInterface>()) :
+              object, base::GetTypeId<ArbitraryInterface>()) :
               false) {
         return fcn_overloadedFunction5(
                   context, argc, vp);
@@ -795,6 +893,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -841,6 +948,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -883,6 +999,10 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
       if (arg.isNullOrUndefined()) {
         return fcn_overloadedNullable2(
                   context, argc, vp);
@@ -917,6 +1037,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -951,6 +1080,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -999,6 +1137,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -1085,6 +1232,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -1131,6 +1287,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -1159,6 +1324,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -1205,6 +1379,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<OperationsTestInterface>())) {
+    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 870e3a3..dfc7ffa 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
@@ -193,6 +193,15 @@
 JSBool get_forwardingAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -215,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/MozjsSingleOperationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc
index aaf933d..36e73dd 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc
@@ -27,6 +27,7 @@
 #include "cobalt/script/logging_exception_state.h"
 #include "cobalt/script/mozjs/conversion_helpers.h"
 #include "cobalt/script/mozjs/mozjs_callback_interface.h"
+#include "cobalt/script/mozjs/util/exception_helpers.h"
 #include "third_party/mozjs/js/src/jsapi.h"
 #include "third_party/mozjs/js/src/jscntxt.h"
 
@@ -40,6 +41,7 @@
 using cobalt::script::mozjs::FromJSValue;
 using cobalt::script::mozjs::GetCallableForCallbackInterface;
 using cobalt::script::mozjs::ToJSValue;
+using cobalt::script::mozjs::util::GetExceptionString;
 }  // namespace
 
 namespace cobalt {
@@ -58,13 +60,13 @@
     bool* had_exception) const {
   bool success = false;
   base::optional<int32_t > cobalt_return_value;
+  JSAutoRequest auto_request(context_);
   JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_);
 
   // This could be set to NULL if it was garbage collected.
   JS::RootedObject implementing_object(context_, implementing_object_.Get());
   DLOG_IF(WARNING, !implementing_object) << "Implementing object is NULL.";
   if (implementing_object) {
-    JSAutoRequest auto_request(context_);
     JSAutoCompartment auto_compartment(context_, implementing_object);
 
     // Get callable object.
@@ -90,7 +92,8 @@
       DCHECK(function);
       success = JS::Call(context_, this_value, function, kNumArguments, args,
                          return_value.address());
-      DLOG_IF(WARNING, !success) << "Exception in callback.";
+      DLOG_IF(WARNING, !success) << "Exception in callback: "
+                                 << GetExceptionString(context_);
       if (success) {
         LoggingExceptionState exception_state;
         FromJSValue(context_, return_value, 0, &exception_state,
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc
index 02ad356..82a5868 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc
@@ -425,6 +425,10 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
       if (arg.isNumber()) {
         return staticfcn_staticFunction2(
                   context, argc, vp);
@@ -446,8 +450,12 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
       if (arg.isObject() ? wrapper_factory->DoesObjectImplementInterface(
-              JSVAL_TO_OBJECT(arg), base::GetTypeId<ArbitraryInterface>()) :
+              object, base::GetTypeId<ArbitraryInterface>()) :
               false) {
         return staticfcn_staticFunction5(
                   context, argc, vp);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
index b1b8349..428506c 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
@@ -189,6 +189,15 @@
 JSBool get_theStringifierAttribute(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -211,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/MozjsStringifierOperationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc
index 6b6dd67..8d33990 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc
@@ -201,6 +201,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<StringifierOperationInterface>())) {
+    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/MozjsTargetInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc
index 644db8f..82de15a 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc
@@ -201,6 +201,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<TargetInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
   MozjsExceptionState exception_state(context);
   JS::RootedValue result_value(context);
 
@@ -229,6 +238,15 @@
     NOTREACHED();
     return false;
   }
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<TargetInterface>())) {
+    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 5c9b31f..e6cf73b 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
@@ -197,6 +197,15 @@
 JSBool get_unionProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -219,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);
 
@@ -241,6 +259,15 @@
 JSBool get_unionWithNullableMemberProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -263,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);
 
@@ -285,6 +321,15 @@
 JSBool get_nullableUnionProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -307,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);
 
@@ -329,6 +383,15 @@
 JSBool get_unionBaseProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -351,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 0a5ddd1..212ecca 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
@@ -382,6 +382,15 @@
 JSBool get_windowProperty(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -404,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);
 
@@ -426,6 +444,15 @@
 JSBool get_window(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     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);
 
@@ -460,6 +487,15 @@
     NOTREACHED();
     return false;
   }
+  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);
 
@@ -467,12 +503,12 @@
       WrapperPrivate::GetFromObject(context, object);
   Window* impl =
       wrapper_private->wrappable<Window>().get();
-  MozjsGlobalEnvironment* global_environment =
+  MozjsGlobalEnvironment* callwith_global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
 
   if (!exception_state.is_exception_set()) {
     ToJSValue(context,
-              impl->GetStackTrace(global_environment->GetStackTrace()),
+              impl->GetStackTrace(callwith_global_environment->GetStackTrace()),
               &result_value);
   }
   if (!exception_state.is_exception_set()) {
@@ -496,6 +532,15 @@
     NOTREACHED();
     return false;
   }
+  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/code_generator.py b/src/cobalt/bindings/mozjs/code_generator.py
index 12ac5fb..c99c10a 100644
--- a/src/cobalt/bindings/mozjs/code_generator.py
+++ b/src/cobalt/bindings/mozjs/code_generator.py
@@ -42,8 +42,8 @@
   def inherits_interface(self, interface_name, arg):
     return ('%s.isObject() ?'
             ' wrapper_factory->DoesObjectImplementInterface(\n'
-            '              JSVAL_TO_OBJECT(%s), base::GetTypeId<%s>()) :\n'
-            '              false') % (arg, arg, interface_name)
+            '              object, base::GetTypeId<%s>()) :\n'
+            '              false') % (arg, interface_name)
 
   def is_number(self, arg):
     return '%s.isNumber()' % arg
diff --git a/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template b/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template
index f307bbc..895e453 100644
--- a/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template
@@ -20,6 +20,7 @@
 #include "cobalt/script/logging_exception_state.h"
 #include "cobalt/script/mozjs/conversion_helpers.h"
 #include "cobalt/script/mozjs/mozjs_callback_interface.h"
+#include "cobalt/script/mozjs/util/exception_helpers.h"
 #include "third_party/mozjs/js/src/jsapi.h"
 #include "third_party/mozjs/js/src/jscntxt.h"
 {% endblock includes %}
@@ -30,6 +31,7 @@
 using cobalt::script::mozjs::FromJSValue;
 using cobalt::script::mozjs::GetCallableForCallbackInterface;
 using cobalt::script::mozjs::ToJSValue;
+using cobalt::script::mozjs::util::GetExceptionString;
 {% endblock using_directives %}
 
 {% block implementation %}
@@ -56,13 +58,13 @@
 {% if overload.type != 'void' %}
   {{overload.type}} cobalt_return_value;
 {% endif %}
+  JSAutoRequest auto_request(context_);
   JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_);
 
   // This could be set to NULL if it was garbage collected.
   JS::RootedObject implementing_object(context_, implementing_object_.Get());
   DLOG_IF(WARNING, !implementing_object) << "Implementing object is NULL.";
   if (implementing_object) {
-    JSAutoRequest auto_request(context_);
     JSAutoCompartment auto_compartment(context_, implementing_object);
 
     // Get callable object.
@@ -90,7 +92,8 @@
       DCHECK(function);
       success = JS::Call(context_, this_value, function, kNumArguments, args,
                          return_value.address());
-      DLOG_IF(WARNING, !success) << "Exception in callback.";
+      DLOG_IF(WARNING, !success) << "Exception in callback: "
+                                 << GetExceptionString(context_);
 {% if overload.type != 'void' %}
       if (success) {
         LoggingExceptionState exception_state;
diff --git a/src/cobalt/bindings/mozjs/templates/interface.cc.template b/src/cobalt/bindings/mozjs/templates/interface.cc.template
index eb4e553..b92a526 100644
--- a/src/cobalt/bindings/mozjs/templates/interface.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/interface.cc.template
@@ -15,6 +15,7 @@
  #}
 {% from 'macros.cc.template' import add_extra_arguments %}
 {% from 'macros.cc.template' import call_cobalt_function %}
+{% from 'macros.cc.template' import check_if_object_implements_interface with context %}
 {% from 'macros.cc.template' import constructor_implementation with context %}
 {% from 'macros.cc.template' import function_implementation with context %}
 {% from 'macros.cc.template' import get_impl_class_instance %}
@@ -458,6 +459,7 @@
 JSBool get_{{attribute.idl_name}}(
     JSContext* context, JS::HandleObject object, JS::HandleId id,
     JS::MutableHandleValue vp) {
+{{ check_if_object_implements_interface() }}
 {{ nonstatic_function_prologue(impl_class) }}
 {% endif %}
 {{ call_cobalt_function(impl_class, attribute.type,
@@ -480,6 +482,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/bindings/mozjs/templates/macros.cc.template b/src/cobalt/bindings/mozjs/templates/macros.cc.template
index f9153c7..58b5669 100644
--- a/src/cobalt/bindings/mozjs/templates/macros.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/macros.cc.template
@@ -15,6 +15,21 @@
  #}
 
 {#
+ # Checks if object implements interface.
+ #}
+{% macro check_if_object_implements_interface() %}
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<{{impl_class}}>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
+{%- endmacro %}
+
+{#
  # Function body for operation bindings.
  # Parameters:
  #     operation: The operation context object
@@ -36,6 +51,7 @@
     NOTREACHED();
     return false;
   }
+{{ check_if_object_implements_interface() }}
 {{ nonstatic_function_prologue(impl_class) }}
 {% endif %}
 {% call(arguments_list) extract_arguments(operation) %}
@@ -256,10 +272,10 @@
  #}
 {% macro add_extra_arguments(arguments_list, raises_exception, call_with) %}
 {% if call_with %}
-  MozjsGlobalEnvironment* global_environment =
+  MozjsGlobalEnvironment* callwith_global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
-{% do arguments_list.insert(0,
-                            'global_environment->Get%s()'|format(call_with)) %}
+{% do arguments_list.insert(
+    0, 'callwith_global_environment->Get%s()'|format(call_with)) %}
 {% endif %}
 {% do arguments_list.append('&exception_state') if raises_exception %}
 {%- endmacro %}
@@ -372,6 +388,10 @@
       MozjsGlobalEnvironment* global_environment =
           static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
       WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+      JS::RootedObject object(context);
+      if (arg.isObject()) {
+        object = JSVAL_TO_OBJECT(arg);
+      }
 {% endif %}
 {% for test, overload in resolution_tests %}
       if ({{test("arg")}}) {
diff --git a/src/cobalt/bindings/testing/bindings_test_base.h b/src/cobalt/bindings/testing/bindings_test_base.h
index 25e9ea2..f634699 100644
--- a/src/cobalt/bindings/testing/bindings_test_base.h
+++ b/src/cobalt/bindings/testing/bindings_test_base.h
@@ -25,6 +25,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
+#include "cobalt/script/wrappable.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -71,6 +72,17 @@
     return global_environment_->EvaluateScript(source, out_result);
   }
 
+  bool EvaluateScript(const std::string& script,
+                      const scoped_refptr<script::Wrappable>& owning_object,
+                      base::optional<script::OpaqueHandleHolder::Reference>*
+                          out_opaque_handle = NULL) {
+    scoped_refptr<script::SourceCode> source =
+        script::SourceCode::CreateSourceCode(
+            script, base::SourceLocation("[object BindingsTestBase]", 1, 1));
+    return global_environment_->EvaluateScript(source, owning_object,
+                                               out_opaque_handle);
+  }
+
   void CollectGarbage() { engine_->CollectGarbage(); }
 
  protected:
diff --git a/src/cobalt/bindings/testing/evaluate_script_test.cc b/src/cobalt/bindings/testing/evaluate_script_test.cc
new file mode 100644
index 0000000..61d74fa
--- /dev/null
+++ b/src/cobalt/bindings/testing/evaluate_script_test.cc
@@ -0,0 +1,87 @@
+/*
+ * 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/bindings/testing/arbitrary_interface.h"
+#include "cobalt/bindings/testing/bindings_test_base.h"
+#include "cobalt/bindings/testing/object_type_bindings_interface.h"
+#include "cobalt/bindings/testing/script_object_owner.h"
+
+using cobalt::script::OpaqueHandleHolder;
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+namespace cobalt {
+namespace bindings {
+namespace testing {
+
+namespace {
+class EvaluateScriptTest
+    : public InterfaceBindingsTest<ObjectTypeBindingsInterface> {};
+}  // namespace
+
+TEST_F(EvaluateScriptTest, TwoArguments) {
+  std::string result;
+
+  std::string script =
+      "function fib(n) {\n"
+      "  if (n <= 0) {\n"
+      "    return 0;\n"
+      "  } else if (n == 1) {\n"
+      "    return 1;\n"
+      "  } else {\n"
+      "    return fib(n - 1) + fib(n - 2);\n"
+      "  }\n"
+      "}\n"
+      "fib(8)";
+  EXPECT_TRUE(EvaluateScript(script, &result));
+  EXPECT_STREQ("21", result.c_str());
+}
+
+TEST_F(EvaluateScriptTest, ThreeArguments) {
+  std::string script =
+      "function fib(n) {\n"
+      "  if (n <= 0) {\n"
+      "    return 0;\n"
+      "  } else if (n == 1) {\n"
+      "    return 1;\n"
+      "  } else {\n"
+      "    return fib(n - 1) + fib(n - 2);\n"
+      "  }\n"
+      "}\n"
+      "fib(8)";
+
+  // Call with null out handle.
+  scoped_refptr<StrictMock<ArbitraryInterface> > arbitrary_interface_mock(
+      new StrictMock<ArbitraryInterface>());
+  EXPECT_TRUE(EvaluateScript(script, arbitrary_interface_mock, NULL));
+
+  // Call with non-null, but unset optional handle.
+  base::optional<OpaqueHandleHolder::Reference> opaque_handle;
+  EXPECT_TRUE(EvaluateScript(script, arbitrary_interface_mock, &opaque_handle));
+  ASSERT_FALSE(opaque_handle->referenced_object().IsNull());
+
+  EXPECT_CALL(test_mock(), object_property())
+      .WillOnce(Return(&opaque_handle->referenced_object()));
+  std::string result;
+  EXPECT_TRUE(EvaluateScript("test.objectProperty == 21;", &result));
+  EXPECT_STREQ("true", result.c_str());
+}
+
+}  // namespace testing
+}  // namespace bindings
+}  // namespace cobalt
diff --git a/src/cobalt/bindings/testing/object_type_bindings_test.cc b/src/cobalt/bindings/testing/object_type_bindings_test.cc
index 7c5cbf0..e80d859 100644
--- a/src/cobalt/bindings/testing/object_type_bindings_test.cc
+++ b/src/cobalt/bindings/testing/object_type_bindings_test.cc
@@ -273,6 +273,18 @@
   EXPECT_THAT(result.c_str(), StartsWith("TypeError:"));
 }
 
+TEST_F(UserObjectBindingsTest, CallWrongObjectType) {
+  std::string result;
+  EXPECT_TRUE(EvaluateScript("var obj = new Object()", NULL));
+  EXPECT_TRUE(EvaluateScript("var arb = new ArbitraryInterface()", NULL));
+
+  // Calling a function with the wrong object type is a type error.
+  EXPECT_FALSE(
+      EvaluateScript("obj.arbitraryFunction = arb.arbitraryFunction;\n"
+                     "obj.arbitraryFunction();", &result));
+  EXPECT_THAT(result.c_str(), StartsWith("TypeError:"));
+}
+
 }  // namespace testing
 }  // namespace bindings
 }  // namespace cobalt
diff --git a/src/cobalt/bindings/testing/testing.gyp b/src/cobalt/bindings/testing/testing.gyp
index 0286dcd..e9f6492 100644
--- a/src/cobalt/bindings/testing/testing.gyp
+++ b/src/cobalt/bindings/testing/testing.gyp
@@ -129,6 +129,7 @@
         'dependent_interface_test.cc',
         'dom_string_bindings_test.cc',
         'enumeration_bindings_test.cc',
+        'evaluate_script_test.cc',
         'exceptions_bindings_test.cc',
         'extended_attributes_test.cc',
         'garbage_collection_test.cc',
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 72e730e..470eda6 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/debug/trace_event.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/path_service.h"
@@ -50,18 +51,34 @@
 #endif  // defined(__LB_SHELL__FOR_RELEASE__)
 #include "lbshell/src/lb_memory_pages.h"
 #endif  // defined(__LB_SHELL__)
+#if defined(OS_STARBOARD)
+#include "nb/analytics/memory_tracker.h"
+#include "nb/analytics/memory_tracker_impl.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#endif  // defined(OS_STARBOARD)
 
 namespace cobalt {
 namespace browser {
 
 namespace {
 const int kStatUpdatePeriodMs = 1000;
+#if defined(COBALT_BUILD_TYPE_GOLD)
+const int kLiteStatUpdatePeriodMs = 1000;
+#else
+const int kLiteStatUpdatePeriodMs = 16;
+#endif
 
 const char kDefaultURL[] = "https://www.youtube.com/tv";
 
 #if defined(ENABLE_REMOTE_DEBUGGING)
 int GetRemoteDebuggingPort() {
+#if defined(SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT)
+  const int kDefaultRemoteDebuggingPort =
+      SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT;
+#else
   const int kDefaultRemoteDebuggingPort = 9222;
+#endif  // defined(SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT)
   int remote_debugging_port = kDefaultRemoteDebuggingPort;
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   CommandLine* command_line = CommandLine::ForCurrentProcess();
@@ -100,6 +117,19 @@
   }
   return webdriver_port;
 }
+
+std::string GetWebDriverListenIp() {
+  // The default port on which the webdriver server should listen for incoming
+  // connections.
+  std::string webdriver_listen_ip =
+      webdriver::WebDriverModule::kDefaultListenIp;
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kWebDriverListenIp)) {
+    webdriver_listen_ip =
+        command_line->GetSwitchValueASCII(switches::kWebDriverListenIp);
+  }
+  return webdriver_listen_ip;
+}
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 #endif  // ENABLE_WEBDRIVER
 
@@ -164,6 +194,31 @@
 }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
+std::string GetMinLogLevelString() {
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kMinLogLevel)) {
+    return command_line->GetSwitchValueASCII(switches::kMinLogLevel);
+  }
+#endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+  return "info";
+}
+
+int StringToLogLevel(const std::string& log_level) {
+  if (log_level == "info") {
+    return logging::LOG_INFO;
+  } else if (log_level == "warning") {
+    return logging::LOG_WARNING;
+  } else if (log_level == "error") {
+    return logging::LOG_ERROR;
+  } else if (log_level == "fatal") {
+    return logging::LOG_FATAL;
+  } else {
+    NOTREACHED() << "Unrecognized logging level: " << log_level;
+    return logging::LOG_INFO;
+  }
+}
+
 void SetIntegerIfSwitchIsSet(const char* switch_name, int* output) {
   if (CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) {
     int32 out;
@@ -187,6 +242,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) {
@@ -258,7 +315,18 @@
     : message_loop_(MessageLoop::current()),
       quit_closure_(quit_closure),
       start_time_(base::TimeTicks::Now()),
-      stats_update_timer_(true, true) {
+      stats_update_timer_(true, true),
+      lite_stats_update_timer_(true, true) {
+  // Check to see if a timed_trace has been set, indicating that we should
+  // begin a timed trace upon startup.
+  base::TimeDelta trace_duration = GetTimedTraceDuration();
+  if (trace_duration != base::TimeDelta()) {
+    trace_event::TraceToFileForDuration(
+        FilePath(FILE_PATH_LITERAL("timed_trace.json")), trace_duration);
+  }
+
+  TRACE_EVENT0("cobalt::browser", "Application::Application()");
+
   DCHECK(MessageLoop::current());
   DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
 
@@ -267,17 +335,16 @@
 
   RegisterUserLogs();
 
+  // Set the minimum logging level, if specified on the command line.
+  logging::SetMinLogLevel(StringToLogLevel(GetMinLogLevelString()));
+
   stats_update_timer_.Start(
       FROM_HERE, base::TimeDelta::FromMilliseconds(kStatUpdatePeriodMs),
       base::Bind(&Application::UpdatePeriodicStats, base::Unretained(this)));
-
-  // Check to see if a timed_trace has been set, indicating that we should
-  // begin a timed trace upon startup.
-  base::TimeDelta trace_duration = GetTimedTraceDuration();
-  if (trace_duration != base::TimeDelta()) {
-    trace_event::TraceToFileForDuration(
-        FilePath(FILE_PATH_LITERAL("timed_trace.json")), trace_duration);
-  }
+  lite_stats_update_timer_.Start(
+      FROM_HERE, base::TimeDelta::FromMilliseconds(kLiteStatUpdatePeriodMs),
+      base::Bind(&Application::UpdatePeriodicLiteStats,
+                 base::Unretained(this)));
 
   // Get the initial URL.
   GURL initial_url = GetInitialURL();
@@ -374,6 +441,20 @@
     DLOG(INFO) << "Use ShellRawVideoDecoderStub";
     options.media_module_options.use_video_decoder_stub = true;
   }
+  if (command_line->HasSwitch(switches::kMemoryTracker)) {
+#if defined(OS_STARBOARD)
+    using nb::analytics::MemoryTrackerPrintThread;
+    using nb::analytics::MemoryTracker;
+
+    DLOG(INFO) << "Using MemoryTracking";
+    MemoryTracker* memory_tracker = MemoryTracker::Get();
+    memory_tracker->InstallGlobalTrackingHooks();
+    memory_tracker_print_thread_ = CreateDebugPrintThread(memory_tracker);
+#else
+    DLOG(INFO)
+        << "Memory tracker is not enabled on non-starboard builds.";
+#endif
+  }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
   base::optional<math::Size> viewport_size;
@@ -430,14 +511,18 @@
       base::Bind(&Application::OnApplicationEvent, base::Unretained(this));
   event_dispatcher_.AddEventCallback(system_window::ApplicationEvent::TypeId(),
                                      application_event_callback_);
+  deep_link_event_callback_ =
+      base::Bind(&Application::OnDeepLinkEvent, base::Unretained(this));
+  event_dispatcher_.AddEventCallback(base::DeepLinkEvent::TypeId(),
+                                     deep_link_event_callback_);
 
 #if defined(ENABLE_WEBDRIVER)
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   if (command_line->HasSwitch(switches::kEnableWebDriver)) {
-    int webdriver_port = GetWebDriverPort();
     web_driver_module_.reset(new webdriver::WebDriverModule(
-        webdriver_port, base::Bind(&BrowserModule::CreateSessionDriver,
-                                   base::Unretained(browser_module_.get())),
+        GetWebDriverPort(), GetWebDriverListenIp(),
+        base::Bind(&BrowserModule::CreateSessionDriver,
+                   base::Unretained(browser_module_.get())),
         base::Bind(&BrowserModule::RequestScreenshotToBuffer,
                    base::Unretained(browser_module_.get())),
         base::Bind(&BrowserModule::SetProxy,
@@ -472,6 +557,13 @@
 }
 
 Application::~Application() {
+#if defined(OS_STARBOARD)
+  // explicitly reset here because the destruction of the object is complex
+  // and involves a thread join. If this were to hang the app then having
+  // the destruction at this point gives a real file-line number and a place
+  // for the debugger to land.
+  memory_tracker_print_thread_.reset(NULL);
+#endif
   // Unregister event callbacks.
   event_dispatcher_.RemoveEventCallback(account::AccountEvent::TypeId(),
                                         account_event_callback_);
@@ -479,6 +571,8 @@
                                         network_event_callback_);
   event_dispatcher_.RemoveEventCallback(
       system_window::ApplicationEvent::TypeId(), application_event_callback_);
+  event_dispatcher_.RemoveEventCallback(
+      base::DeepLinkEvent::TypeId(), deep_link_event_callback_);
 
   app_status_ = kShutDownAppStatus;
 }
@@ -499,6 +593,7 @@
 }
 
 void Application::OnAccountEvent(const base::Event* event) {
+  TRACE_EVENT0("cobalt::browser", "Application::OnAccountEvent()");
   const account::AccountEvent* account_event =
       base::polymorphic_downcast<const account::AccountEvent*>(event);
   if (account_event->type() == account::AccountEvent::kSignedIn) {
@@ -513,6 +608,7 @@
 }
 
 void Application::OnNetworkEvent(const base::Event* event) {
+  TRACE_EVENT0("cobalt::browser", "Application::OnNetworkEvent()");
   DCHECK(network_event_thread_checker_.CalledOnValidThread());
   const network::NetworkEvent* network_event =
       base::polymorphic_downcast<const network::NetworkEvent*>(event);
@@ -533,6 +629,7 @@
 }
 
 void Application::OnApplicationEvent(const base::Event* event) {
+  TRACE_EVENT0("cobalt::browser", "Application::OnApplicationEvent()");
   DCHECK(application_event_thread_checker_.CalledOnValidThread());
   const system_window::ApplicationEvent* app_event =
       base::polymorphic_downcast<const system_window::ApplicationEvent*>(event);
@@ -563,7 +660,18 @@
   }
 }
 
+void Application::OnDeepLinkEvent(const base::Event* event) {
+  TRACE_EVENT0("cobalt::browser", "Application::OnDeepLinkEvent()");
+  const base::DeepLinkEvent* deep_link_event =
+      base::polymorphic_downcast<const base::DeepLinkEvent*>(event);
+  // TODO: Remove this when terminal application states are properly handled.
+  if (deep_link_event->IsH5vccLink()) {
+    browser_module_->Navigate(GURL(deep_link_event->link()));
+  }
+}
+
 void Application::WebModuleRecreated() {
+  TRACE_EVENT0("cobalt::browser", "Application::WebModuleRecreated()");
 #if defined(ENABLE_WEBDRIVER)
   if (web_driver_module_) {
     web_driver_module_->OnWindowRecreated();
@@ -640,7 +748,12 @@
   }
 }
 
+void Application::UpdatePeriodicLiteStats() {
+  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
+}
+
 void Application::UpdatePeriodicStats() {
+  TRACE_EVENT0("cobalt::browser", "Application::UpdatePeriodicStats()");
 #if defined(__LB_SHELL__)
   bool memory_stats_updated = false;
 #if !defined(__LB_SHELL__FOR_RELEASE__)
@@ -679,8 +792,6 @@
     *c_val_stats_.used_gpu_memory = used_gpu_memory;
   }
 #endif
-
-  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 5abba84..a118a1c 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -33,6 +33,11 @@
 #include "cobalt/debug/debug_web_server.h"
 #endif
 
+#if defined(OS_STARBOARD)
+#include "nb/analytics/memory_tracker.h"
+#include "nb/scoped_ptr.h"
+#endif
+
 namespace cobalt {
 namespace browser {
 
@@ -67,6 +72,9 @@
   // Called to handle an application event.
   void OnApplicationEvent(const base::Event* event);
 
+  // Called to handle a deep link event.
+  void OnDeepLinkEvent(const base::Event* event);
+
   // Called when a navigation occurs in the BrowserModule.
   void WebModuleRecreated();
 
@@ -88,6 +96,7 @@
   base::EventCallback account_event_callback_;
   base::EventCallback network_event_callback_;
   base::EventCallback application_event_callback_;
+  base::EventCallback deep_link_event_callback_;
 
   // Thread checkers to ensure that callbacks for network and application events
   // always occur on the same thread.
@@ -150,6 +159,7 @@
   void UpdateAndMaybeRegisterUserAgent();
 
   void UpdatePeriodicStats();
+  void UpdatePeriodicLiteStats();
 
   static ssize_t available_memory_;
   static int64 lifetime_in_ms_;
@@ -167,6 +177,14 @@
   CValStats c_val_stats_;
 
   base::Timer stats_update_timer_;
+  base::Timer lite_stats_update_timer_;
+
+#if defined(OS_STARBOARD)
+  // This thread (when active) will print out memory statistics of the engine.
+  // It is activated by the command line -memory_tracker.
+  nb::scoped_ptr<nb::analytics::MemoryTrackerPrintThread>
+      memory_tracker_print_thread_;
+#endif
 };
 
 // Factory method for creating an application.  It should be implemented
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp
index 1115bbd..3572dbb 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -75,6 +75,7 @@
         '<(DEPTH)/cobalt/webdriver/webdriver.gyp:webdriver',
         '<(DEPTH)/cobalt/xhr/xhr.gyp:xhr',
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
+        '<(DEPTH)/nb/nb.gyp:nb',
         'browser_bindings.gyp:bindings',
         'screen_shot_writer',
       ],
@@ -82,11 +83,6 @@
         ['enable_about_scheme == 1', {
           'defines': [ 'ENABLE_ABOUT_SCHEME' ],
         }],
-        ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', {
-          'dependencies': [
-            '<(DEPTH)/nb/nb.gyp:nb',
-          ],
-        }],
       ],
     },
 
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 a6df61e..9a730cc 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -16,12 +16,16 @@
 
 #include "cobalt/browser/browser_module.h"
 
+#include <vector>
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/base/tokens.h"
@@ -33,6 +37,7 @@
 #include "cobalt/dom/keycode.h"
 #include "cobalt/h5vcc/h5vcc.h"
 #include "cobalt/input/input_device_manager_fuzzer.h"
+#include "nb/memory_scope.h"
 
 namespace cobalt {
 namespace browser {
@@ -54,6 +59,15 @@
     "activated or not.  While activated, input will constantly and randomly be "
     "generated and passed directly into the main web module.";
 
+const char kSetMediaConfigCommand[] = "set_media_config";
+const char kSetMediaConfigCommandShortHelp[] =
+    "Sets media module configuration.";
+const char kSetMediaConfigCommandLongHelp[] =
+    "This can be called in the form of set_media_config('name=value'), where "
+    "name is a string and value is an int.  Refer to the implementation of "
+    "MediaModule::SetConfiguration() on individual platform for settings "
+    "supported on the particular platform.";
+
 #if defined(ENABLE_SCREENSHOT)
 // Command to take a screenshot.
 const char kScreenshotCommand[] = "screenshot";
@@ -122,10 +136,6 @@
           renderer_module_.pipeline()->GetResourceProvider())),
       array_buffer_cache_(new dom::ArrayBuffer::Cache(3 * 1024 * 1024)),
 #endif  // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
-      media_module_(media::MediaModule::Create(
-          system_window, renderer_module_.render_target()->GetSize(),
-          renderer_module_.pipeline()->GetResourceProvider(),
-          options.media_module_options)),
       network_module_(&storage_manager_, system_window->event_dispatcher(),
                       options.network_module_options),
       render_tree_combiner_(&renderer_module_,
@@ -141,6 +151,10 @@
           kFuzzerToggleCommand,
           base::Bind(&BrowserModule::OnFuzzerToggle, base::Unretained(this)),
           kFuzzerToggleCommandShortHelp, kFuzzerToggleCommandLongHelp)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(set_media_config_command_handler_(
+          kSetMediaConfigCommand,
+          base::Bind(&BrowserModule::OnSetMediaConfig, base::Unretained(this)),
+          kSetMediaConfigCommandShortHelp, kSetMediaConfigCommandLongHelp)),
 #if defined(ENABLE_SCREENSHOT)
       ALLOW_THIS_IN_INITIALIZER_LIST(screenshot_command_handler_(
           kScreenshotCommand,
@@ -154,6 +168,25 @@
       has_resumed_(true, false),
       will_quit_(false),
       suspended_(false) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()");
+  // All allocations for media will be tracked by "Media" memory scope.
+  {
+    TRACK_MEMORY_SCOPE("Media");
+    math::Size output_size = renderer_module_.render_target()->GetSize();
+    if (system_window->GetVideoPixelRatio() != 1.f) {
+      output_size.set_width(
+          static_cast<int>(static_cast<float>(output_size.width()) *
+                           system_window->GetVideoPixelRatio()));
+      output_size.set_height(
+          static_cast<int>(static_cast<float>(output_size.height()) *
+                           system_window->GetVideoPixelRatio()));
+    }
+    media_module_ = (media::MediaModule::Create(
+        system_window, output_size,
+        renderer_module_.pipeline()->GetResourceProvider(),
+        options.media_module_options));
+  }
+
   // Setup our main web module to have the H5VCC API injected into it.
   DCHECK(!ContainsKey(web_module_options_.injected_window_attributes, "h5vcc"));
   h5vcc::H5vcc::Settings h5vcc_settings;
@@ -202,6 +235,7 @@
 }
 
 void BrowserModule::Navigate(const GURL& url) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Navigate()");
   web_module_loaded_.Reset();
 
   // Always post this as a task in case this is being called from the WebModule.
@@ -210,6 +244,7 @@
 }
 
 void BrowserModule::Reload() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Reload()");
   DCHECK_EQ(MessageLoop::current(), self_message_loop_);
   DCHECK(web_module_);
   web_module_->ExecuteJavascript(
@@ -218,6 +253,7 @@
 }
 
 void BrowserModule::NavigateInternal(const GURL& url) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::NavigateInternal()");
   DCHECK_EQ(MessageLoop::current(), self_message_loop_);
 
   // First try the registered handlers (e.g. for h5vcc://). If one of these
@@ -241,6 +277,15 @@
                        renderer_module_.pipeline()->GetResourceProvider(),
                        kLayoutMaxRefreshFrequencyInHz));
 
+#if defined(OS_STARBOARD)
+#if SB_HAS(1_CORE)
+  // Wait until the splash screen is ready before loading the main web module.
+  // This prevents starvation of the splash screen module and decoding of the
+  // splash screen image(s).
+  splash_screen_->WaitUntilReady();
+#endif
+#endif
+
   // Create new WebModule.
 #if !defined(COBALT_FORCE_CSP)
   web_module_options_.csp_insecure_allowed_token =
@@ -256,6 +301,14 @@
       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.microphone_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(
@@ -272,6 +325,7 @@
 }
 
 void BrowserModule::OnLoad() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::OnLoad()");
   // Repost to our own message loop if necessary. This also prevents
   // asynchonrous access to this object by |web_module_| during destruction.
   if (MessageLoop::current() != self_message_loop_) {
@@ -284,11 +338,10 @@
   web_module_loaded_.Signal();
 }
 
-#if defined(ENABLE_WEBDRIVER)
 bool BrowserModule::WaitForLoad(const base::TimeDelta& timeout) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::WaitForLoad()");
   return web_module_loaded_.TimedWait(timeout);
 }
-#endif
 
 #if defined(ENABLE_SCREENSHOT)
 void BrowserModule::RequestScreenshotToFile(const FilePath& path,
@@ -374,6 +427,30 @@
   }
 }
 
+void BrowserModule::OnSetMediaConfig(const std::string& config) {
+  if (MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->PostTask(
+        FROM_HERE,
+        base::Bind(&BrowserModule::OnSetMediaConfig, weak_this_, config));
+    return;
+  }
+
+  std::vector<std::string> tokens;
+  base::SplitString(config, '=', &tokens);
+
+  int value;
+  if (tokens.size() != 2 || !base::StringToInt(tokens[1], &value)) {
+    LOG(WARNING) << "Media configuration '" << config << "' is not in the"
+                 << " form of '<string name>=<int value>'.";
+    return;
+  }
+  if (media_module_->SetConfiguration(tokens[0], value)) {
+    LOG(INFO) << "Successfully setting " << tokens[0] << " to " << value;
+  } else {
+    LOG(WARNING) << "Failed to set " << tokens[0] << " to " << value;
+  }
+}
+
 void BrowserModule::QueueOnDebugConsoleRenderTreeProduced(
     const browser::WebModule::LayoutResults& layout_results) {
   TRACE_EVENT0("cobalt::browser",
@@ -392,6 +469,11 @@
                "BrowserModule::OnDebugConsoleRenderTreeProduced()");
   DCHECK_EQ(MessageLoop::current(), self_message_loop_);
 
+  if (debug_console_->GetMode() == debug::DebugHub::kDebugConsoleOff) {
+    render_tree_combiner_.UpdateDebugConsoleRenderTree(base::nullopt);
+    return;
+  }
+
   render_tree_combiner_.UpdateDebugConsoleRenderTree(renderer::Submission(
       layout_results.render_tree, layout_results.layout_time));
 }
@@ -435,6 +517,7 @@
 }
 
 void BrowserModule::OnError(const GURL& url, const std::string& error) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::OnError()");
   LOG(ERROR) << error;
   std::string url_string = "h5vcc://network-failure";
 
@@ -445,6 +528,7 @@
 }
 
 bool BrowserModule::FilterKeyEvent(const dom::KeyboardEvent::Data& event) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::FilterKeyEvent()");
   // Check for hotkeys first. If it is a hotkey, no more processing is needed.
   if (!FilterKeyEventForHotkeys(event)) {
     return false;
@@ -510,7 +594,10 @@
   return false;
 }
 
-void BrowserModule::DestroySplashScreen() { splash_screen_.reset(NULL); }
+void BrowserModule::DestroySplashScreen() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroySplashScreen()");
+  splash_screen_.reset(NULL);
+}
 
 #if defined(ENABLE_WEBDRIVER)
 scoped_ptr<webdriver::SessionDriver> BrowserModule::CreateSessionDriver(
@@ -582,10 +669,12 @@
 }
 
 void BrowserModule::Suspend() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Suspend()");
   DCHECK_EQ(MessageLoop::current(), self_message_loop_);
   DCHECK(!suspended_);
 
-// First release the resource provider used by all of our web modules.
+// First suspend all our web modules which implies that they will release their
+// resource provider and all resources created through it.
 #if defined(ENABLE_DEBUG_CONSOLE)
   if (debug_console_) {
     debug_console_->Suspend();
@@ -623,6 +712,7 @@
 }
 
 void BrowserModule::Resume() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Resume()");
   DCHECK_EQ(MessageLoop::current(), self_message_loop_);
   DCHECK(suspended_);
 
@@ -653,6 +743,8 @@
 
 #if defined(OS_STARBOARD)
 void BrowserModule::OnRendererSubmissionRasterized() {
+  TRACE_EVENT0("cobalt::browser",
+               "BrowserModule::OnRendererSubmissionRasterized()");
   if (!is_rendered_) {
     // Hide the system splash screen when the first render has completed.
     is_rendered_ = true;
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 669738a..82322b3 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -17,8 +17,8 @@
 #ifndef COBALT_BROWSER_BROWSER_MODULE_H_
 #define COBALT_BROWSER_BROWSER_MODULE_H_
 
-#include <list>
 #include <string>
+#include <vector>
 
 #include "base/memory/scoped_ptr.h"
 #include "base/synchronization/lock.h"
@@ -71,7 +71,7 @@
 
   // Type for a collection of URL handler callbacks that can potentially handle
   // a URL before using it to initialize a new WebModule.
-  typedef std::list<URLHandler::URLHandlerCallback> URLHandlerCollection;
+  typedef std::vector<URLHandler::URLHandlerCallback> URLHandlerCollection;
 
   BrowserModule(const GURL& url, system_window::SystemWindow* system_window,
                 account::AccountManager* account_manager,
@@ -127,12 +127,10 @@
   // Called when the WebModule's Window.onload event is fired.
   void OnLoad();
 
-#if defined(ENABLE_WEBDRIVER)
   // Wait for the onload event to be fired with the specified timeout. If the
   // webmodule is not currently loading the document, this will return
   // immediately.
   bool WaitForLoad(const base::TimeDelta& timeout);
-#endif
 
   // Glue function to deal with the production of the main render tree,
   // and will manage handing it off to the renderer.
@@ -182,6 +180,10 @@
   // Toggles the input fuzzer on/off.  Ignores the parameter.
   void OnFuzzerToggle(const std::string&);
 
+  // Use the config in the form of '<string name>=<int value>' to call
+  // MediaModule::SetConfiguration().
+  void OnSetMediaConfig(const std::string& config);
+
   // Glue function to deal with the production of the debug console render tree,
   // and will manage handing it off to the renderer.
   void QueueOnDebugConsoleRenderTreeProduced(
@@ -299,9 +301,12 @@
 
   TraceManager trace_manager;
 
-  // Command handler object for toggline the input fuzzer on/off.
+  // Command handler object for toggling the input fuzzer on/off.
   base::ConsoleCommandManager::CommandHandler fuzzer_toggle_command_handler_;
 
+  // Command handler object for setting media module config.
+  base::ConsoleCommandManager::CommandHandler set_media_config_command_handler_;
+
 #if defined(ENABLE_SCREENSHOT)
   // Command handler object for screenshot command from the debug console.
   base::ConsoleCommandManager::CommandHandler screenshot_command_handler_;
diff --git a/src/cobalt/browser/cobalt.gyp b/src/cobalt/browser/cobalt.gyp
index 4f73099..f5bcfe9 100644
--- a/src/cobalt/browser/cobalt.gyp
+++ b/src/cobalt/browser/cobalt.gyp
@@ -94,4 +94,17 @@
     },
 
   ],
+  'conditions': [
+    ['final_executable_type == "shared_library"', {
+      'targets': [
+        {
+          'target_name': 'cobalt_bin',
+          'type': 'executable',
+          'dependencies': [
+            'cobalt',
+          ],
+        },
+      ],
+    }],
+  ],
 }
diff --git a/src/cobalt/browser/debug_console/debug_console.css b/src/cobalt/browser/debug_console/debug_console.css
index afac884..3df005f 100644
--- a/src/cobalt/browser/debug_console/debug_console.css
+++ b/src/cobalt/browser/debug_console/debug_console.css
@@ -10,6 +10,7 @@
   right: 0;
   background-color: rgba(128, 128, 128, 0.6);
   color: #FFFFFF;
+  display: none;
 }
 
 #hud {
@@ -35,6 +36,7 @@
   background-color: rgba(128, 128, 128, 0.6);
   color: #FFFFFF;
   overflow: hidden;
+  display: none;
 }
 
 #messageContainerFrame {
diff --git a/src/cobalt/browser/debug_console/debug_console.js b/src/cobalt/browser/debug_console/debug_console.js
index 58282d5..7c2f8a5 100644
--- a/src/cobalt/browser/debug_console/debug_console.js
+++ b/src/cobalt/browser/debug_console/debug_console.js
@@ -258,6 +258,7 @@
   createCommandInput();
   createMessageLog();
   createDebuggerClient();
+  showHud(false);
   showConsole(false);
   createConsoleValues();
   initDebugCommands();
diff --git a/src/cobalt/browser/render_tree_combiner.cc b/src/cobalt/browser/render_tree_combiner.cc
index 364158b..6a076e1 100644
--- a/src/cobalt/browser/render_tree_combiner.cc
+++ b/src/cobalt/browser/render_tree_combiner.cc
@@ -45,7 +45,7 @@
 }
 
 void RenderTreeCombiner::UpdateDebugConsoleRenderTree(
-    const renderer::Submission& render_tree_submission) {
+    const base::optional<renderer::Submission>& render_tree_submission) {
   debug_console_render_tree_ = render_tree_submission;
   SubmitToRenderer();
 }
@@ -105,7 +105,7 @@
 }
 
 void RenderTreeCombiner::UpdateDebugConsoleRenderTree(
-    const renderer::Submission& render_tree_submission) {
+    const base::optional<renderer::Submission>& render_tree_submission) {
   UNREFERENCED_PARAMETER(render_tree_submission);
 }
 #endif  // ENABLE_DEBUG_CONSOLE
diff --git a/src/cobalt/browser/render_tree_combiner.h b/src/cobalt/browser/render_tree_combiner.h
index 44f38d6..75bca9b 100644
--- a/src/cobalt/browser/render_tree_combiner.h
+++ b/src/cobalt/browser/render_tree_combiner.h
@@ -40,7 +40,7 @@
 
   // Update the debug console render tree.
   void UpdateDebugConsoleRenderTree(
-      const renderer::Submission& render_tree_submission);
+      const base::optional<renderer::Submission>& render_tree_submission);
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   bool render_debug_console() const { return render_debug_console_; }
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc
index a2a3d97..0ccc88c 100644
--- a/src/cobalt/browser/splash_screen.cc
+++ b/src/cobalt/browser/splash_screen.cc
@@ -30,14 +30,17 @@
         render_tree_produced_callback,
     network::NetworkModule* network_module, const math::Size& window_dimensions,
     render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
-    const SplashScreen::Options& options) {
+    const SplashScreen::Options& options)
+    : render_tree_produced_callback_(render_tree_produced_callback)
+    , is_ready_(true, false) {
   WebModule::Options web_module_options;
   web_module_options.name = "SplashScreenWebModule";
 
   web_module_.reset(new WebModule(
-      options.url, render_tree_produced_callback,
+      options.url,
+      base::Bind(&SplashScreen::OnRenderTreeProduced, base::Unretained(this)),
       base::Bind(&SplashScreen::OnError, base::Unretained(this)),
-      base::Closure(), /* window_close_callback */
+      base::Bind(&SplashScreen::OnWindowClosed, base::Unretained(this)),
       &stub_media_module_, network_module, window_dimensions, resource_provider,
       layout_refresh_rate, web_module_options));
 }
@@ -47,5 +50,19 @@
   web_module_->Resume(resource_provider);
 }
 
+void SplashScreen::WaitUntilReady() {
+  is_ready_.Wait();
+}
+
+void SplashScreen::OnRenderTreeProduced(
+    const browser::WebModule::LayoutResults& layout_results) {
+  is_ready_.Signal();
+  render_tree_produced_callback_.Run(layout_results);
+}
+
+void SplashScreen::OnWindowClosed() {
+  is_ready_.Signal();
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index a105fe4..7888fa8 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -21,6 +21,7 @@
 
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/media/media_module_stub.h"
 #include "googleurl/src/gurl.h"
@@ -48,13 +49,28 @@
   void Suspend();
   void Resume(render_tree::ResourceProvider* resource_provider);
 
+  // Block the caller until the splash screen is ready to be rendered.
+  void WaitUntilReady();
+
  private:
+  void OnRenderTreeProduced(
+      const browser::WebModule::LayoutResults& layout_results);
+
   void OnError(const GURL& /* url */, const std::string& error) {
+    is_ready_.Signal();
     LOG(ERROR) << error;
   }
 
+  void OnWindowClosed();
+
   media::MediaModuleStub stub_media_module_;
   scoped_ptr<WebModule> web_module_;
+
+  WebModule::OnRenderTreeProducedCallback render_tree_produced_callback_;
+
+  // Signalled once the splash screen has produced its first render tree or
+  // an error occurred.
+  base::WaitableEvent is_ready_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/stack_size_constants.h b/src/cobalt/browser/stack_size_constants.h
new file mode 100644
index 0000000..c4e62ea
--- /dev/null
+++ b/src/cobalt/browser/stack_size_constants.h
@@ -0,0 +1,38 @@
+/*
+ * 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_BROWSER_STACK_SIZE_CONSTANTS_H_
+#define COBALT_BROWSER_STACK_SIZE_CONSTANTS_H_
+
+#include "cobalt/base/address_sanitizer.h"
+
+namespace cobalt {
+namespace browser {
+#if defined(COBALT_BUILD_TYPE_DEBUG)
+  // Non-optimized builds require a bigger stack size.
+  const size_t kBaseStackSize = 2 * 1024 * 1024;
+#elif defined(COBALT_BUILD_TYPE_DEVEL)
+  // Devel builds require a slightly bigger stack size.
+  const size_t kBaseStackSize = 448 * 1024;
+#else
+  const size_t kBaseStackSize = 384 * 1024;
+#endif
+  const size_t kWebModuleStackSize =
+      kBaseStackSize + base::kAsanAdditionalStackSize;
+}  // namespace browser
+}  // namespace cobalt
+
+#endif  // COBALT_BROWSER_STACK_SIZE_CONSTANTS_H_
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index a402253..91e0e6d 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";
 
@@ -52,6 +56,9 @@
 // taken from an external input device (like a controller).
 const char kInputFuzzer[] = "input_fuzzer";
 
+// Set the minimum logging level: info|warning|error|fatal.
+const char kMinLogLevel[] = "min_log_level";
+
 // Use the NullAudioStreamer. Audio will be decoded but will not play back. No
 // audio output library will be initialized or used.
 const char kNullAudioStreamer[] = "null_audio_streamer";
@@ -94,6 +101,13 @@
 // Port that the WebDriver server should be listening on.
 const char kWebDriverPort[] = "webdriver_port";
 
+// IP that the WebDriver server should be listening on.
+// (INADDR_ANY if unspecified).
+const char kWebDriverListenIp[] = "webdriver_listen_ip";
+
+// Enables memory tracking by installing the memory tracker on startup.
+const char kMemoryTracker[] = "memory_tracker";
+
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 // Determines the capacity of the image cache which manages image surfaces
@@ -126,6 +140,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 7d463d0..7ef6a28 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -29,8 +29,10 @@
 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[];
 extern const char kNullAudioStreamer[];
 extern const char kNullSavegame[];
 extern const char kPartialLayout[];
@@ -42,6 +44,8 @@
 extern const char kVideoContainerSizeOverride[];
 extern const char kVideoDecoderStub[];
 extern const char kWebDriverPort[];
+extern const char kWebDriverListenIp[];
+extern const char kMemoryTracker[];
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 extern const char kImageCacheSizeInBytes[];
@@ -49,6 +53,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/testdata/mtm-demo/README.txt b/src/cobalt/browser/testdata/mtm-demo/README.txt
new file mode 100644
index 0000000..5368357
--- /dev/null
+++ b/src/cobalt/browser/testdata/mtm-demo/README.txt
@@ -0,0 +1,37 @@
+These test whether the presence of the CSS MTM filter affect the path taken in
+rendering the video replaced box. <normal.html> has a video tag without the
+filter, while <mtm.html> has a video with the filter applied to it.
+
+To test locally, all 3 of mtm.html, normal.html and progressive.mp4 have to be
+hosted on a server that supports HTTP RANGE header, which is needed for video to
+be loaded correctly. One such server is the extension to the Python
+SimpleHTTPServer at https://github.com/danvk/RangeHTTPServer (run from this
+directory, "cobalt/src/cobalt/browser/testdata/mtm-demo/"):
+
+  $ sudo apt-get install python-pip
+  $ pip install --user rangehttpserver
+  $ python -m RangeHTTPServer
+
+Test video without the MTM filter (should render normally, run from cobalt/src
+directory):
+
+  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/normal.html
+
+Test the video with the MTM filter (should NOT render in its bounding box in the
+document, and in the presence of the correct rasterizer, should be rendered onto
+its own texture off the main UI layout):
+
+  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/mtm.html
+
+There is also the option of using the google cloud utils and the cloud storage
+to publish these files, as is done with the other demos. Care should be taken
+that nothing confidential gets uploaded with these files. "public-read" is
+needed in the ACL because linking and video srcing does not work without it.
+
+A bucket specifically for this demo already exists at "gs://yt-cobalt-mtm-test",
+but uploading to it often might be problematic since buckets are not verison-
+controlled:
+
+  $ gsutil cp -a public-read cobalt/browser/testdata/mtm-demo/normal.html cobalt/browser/testdata/mtm-demo/mtm.html cobalt/browser/testdata/mtm-demo/progressive.mp4 gs://yt-cobalt-mtm-test/
+  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/mtm.html
+  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/normal.html
diff --git a/src/cobalt/browser/testdata/mtm-demo/mtm.html b/src/cobalt/browser/testdata/mtm-demo/mtm.html
new file mode 100644
index 0000000..6877674
--- /dev/null
+++ b/src/cobalt/browser/testdata/mtm-demo/mtm.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>mtm Demo</title>
+  <style>
+    body {
+      background-color: rgb(255, 255, 255);
+      color: #0047ab;
+      font-size: 100px;
+    }
+    .vid {
+      margin: 100px;
+      border: 10px solid blue;
+      width: 960px;
+      height: 540px;
+      filter: -cobalt-mtm(url(projection.msh), 100deg 60deg,
+                              matrix3d(1, 0, 0, 0,
+                                       0, 1, 0, 0,
+                                       0, 0, 1, 0,
+                                       0, 0, 0, 1));
+    }
+  </style>
+</head>
+<body>
+  <div>Mtm Demo!!!</div>
+  <video autoplay loop id="v" class="vid" src="progressive.mp4"></video>
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/mtm-demo/normal.html b/src/cobalt/browser/testdata/mtm-demo/normal.html
new file mode 100644
index 0000000..3d516f6
--- /dev/null
+++ b/src/cobalt/browser/testdata/mtm-demo/normal.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Normal Demo</title>
+  <style>
+    body {
+      background-color: rgb(255, 255, 255);
+      color: #0047ab;
+      font-size: 100px;
+    }
+    .vid {
+      margin: 100px;
+      border: 10px solid blue;
+      width: 960px;
+      height: 540px;
+    }
+  </style>
+</head>
+<body>
+  <div>Normal Demo</div>
+  <video autoplay loop id="v" class="vid" src="progressive.mp4"></video>
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/mtm-demo/progressive.mp4 b/src/cobalt/browser/testdata/mtm-demo/progressive.mp4
new file mode 100644
index 0000000..2686a9b
--- /dev/null
+++ b/src/cobalt/browser/testdata/mtm-demo/progressive.mp4
Binary files differ
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 3c26c18..defb344 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -23,16 +23,22 @@
 #include "base/message_loop_proxy.h"
 #include "base/optional.h"
 #include "base/stringprintf.h"
-#include "cobalt/base/address_sanitizer.h"
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/poller.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/browser/stack_size_constants.h"
 #include "cobalt/browser/switches.h"
 #include "cobalt/browser/web_module_stat_tracker.h"
+#include "cobalt/css_parser/parser.h"
 #include "cobalt/debug/debug_server_module.h"
+#include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate_factory.h"
+#include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/storage.h"
+#include "cobalt/dom/url.h"
+#include "cobalt/dom_parser/parser.h"
 #include "cobalt/h5vcc/h5vcc.h"
+#include "cobalt/script/javascript_engine.h"
 #include "cobalt/storage/storage_manager.h"
 
 namespace cobalt {
@@ -40,11 +46,11 @@
 
 namespace {
 
-#if defined(COBALT_RELEASE)
+#if defined(COBALT_BUILD_TYPE_GOLD)
 const int kPollerPeriodMs = 2000;
-#else   // #if defined(COBALT_RELEASE)
+#else   // #if defined(COBALT_BUILD_TYPE_GOLD)
 const int kPollerPeriodMs = 20;
-#endif  // #if defined(COBALT_RELEASE)
+#endif  // #if defined(COBALT_BUILD_TYPE_GOLD)
 
 // The maximum number of element depth in the DOM tree. Elements at a level
 // deeper than this could be discarded, and will not be rendered.
@@ -112,7 +118,8 @@
 #endif  // ENABLE_DEBUG_CONSOLE
 
   // Called to inject a keyboard event into the web module.
-  void InjectKeyboardEvent(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|.
@@ -140,7 +147,11 @@
   void CreateDebugServerIfNull();
 #endif  // ENABLE_DEBUG_CONSOLE
 
-  void Suspend();
+  // Suspension of the WebModule is a two-part process since a message loop
+  // gap is needed in order to give a chance to handle loader callbacks
+  // that were initiated from a loader thread.
+  void SuspendLoaders();
+  void FinishSuspend();
   void Resume(render_tree::ResourceProvider* resource_provider);
 
  private:
@@ -238,6 +249,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_;
 
@@ -310,8 +324,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(
@@ -389,8 +406,9 @@
 
   environment_settings_.reset(new dom::DOMSettings(
       kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module, window_,
-      media_source_registry_.get(), 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());
@@ -457,6 +475,7 @@
   window_weak_.reset();
   window_ = NULL;
   media_source_registry_.reset();
+  blob_registry_.reset();
   script_runner_.reset();
   execution_state_.reset();
   global_environment_ = NULL;
@@ -472,6 +491,7 @@
 }
 
 void WebModule::Impl::InjectKeyboardEvent(
+    scoped_refptr<dom::Element> element,
     const dom::KeyboardEvent::Data& event) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(is_running_);
@@ -486,7 +506,11 @@
   // injected.
   web_module_stat_tracker_->OnInjectEvent(keyboard_event);
 
-  window_->InjectEvent(keyboard_event);
+  if (element) {
+    element->DispatchEvent(keyboard_event);
+  } else {
+    window_->InjectEvent(keyboard_event);
+  }
 }
 
 void WebModule::Impl::ExecuteJavascript(
@@ -563,6 +587,7 @@
   window_driver_out->reset(new webdriver::WindowDriver(
       window_id, window_weak_,
       base::Bind(&WebModule::Impl::global_environment, base::Unretained(this)),
+      base::Bind(&WebModule::Impl::InjectKeyboardEvent, base::Unretained(this)),
       base::MessageLoopProxy::current()));
 }
 #endif  // defined(ENABLE_WEBDRIVER)
@@ -597,21 +622,20 @@
   }
 }
 
-void WebModule::Impl::Suspend() {
-  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Suspend()");
-  DCHECK(resource_provider_);
+void WebModule::Impl::SuspendLoaders() {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::SuspendLoaders()");
 
   // Stop the generation of render trees.
   layout_manager_->Suspend();
 
-#if defined(ENABLE_DEBUG_CONSOLE)
-  // The debug overlay may be holding onto a render tree, clear that out.
-  debug_overlay_->ClearInput();
-#endif
-
   // Clear out the loader factory's resource provider, possibly aborting any
   // in-progress loads.
   loader_factory_->Suspend();
+}
+
+void WebModule::Impl::FinishSuspend() {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::FinishSuspend()");
+  DCHECK(resource_provider_);
 
   // Ensure the document is not holding onto any more image cached resources so
   // that they are eligible to be purged.
@@ -624,6 +648,11 @@
   image_cache_->Purge();
   remote_typeface_cache_->Purge();
 
+#if defined(ENABLE_DEBUG_CONSOLE)
+  // The debug overlay may be holding onto a render tree, clear that out.
+  debug_overlay_->ClearInput();
+#endif
+
   // Finally mark that we have no resource provider.
   resource_provider_ = NULL;
 }
@@ -676,18 +705,11 @@
       window_close_callback, media_module, network_module, window_dimensions,
       resource_provider, kDOMMaxElementDepth, layout_refresh_rate, options);
 
-#if defined(COBALT_BUILD_TYPE_DEBUG)
-  // Non-optimized builds require a bigger stack size.
-  const size_t kBaseStackSize = 2 * 1024 * 1024;
-#else
-  const size_t kBaseStackSize = 256 * 1024;
-#endif
-
   // Start the dedicated thread and create the internal implementation
   // object on that thread.
-  size_t stack_size = kBaseStackSize + base::kAsanAdditionalStackSize;
   thread_.StartWithOptions(
-      base::Thread::Options(MessageLoop::TYPE_DEFAULT, stack_size));
+      base::Thread::Options(MessageLoop::TYPE_DEFAULT,
+        cobalt::browser::kWebModuleStackSize));
   DCHECK(message_loop());
 
   message_loop()->PostTask(
@@ -759,14 +781,23 @@
 }
 
 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));
+}
+
+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());
   DCHECK(impl_);
+  DCHECK_EQ(MessageLoop::current(), message_loop());
 
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&WebModule::Impl::InjectKeyboardEvent,
-                                      base::Unretained(impl_.get()), event));
+  impl_->InjectKeyboardEvent(element, event);
 }
 
 std::string WebModule::ExecuteJavascript(
@@ -854,16 +885,43 @@
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
 void WebModule::Suspend() {
-  message_loop()->PostTask(
-      FROM_HERE,
-      base::Bind(&WebModule::Impl::Suspend, base::Unretained(impl_.get())));
+  TRACE_EVENT0("cobalt::browser", "WebModule::Suspend()");
 
-  base::WaitableEvent resource_provider_released(true, false);
-  message_loop()->PostTask(
-      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
-                            base::Unretained(&resource_provider_released)));
+  // Suspend() must only be called by a thread external from the WebModule
+  // thread.
+  DCHECK_NE(MessageLoop::current(), message_loop());
 
-  resource_provider_released.Wait();
+  base::WaitableEvent task_finished(false /* automatic reset */,
+                                    false /* initially unsignaled */);
+
+  // Suspension of the WebModule is orchestrated here in two phases.
+  // 1) Send a signal to suspend WebModule loader activity and cancel any
+  //    in-progress loads.  Since loading may occur from any thread, this may
+  //    result in cancel/completion callbacks being posted to message_loop().
+  message_loop()->PostTask(FROM_HERE,
+                           base::Bind(&WebModule::Impl::SuspendLoaders,
+                                      base::Unretained(impl_.get())));
+
+  // Wait for the suspension task to complete before proceeding.
+  message_loop()->PostTask(FROM_HERE,
+                           base::Bind(&base::WaitableEvent::Signal,
+                                      base::Unretained(&task_finished)));
+  task_finished.Wait();
+
+  // 2) Now append to the task queue a task to complete the suspension process.
+  //    Between 1 and 2, tasks may have been registered to handle resource load
+  //    completion events, and so this FinishSuspend task will be executed after
+  //    the load completions are all resolved.
+  message_loop()->PostTask(FROM_HERE,
+                           base::Bind(&WebModule::Impl::FinishSuspend,
+                                      base::Unretained(impl_.get())));
+
+  // Wait for suspension to fully complete on the WebModule thread before
+  // continuing.
+  message_loop()->PostTask(FROM_HERE,
+                           base::Bind(&base::WaitableEvent::Signal,
+                                      base::Unretained(&task_finished)));
+  task_finished.Wait();
 }
 
 void WebModule::Resume(render_tree::ResourceProvider* resource_provider) {
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index 8907911..34f38b2 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -27,6 +27,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/base/address_sanitizer.h"
 #include "cobalt/base/console_commands.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/css_parser/parser.h"
@@ -34,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"
@@ -159,6 +161,13 @@
   ~WebModule();
 
   // Call this to inject a keyboard event into the web module.
+  // 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);
+
+  // Call this to inject a keyboard event into the web module.
   void InjectKeyboardEvent(const dom::KeyboardEvent::Data& event);
 
   // Call this to execute Javascript code in this web module.  The calling
diff --git a/src/cobalt/browser/web_module_stat_tracker.cc b/src/cobalt/browser/web_module_stat_tracker.cc
index 10bdf94..aa4d634 100644
--- a/src/cobalt/browser/web_module_stat_tracker.cc
+++ b/src/cobalt/browser/web_module_stat_tracker.cc
@@ -28,7 +28,10 @@
     : dom_stat_tracker_(new dom::DomStatTracker(name)),
       layout_stat_tracker_(new layout::LayoutStatTracker(name)),
       should_track_event_stats_(should_track_event_stats),
-      current_event_type_(kEventTypeInvalid) {
+      current_event_type_(kEventTypeInvalid),
+      name_(name),
+      event_is_processing_(StringPrintf("Event.%s.IsProcessing", name.c_str()),
+          0, "Nonzero when an event is being processed.") {
   if (should_track_event_stats_) {
     event_stats_.reserve(kNumEventTypes);
     for (int i = 0; i < kNumEventTypes; ++i) {
@@ -56,6 +59,8 @@
 
   EndCurrentEvent(false);
 
+  event_is_processing_ = 1;
+
   if (event->type() == base::Tokens::keydown()) {
     current_event_type_ = kEventTypeKeyDown;
   } else if (event->type() == base::Tokens::keyup()) {
@@ -152,6 +157,8 @@
     return;
   }
 
+  event_is_processing_ = 0;
+
   stop_watch_durations_[kStopWatchTypeEvent] = base::TimeDelta();
   stop_watches_[kStopWatchTypeEvent].Stop();
   dom_stat_tracker_->DisableStopWatches();
diff --git a/src/cobalt/browser/web_module_stat_tracker.h b/src/cobalt/browser/web_module_stat_tracker.h
index 8e9dbd3..5b317e7 100644
--- a/src/cobalt/browser/web_module_stat_tracker.h
+++ b/src/cobalt/browser/web_module_stat_tracker.h
@@ -115,6 +115,10 @@
   // Stop watch-related
   std::vector<base::StopWatch> stop_watches_;
   std::vector<base::TimeDelta> stop_watch_durations_;
+
+  std::string name_;
+
+  base::CVal<int, base::CValPublic> event_is_processing_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index 8352629..52a802c 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -59,11 +59,13 @@
         '<(DEPTH)/cobalt/samples/samples.gyp:*',
         '<(DEPTH)/cobalt/script/script.gyp:*',
         '<(DEPTH)/cobalt/script/engine.gyp:all_engines',
+        '<(DEPTH)/cobalt/speech/sandbox/sandbox.gyp:*',
         '<(DEPTH)/cobalt/speech/speech.gyp:*',
         '<(DEPTH)/cobalt/storage/storage.gyp:*',
         '<(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 159bca5..e0a264e 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-15147
\ No newline at end of file
+16000
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index 7170bcb..53cc9b4 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -67,6 +67,13 @@
     # platform.
     'default_renderer_options_dependency%': '<(DEPTH)/cobalt/renderer/default_options_starboard.gyp:default_options',
 
+    # Allow throttling of the frame rate. This is expressed in terms of
+    # milliseconds and can be a floating point number. Keep in mind that
+    # swapping frames may take some additional processing time, so it may be
+    # better to specify a lower delay. For example, '33' instead of '33.33'
+    # for 30 Hz refresh.
+    'cobalt_minimum_frame_time_in_milliseconds%': '0',
+
     # The variables allow changing the target type on platforms where the
     # native code may require an additional packaging step (ex. Android).
     'gtest_target_type%': 'executable',
@@ -95,9 +102,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!):
@@ -128,6 +137,7 @@
     #   - scratch_surface_cache_size_in_bytes
     #   - surface_cache_size_in_bytes
     #   - image_cache_size_in_bytes
+    #   - skia_glyph_atlas_width * skia_glyph_atlas_height
     #
     # The other caches affect CPU memory usage.
 
@@ -159,6 +169,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
@@ -168,6 +184,15 @@
     #     image_cache_capacity_multiplier_when_playing_video.
     'image_cache_capacity_multiplier_when_playing_video%': '1.0f',
 
+    # Determines the size in pixels of the glyph atlas where rendered glyphs are
+    # cached. The resulting memory usage is 2 bytes of GPU memory per pixel.
+    # When a value is used that is too small, thrashing may occur that will
+    # result in visible stutter. Such thrashing is more likely to occur when CJK
+    # language glyphs are rendered and when the size of the glyphs in pixels is
+    # larger, such as for higher resolution displays.
+    'skia_glyph_atlas_width%': '2048',
+    'skia_glyph_atlas_height%': '2048',
+
     # Compiler configuration.
 
     # The following variables are used to specify compiler and linker
@@ -226,8 +251,10 @@
     'werror': '',
     # Cobalt doesn't currently support tcmalloc.
     'linux_use_tcmalloc': 0,
-
-    'enable_webdriver%': 0,
+    # The event polling mechanism available on this platform to support libevent.
+    # Platforms may redefine to 'poll' if necessary.
+    # Other mechanisms, e.g. devpoll, kqueue, select, are not yet supported.
+    'sb_libevent_method%': 'epoll',
   },
 
   'target_defaults': {
@@ -268,13 +295,25 @@
     'include_dirs': [ '<(DEPTH)' ],
     'libraries': [ '<@(platform_libraries)' ],
 
-    # TODO: This is needed to support the option to include
-    # posix_emulation.h to all compiled source files. This dependency should
-    # be refactored and removed.
-    'include_dirs_target': [
-      '<(DEPTH)/lbshell/src',
-    ],
     'conditions': [
+      ['final_executable_type=="shared_library"', {
+        'target_conditions': [
+          ['_toolset=="target"', {
+            'defines': [
+              # Rewrite main() functions into StarboardMain. TODO: This is a
+              # hack, it would be better to be more surgical, here.
+              'main=StarboardMain',
+            ],
+            'cflags': [
+              # To link into a shared library on Linux and similar platforms,
+              # the compiler must be told to generate Position Independent Code.
+              # This appears to cause errors when linking the code statically,
+              # however.
+              '-fPIC',
+            ],
+          }],
+        ],
+      }],
       ['posix_emulation_target_type == "shared_library"', {
         'defines': [
           '__LB_BASE_SHARED__=1',
@@ -294,6 +333,10 @@
           '<(DEPTH)/lbshell/src/platform/<(target_arch)/posix_emulation/lb_shell',
           # headers that we don't need, but should exist somewhere in the path:
           '<(DEPTH)/lbshell/src/platform/<(target_arch)/posix_emulation/place_holders',
+          # TODO: This is needed to support the option to include
+          # posix_emulation.h to all compiled source files. This dependency
+          # should be refactored and removed.
+          '<(DEPTH)/lbshell/src',
         ],
       }],  # OS == "lb_shell"
       ['OS == "starboard"', {
@@ -421,10 +464,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,
       },
     },
     {
@@ -432,10 +478,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/config/win.gypi b/src/cobalt/build/config/win.gypi
index 6a8cab4..8504e8b 100644
--- a/src/cobalt/build/config/win.gypi
+++ b/src/cobalt/build/config/win.gypi
@@ -34,6 +34,9 @@
     # there for acceptable values for this variable.
     'javascript_engine': 'javascriptcore',
 
+    # Webdriver won't compile on MSVC because of UTF8 string constant issues
+    'enable_webdriver': 0,
+
     # Compile with "PREfast" on by default.
     'static_analysis%': 'true',
 
diff --git a/src/cobalt/build/gyp_cobalt b/src/cobalt/build/gyp_cobalt
index 0e60bb9..9ef3934 100755
--- a/src/cobalt/build/gyp_cobalt
+++ b/src/cobalt/build/gyp_cobalt
@@ -208,7 +208,7 @@
         'cobalt_fastbuild': os.environ.get('LB_FASTBUILD', 0),
         'cobalt_version': gyp_utils.GetBuildNumber(),
         'host_os': _GetHostOS(),
-        'CC': os.environ.get('CC', ''),
+        'CC_HOST': os.environ.get('CC_HOST', os.environ.get('CC','')),
     }
     if self.platform_config.IsStarboard():
       variables['OS'] = 'starboard'
@@ -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/build/save_build_id.py b/src/cobalt/build/save_build_id.py
index 1f79d60..63ab937 100755
--- a/src/cobalt/build/save_build_id.py
+++ b/src/cobalt/build/save_build_id.py
@@ -14,13 +14,14 @@
 # limitations under the License.
 """Calculates the current Build ID and writes it to 'build.id'."""
 
+import argparse
 import logging
 import os
 import sys
+import textwrap
 
 import gyp_utils
 
-
 _SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
 _BUILD_ID_PATH = gyp_utils.BUILD_ID_PATH
 
@@ -32,12 +33,28 @@
 def main():
   logging.basicConfig(level=logging.WARNING, format='%(message)s')
 
+  parser = argparse.ArgumentParser(
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+      description=textwrap.dedent(__doc__))
+
+  parser.add_argument(
+      '--delete',
+      '-d',
+      action='store_true',
+      default=False,
+      help='Delete build.id file.')
+
+  options = parser.parse_args()
+
   # Update the build id to the latest, even if one is already set.
   try:
     os.unlink(_BUILD_ID_PATH)
   except OSError:
     pass
 
+  if options.delete:
+    return 0
+
   build_id = gyp_utils.GetBuildNumber()
   if not build_id:
     logging.error('Unable to retrieve build id.')
diff --git a/src/cobalt/content/fonts/10megabytes/fonts.xml b/src/cobalt/content/fonts/10megabytes/fonts.xml
index c0ffb0a..d066d4a 100644
--- a/src/cobalt/content/fonts/10megabytes/fonts.xml
+++ b/src/cobalt/content/fonts/10megabytes/fonts.xml
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
+<familyset version="1">
 <!--
     NOTE: Families with a "fallback" value of "true" are added to the fallback
     list, regardless of whether or not they are named. Fallback fonts are chosen
@@ -13,10 +14,9 @@
     indexed, and each page contains 256 characters, so character 1000 would be
     contained within page 3.
 -->
-<familyset version="1">
     <!-- first font is default -->
     <family name="sans-serif">
-        <font weight="400" style="normal">Roboto-Regular.ttf</font>
+        <font font_name="Roboto Regular" postscript_name="Roboto-Regular" style="normal" weight="400">Roboto-Regular.ttf</font>
     </family>
     <!-- Note that aliases must come after the fonts they reference. -->
     <alias name="arial" to="sans-serif" />
@@ -27,187 +27,187 @@
     <alias name="courier" to="serif-monospace" />
     <alias name="courier new" to="serif-monospace" />
     <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+        <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font>
     </family>
     <!-- fallback fonts -->
-    <family name="Noto Naskh Arabic UI" fallback="true" pages="0,6-8,32,37,46,251-254">
-        <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font>
+    <family fallback="true" name="Noto Naskh Arabic UI" pages="0,6-8,32,37,46,251-254">
+        <font font_name="Noto Naskh Arabic UI" postscript_name="NotoNaskhArabicUI" style="normal" weight="400">NotoNaskhArabicUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,18-19,45,171,254">
-        <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
+        <font font_name="Noto Sans Ethiopic" postscript_name="NotoSansEthiopic" style="normal" weight="400">NotoSansEthiopic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,5,32,37,251,254">
-        <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
+        <font font_name="Noto Sans Hebrew" postscript_name="NotoSansHebrew" style="normal" weight="400">NotoSansHebrew-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,2-3,14,32,37,254">
-        <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Thai UI" postscript_name="NotoSansThaiUI" style="normal" weight="400">NotoSansThaiUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,5,251,254">
-        <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font>
+        <font font_name="Noto Sans Armenian" postscript_name="NotoSansArmenian" style="normal" weight="400">NotoSansArmenian-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,5,16,45,254">
-        <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
+        <font font_name="Noto Sans Georgian" postscript_name="NotoSansGeorgian" style="normal" weight="400">NotoSansGeorgian-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,2,9,28,32,34,37,168,254">
-        <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font>
+        <font font_name="Noto Sans Devanagari UI" postscript_name="NotoSansDevanagariUI" style="normal" weight="400">NotoSansDevanagariUI-Regular.ttf</font>
     </family>
     <!-- Gujarati should come after Devanagari -->
     <family fallback="true" pages="0,9-10,32,34,37,168,254">
-        <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Gujarati UI" postscript_name="NotoSansGujaratiUI" style="normal" weight="400">NotoSansGujaratiUI-Regular.ttf</font>
     </family>
     <!-- Gurmukhi should come after Devanagari -->
     <family fallback="true" pages="0,9-10,32,34,37-38,168,254">
-        <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Gurmukhi UI" postscript_name="NotoSansGurmukhiUI" style="normal" weight="400">NotoSansGurmukhiUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,11,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
+        <font font_name="Noto Sans Tamil UI" postscript_name="NotoSansTamilUI" style="normal" weight="400">NotoSansTamilUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,9,13,32,34,37,254">
-        <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
+        <font font_name="Noto Sans Malayalam UI" postscript_name="NotoSansMalayalamUI" style="normal" weight="400">NotoSansMalayalamUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,32,34,37,254">
-        <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
+        <font font_name="Noto Sans Bengali UI" postscript_name="NotoSansBengaliUI" style="normal" weight="400">NotoSansBengaliUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,12,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
+        <font font_name="Noto Sans Telugu UI" postscript_name="NotoSansTeluguUI" style="normal" weight="400">NotoSansTeluguUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,12,32,34,37,254">
-        <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
+        <font font_name="Noto Sans Kannada UI" postscript_name="NotoSansKannadaUI" style="normal" weight="400">NotoSansKannadaUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,11,32,34,37,254">
-        <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font>
+        <font font_name="Noto Sans Oriya UI" postscript_name="NotoSansOriyaUI" style="normal" weight="400">NotoSansOriyaUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,13,32,34,37,254">
-        <font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font>
+        <font font_name="Noto Sans Sinhala" postscript_name="NotoSansSinhala" style="normal" weight="400">NotoSansSinhala-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,25,32,37">
-        <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
+        <font font_name="Noto Sans Khmer UI" postscript_name="NotoSansKhmerUI" style="normal" weight="400">NotoSansKhmerUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,14,32,37">
-        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font>
+        <font font_name="Noto Sans Lao UI" postscript_name="NotoSansLaoUI" style="normal" weight="400">NotoSansLaoUI-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,6-7,32,37,253-254">
-        <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font>
+        <font font_name="Noto Sans Thaana" postscript_name="NotoSansThaana" style="normal" weight="400">NotoSansThaana-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,170">
-        <font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
+        <font font_name="Noto Sans Cham" postscript_name="NotoSansCham" style="normal" weight="400">NotoSansCham-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,27,32,37,254">
-        <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
+        <font font_name="Noto Sans Balinese" postscript_name="NotoSansBalinese" style="normal" weight="400">NotoSansBalinese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,166,254,360-362">
-        <font weight="400" style="normal">NotoSansBamum-Regular.ttf</font>
+        <font font_name="Noto Sans Bamum" postscript_name="NotoSansBamum" style="normal" weight="400">NotoSansBamum-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,27,254">
-        <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font>
+        <font font_name="Noto Sans Batak" postscript_name="NotoSansBatak" style="normal" weight="400">NotoSansBatak-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,26,32,169,254">
-        <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font>
+        <font font_name="Noto Sans Buginese" postscript_name="NotoSansBuginese" style="normal" weight="400">NotoSansBuginese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font>
+        <font font_name="Noto Sans Buhid" postscript_name="NotoSansBuhid" style="normal" weight="400">NotoSansBuhid-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0-3,20-22,24,254">
-        <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font>
+        <font font_name="Noto Sans Canadian Aboriginal" postscript_name="NotoSansCanadianAboriginal" style="normal" weight="400">NotoSansCanadianAboriginal-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,19,254">
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+        <font font_name="Noto Sans Cherokee" postscript_name="NotoSansCherokee" style="normal" weight="400">NotoSansCherokee-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0-3,29,44,254">
-        <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font>
+        <font font_name="Noto Sans Coptic" postscript_name="NotoSansCoptic" style="normal" weight="400">NotoSansCoptic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,44,254">
-        <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
+        <font font_name="Noto Sans Glagolitic" postscript_name="NotoSansGlagolitic" style="normal" weight="400">NotoSansGlagolitic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
+        <font font_name="Noto Sans Hanunoo" postscript_name="NotoSansHanunoo" style="normal" weight="400">NotoSansHanunoo-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,169,254">
-        <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
+        <font font_name="Noto Sans Javanese" postscript_name="NotoSansJavanese" style="normal" weight="400">NotoSansJavanese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,169,254">
-        <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font>
+        <font font_name="Noto Sans Kayah Li" postscript_name="NotoSansKayahLi" style="normal" weight="400">NotoSansKayahLi-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,28,37,254">
-        <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
+        <font font_name="Noto Sans Lepcha" postscript_name="NotoSansLepcha" style="normal" weight="400">NotoSansLepcha-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,25,254">
-        <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font>
+        <font font_name="Noto Sans Limbu" postscript_name="NotoSansLimbu" style="normal" weight="400">NotoSansLimbu-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,2,164,254">
-        <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font>
+        <font font_name="Noto Sans Lisu" postscript_name="NotoSansLisu" style="normal" weight="400">NotoSansLisu-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,6,8,254">
-        <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font>
+        <font font_name="Noto Sans Mandaic" postscript_name="NotoSansMandaic" style="normal" weight="400">NotoSansMandaic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,170-171,254">
-        <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font>
+        <font font_name="Noto Sans Meetei Mayek" postscript_name="NotoSansMeeteiMayek" style="normal" weight="400">NotoSansMeeteiMayek-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,25,254">
-        <font weight="400" style="normal">NotoSansNewTaiLue-Regular.ttf</font>
+        <font font_name="Noto Sans New Tai Lue" postscript_name="NotoSansNewTaiLue" style="normal" weight="400">NotoSansNewTaiLue-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,6-7,32,46,253-254">
-        <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font>
+        <font font_name="Noto Sans NKo" postscript_name="NotoSansNKo" style="normal" weight="400">NotoSansNKo-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,28,254">
-        <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font>
+        <font font_name="Noto Sans Ol Chiki" postscript_name="NotoSansOlChiki" style="normal" weight="400">NotoSansOlChiki-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,169,254">
-        <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font>
+        <font font_name="Noto Sans Rejang" postscript_name="NotoSansRejang" style="normal" weight="400">NotoSansRejang-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,168,254">
-        <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font>
+        <font font_name="Noto Sans Saurashtra" postscript_name="NotoSansSaurashtra" style="normal" weight="400">NotoSansSaurashtra-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,27-28,254">
-        <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font>
+        <font font_name="Noto Sans Sundanese" postscript_name="NotoSansSundanese" style="normal" weight="400">NotoSansSundanese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,32,37,168,254">
-        <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font>
+        <font font_name="Noto Sans Syloti Nagri" postscript_name="NotoSansSylotiNagri" style="normal" weight="400">NotoSansSylotiNagri-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,6-7,32,34,37-38,254">
-        <font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font>
+        <font font_name="Noto Sans Syriac Estrangela" postscript_name="NotoSansSyriacEstrangela" style="normal" weight="400">NotoSansSyriacEstrangela-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
+        <font font_name="Noto Sans Tagbanwa" postscript_name="NotoSansTagbanwa" style="normal" weight="400">NotoSansTagbanwa-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,26,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Tham" postscript_name="NotoSansTaiTham" style="normal" weight="400">NotoSansTaiTham-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,167,170,254">
-        <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Viet" postscript_name="NotoSansTaiViet" style="normal" weight="400">NotoSansTaiViet-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,15,32,37,254">
-        <font weight="400" style="normal">NotoSansTibetan-Regular.ttf</font>
+        <font font_name="Noto Sans Tibetan" postscript_name="NotoSansTibetan" style="normal" weight="400">NotoSansTibetan-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,32,45,254">
-        <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
+        <font font_name="Noto Sans Tifinagh" postscript_name="NotoSansTifinagh" style="normal" weight="400">NotoSansTifinagh-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,165-166,254">
-        <font weight="400" style="normal">NotoSansVai-Regular.ttf</font>
+        <font font_name="Noto Sans Vai" postscript_name="NotoSansVai" style="normal" weight="400">NotoSansVai-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,160-164,254">
-        <font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
+        <font font_name="Noto Sans Yi" postscript_name="NotoSansYi" style="normal" weight="400">NotoSansYi-Regular.ttf</font>
     </family>
     <family fallback="true" pages="32-43">
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+        <font font_name="Noto Sans Symbols" postscript_name="NotoSansSymbols" style="normal" weight="400">NotoSansSymbols-Regular-Subsetted.ttf</font>
     </family>
     <family fallback="true" lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761">
-        <font weight="400" style="normal">NotoSansJP-Regular.otf</font>
+        <font font_name="Noto Sans JP Regular" postscript_name="NotoSansJP-Regular" style="normal" weight="400">NotoSansJP-Regular.otf</font>
     </family>
     <family fallback="true" pages="0,32-33,35-39,41,43,48,50,224,254-255,496-502,4068">
-        <font weight="400" style="normal">NotoEmoji-Regular.ttf</font>
+        <font font_name="Noto Emoji" postscript_name="NotoEmoji" style="normal" weight="400">NotoEmoji-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,14,17,32,48-51,77-159,172-215,249-250,254-255,260">
-        <font weight="400" style="normal">DroidSansFallback.ttf</font>
+        <font font_name="Droid Sans Fallback" postscript_name="DroidSansFallback" style="normal" weight="400">DroidSansFallback.ttf</font>
     </family>
     <!--
         Tai Le and Mongolian are intentionally kept last, to make sure they don't override
         the East Asian punctuation for Chinese.
     -->
     <family fallback="true" pages="0,16,25,48,254">
-        <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Le" postscript_name="NotoSansTaiLe" style="normal" weight="400">NotoSansTaiLe-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,24,32,36-37,48,254">
-        <font weight="400" style="normal">NotoSansMongolian-Regular.ttf</font>
+        <font font_name="Noto Sans Mongolian" postscript_name="NotoSansMongolian" style="normal" weight="400">NotoSansMongolian-Regular.ttf</font>
     </family>
 </familyset>
diff --git a/src/cobalt/content/fonts/minimal/fonts.xml b/src/cobalt/content/fonts/minimal/fonts.xml
index 1381b81..bd40ee3 100644
--- a/src/cobalt/content/fonts/minimal/fonts.xml
+++ b/src/cobalt/content/fonts/minimal/fonts.xml
@@ -1,11 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <familyset version="1">
+<!--
+    NOTE: Families with a "fallback" value of "true" are added to the fallback
+    list, regardless of whether or not they are named. Fallback fonts are chosen
+    based on a match: full BCP-47 language tag including script, then just
+    language, and finally order (the first font containing the glyph). Order of
+    appearance is also the tiebreaker for weight matching.
+
+    The pages attribute indicates which character pages are contained within
+    the font. It is used with character fallback to allow the system to quickly
+    determine that a character cannot appear in a font without requiring the
+    full character map to be loaded into memory. Character pages are zero
+    indexed, and each page contains 256 characters, so character 1000 would be
+    contained within page 3.
+-->
     <!-- Ideally, this font should only be used if there are no other fonts in
          our final image. -->
     <family name="Minimal Roboto">
-        <font weight="400" style="normal">MinimalRoboto.ttf</font>
+        <font font_name="Minimal Roboto" postscript_name="Minimal Roboto" style="normal" weight="400">MinimalRoboto.ttf</font>
     </family>
     <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+        <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font>
     </family>
 </familyset>
diff --git a/src/cobalt/content/fonts/unlimited/fonts.xml b/src/cobalt/content/fonts/unlimited/fonts.xml
index 3e1ec53..25426c3 100644
--- a/src/cobalt/content/fonts/unlimited/fonts.xml
+++ b/src/cobalt/content/fonts/unlimited/fonts.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<familyset version="1">
 <!--
     NOTE: Families with a "fallback" value of "true" are added to the fallback
     list, regardless of whether or not they are named. Fallback fonts are chosen
@@ -13,13 +13,12 @@
     indexed, and each page contains 256 characters, so character 1000 would be
     contained within page 3.
 -->
-<familyset version="1">
     <!-- first font is default -->
     <family name="sans-serif">
-        <font weight="400" style="normal">Roboto-Regular.ttf</font>
-        <font weight="400" style="italic">Roboto-Italic.ttf</font>
-        <font weight="700" style="normal">Roboto-Bold.ttf</font>
-        <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
+        <font font_name="Roboto Regular" postscript_name="Roboto-Regular" style="normal" weight="400">Roboto-Regular.ttf</font>
+        <font font_name="Roboto Italic" postscript_name="Roboto-Italic" style="italic" weight="400">Roboto-Italic.ttf</font>
+        <font font_name="Roboto Bold" postscript_name="Roboto-Bold" style="normal" weight="700">Roboto-Bold.ttf</font>
+        <font font_name="Roboto Bold Italic" postscript_name="Roboto-BoldItalic" style="italic" weight="700">Roboto-BoldItalic.ttf</font>
     </family>
     <!-- Note that aliases must come after the fonts they reference. -->
     <alias name="arial" to="sans-serif" />
@@ -30,13 +29,13 @@
     <!-- Ideally, this font should only be used if there are no other fonts in
          our final image. -->
     <family name="Minimal Roboto">
-        <font weight="400" style="normal">MinimalRoboto.ttf</font>
+        <font font_name="Minimal Roboto" postscript_name="Minimal Roboto" style="normal" weight="400">MinimalRoboto.ttf</font>
     </family>
     <family name="serif">
-        <font weight="400" style="normal">NotoSerif-Regular.ttf</font>
-        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
-        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
-        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+        <font font_name="Noto Serif" postscript_name="NotoSerif" style="normal" weight="400">NotoSerif-Regular.ttf</font>
+        <font font_name="Noto Serif Italic" postscript_name="NotoSerif-Italic" style="italic" weight="400">NotoSerif-Italic.ttf</font>
+        <font font_name="Noto Serif Bold" postscript_name="NotoSerif-Bold" style="normal" weight="700">NotoSerif-Bold.ttf</font>
+        <font font_name="Noto Serif Bold Italic" postscript_name="NotoSerif-BoldItalic" style="italic" weight="700">NotoSerif-BoldItalic.ttf</font>
     </family>
     <alias name="times" to="serif" />
     <alias name="times new roman" to="serif" />
@@ -47,243 +46,243 @@
     <alias name="fantasy" to="serif" />
     <alias name="ITC Stone Serif" to="serif" />
     <family name="monospace">
-        <font weight="400" style="normal">DroidSansMono.ttf</font>
+        <font font_name="Droid Sans Mono" postscript_name="DroidSansMono" style="normal" weight="400">DroidSansMono.ttf</font>
     </family>
     <alias name="sans-serif-monospace" to="monospace" />
     <alias name="monaco" to="monospace" />
     <family name="serif-monospace">
-        <font weight="400" style="normal">CutiveMono.ttf</font>
+        <font font_name="Cutive Mono" postscript_name="CutiveMono-Regular" style="normal" weight="400">CutiveMono.ttf</font>
     </family>
     <alias name="courier" to="serif-monospace" />
     <alias name="courier new" to="serif-monospace" />
     <family name="casual">
-        <font weight="400" style="normal">ComingSoon.ttf</font>
+        <font font_name="Coming Soon" postscript_name="ComingSoon" style="normal" weight="400">ComingSoon.ttf</font>
     </family>
     <family name="cursive">
-        <font weight="400" style="normal">DancingScript-Regular.ttf</font>
-        <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+        <font font_name="Dancing Script" postscript_name="DancingScript" style="normal" weight="400">DancingScript-Regular.ttf</font>
+        <font font_name="Dancing Script Bold" postscript_name="DancingScript-Bold" style="normal" weight="700">DancingScript-Bold.ttf</font>
     </family>
     <family name="sans-serif-smallcaps">
-        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+        <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font>
     </family>
     <!-- fallback fonts -->
-    <family name="Noto Naskh Arabic UI" fallback="true" pages="0,6-8,32,37,46,251-254">
-        <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    <family fallback="true" name="Noto Naskh Arabic UI" pages="0,6-8,32,37,46,251-254">
+        <font font_name="Noto Naskh Arabic UI" postscript_name="NotoNaskhArabicUI" style="normal" weight="400">NotoNaskhArabicUI-Regular.ttf</font>
+        <font font_name="Noto Naskh Arabic UI Bold" postscript_name="NotoNaskhArabicUI-Bold" style="normal" weight="700">NotoNaskhArabicUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,18-19,45,171,254">
-        <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
+        <font font_name="Noto Sans Ethiopic" postscript_name="NotoSansEthiopic" style="normal" weight="400">NotoSansEthiopic-Regular.ttf</font>
+        <font font_name="Noto Sans Ethiopic Bold" postscript_name="NotoSansEthiopic-Bold" style="normal" weight="700">NotoSansEthiopic-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,5,32,37,251,254">
-        <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font font_name="Noto Sans Hebrew" postscript_name="NotoSansHebrew" style="normal" weight="400">NotoSansHebrew-Regular.ttf</font>
+        <font font_name="Noto Sans Hebrew Bold" postscript_name="NotoSansHebrew-Bold" style="normal" weight="700">NotoSansHebrew-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,2-3,14,32,37,254">
-        <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+        <font font_name="Noto Sans Thai UI" postscript_name="NotoSansThaiUI" style="normal" weight="400">NotoSansThaiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Thai UI Bold" postscript_name="NotoSansThaiUI-Bold" style="normal" weight="700">NotoSansThaiUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,5,251,254">
-        <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font>
+        <font font_name="Noto Sans Armenian" postscript_name="NotoSansArmenian" style="normal" weight="400">NotoSansArmenian-Regular.ttf</font>
+        <font font_name="Noto Sans Armenian Bold" postscript_name="NotoSansArmenian-Bold" style="normal" weight="700">NotoSansArmenian-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,5,16,45,254">
-        <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansGeorgian-Bold.ttf</font>
+        <font font_name="Noto Sans Georgian" postscript_name="NotoSansGeorgian" style="normal" weight="400">NotoSansGeorgian-Regular.ttf</font>
+        <font font_name="Noto Sans Georgian Bold" postscript_name="NotoSansGeorgian-Bold" style="normal" weight="700">NotoSansGeorgian-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,2,9,28,32,34,37,168,254">
-        <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansDevanagariUI-Bold.ttf</font>
+        <font font_name="Noto Sans Devanagari UI" postscript_name="NotoSansDevanagariUI" style="normal" weight="400">NotoSansDevanagariUI-Regular.ttf</font>
+        <font font_name="Noto Sans Devanagari UI Bold" postscript_name="NotoSansDevanagariUI-Bold" style="normal" weight="700">NotoSansDevanagariUI-Bold.ttf</font>
     </family>
     <!-- Gujarati should come after Devanagari -->
     <family fallback="true" pages="0,9-10,32,34,37,168,254">
-        <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+        <font font_name="Noto Sans Gujarati UI" postscript_name="NotoSansGujaratiUI" style="normal" weight="400">NotoSansGujaratiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Gujarati UI Bold" postscript_name="NotoSansGujaratiUI-Bold" style="normal" weight="700">NotoSansGujaratiUI-Bold.ttf</font>
     </family>
     <!-- Gurmukhi should come after Devanagari -->
     <family fallback="true" pages="0,9-10,32,34,37-38,168,254">
-        <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansGurmukhiUI-Bold.ttf</font>
+        <font font_name="Noto Sans Gurmukhi UI" postscript_name="NotoSansGurmukhiUI" style="normal" weight="400">NotoSansGurmukhiUI-Regular.ttf</font>
+        <font font_name="Noto Sans Gurmukhi UI Bold" postscript_name="NotoSansGurmukhiUI-Bold" style="normal" weight="700">NotoSansGurmukhiUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,11,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansTamilUI-Bold.ttf</font>
+        <font font_name="Noto Sans Tamil UI" postscript_name="NotoSansTamilUI" style="normal" weight="400">NotoSansTamilUI-Regular.ttf</font>
+        <font font_name="Noto Sans Tamil UI Bold" postscript_name="NotoSansTamilUI-Bold" style="normal" weight="700">NotoSansTamilUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,3,9,13,32,34,37,254">
-        <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansMalayalamUI-Bold.ttf</font>
+        <font font_name="Noto Sans Malayalam UI" postscript_name="NotoSansMalayalamUI" style="normal" weight="400">NotoSansMalayalamUI-Regular.ttf</font>
+        <font font_name="Noto Sans Malayalam UI Bold" postscript_name="NotoSansMalayalamUI-Bold" style="normal" weight="700">NotoSansMalayalamUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,32,34,37,254">
-        <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansBengaliUI-Bold.ttf</font>
+        <font font_name="Noto Sans Bengali UI" postscript_name="NotoSansBengaliUI" style="normal" weight="400">NotoSansBengaliUI-Regular.ttf</font>
+        <font font_name="Noto Sans Bengali UI Bold" postscript_name="NotoSansBengaliUI-Bold" style="normal" weight="700">NotoSansBengaliUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,12,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansTeluguUI-Bold.ttf</font>
+        <font font_name="Noto Sans Telugu UI" postscript_name="NotoSansTeluguUI" style="normal" weight="400">NotoSansTeluguUI-Regular.ttf</font>
+        <font font_name="Noto Sans Telugu UI Bold" postscript_name="NotoSansTeluguUI-Bold" style="normal" weight="700">NotoSansTeluguUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,12,32,34,37,254">
-        <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansKannadaUI-Bold.ttf</font>
+        <font font_name="Noto Sans Kannada UI" postscript_name="NotoSansKannadaUI" style="normal" weight="400">NotoSansKannadaUI-Regular.ttf</font>
+        <font font_name="Noto Sans Kannada UI Bold" postscript_name="NotoSansKannadaUI-Bold" style="normal" weight="700">NotoSansKannadaUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,11,32,34,37,254">
-        <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+        <font font_name="Noto Sans Oriya UI" postscript_name="NotoSansOriyaUI" style="normal" weight="400">NotoSansOriyaUI-Regular.ttf</font>
+        <font font_name="Noto Sans Oriya UI Bold" postscript_name="NotoSansOriyaUI-Bold" style="normal" weight="700">NotoSansOriyaUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,9,13,32,34,37,254">
-        <font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font>
+        <font font_name="Noto Sans Sinhala" postscript_name="NotoSansSinhala" style="normal" weight="400">NotoSansSinhala-Regular.ttf</font>
+        <font font_name="Noto Sans Sinhala Bold" postscript_name="NotoSansSinhala-Bold" style="normal" weight="700">NotoSansSinhala-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,23,25,32,37">
-        <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+        <font font_name="Noto Sans Khmer UI" postscript_name="NotoSansKhmerUI" style="normal" weight="400">NotoSansKhmerUI-Regular.ttf</font>
+        <font font_name="Noto Sans Khmer UI Bold" postscript_name="NotoSansKhmerUI-Bold" style="normal" weight="700">NotoSansKhmerUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,3,14,32,37">
-        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+        <font font_name="Noto Sans Lao UI" postscript_name="NotoSansLaoUI" style="normal" weight="400">NotoSansLaoUI-Regular.ttf</font>
+        <font font_name="Noto Sans Lao UI Bold" postscript_name="NotoSansLaoUI-Bold" style="normal" weight="700">NotoSansLaoUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,16,32,37,169-170,254">
-        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.ttf</font>
+        <font font_name="Noto Sans Myanmar UI" postscript_name="NotoSansMyanmarUI" style="normal" weight="400">NotoSansMyanmarUI-Regular.ttf</font>
+        <font font_name="Noto Sans Myanmar UI Bold" postscript_name="NotoSansMyanmarUI-Bold" style="normal" weight="700">NotoSansMyanmarUI-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,6-7,32,37,253-254">
-        <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+        <font font_name="Noto Sans Thaana" postscript_name="NotoSansThaana" style="normal" weight="400">NotoSansThaana-Regular.ttf</font>
+        <font font_name="Noto Sans Thaana Bold" postscript_name="NotoSansThaana-Bold" style="normal" weight="700">NotoSansThaana-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,3,170">
-        <font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
-        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+        <font font_name="Noto Sans Cham" postscript_name="NotoSansCham" style="normal" weight="400">NotoSansCham-Regular.ttf</font>
+        <font font_name="Noto Sans Cham Bold" postscript_name="NotoSansCham-Bold" style="normal" weight="700">NotoSansCham-Bold.ttf</font>
     </family>
     <family fallback="true" pages="0,27,32,37,254">
-        <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
+        <font font_name="Noto Sans Balinese" postscript_name="NotoSansBalinese" style="normal" weight="400">NotoSansBalinese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,166,254,360-362">
-        <font weight="400" style="normal">NotoSansBamum-Regular.ttf</font>
+        <font font_name="Noto Sans Bamum" postscript_name="NotoSansBamum" style="normal" weight="400">NotoSansBamum-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,27,254">
-        <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font>
+        <font font_name="Noto Sans Batak" postscript_name="NotoSansBatak" style="normal" weight="400">NotoSansBatak-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,26,32,169,254">
-        <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font>
+        <font font_name="Noto Sans Buginese" postscript_name="NotoSansBuginese" style="normal" weight="400">NotoSansBuginese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font>
+        <font font_name="Noto Sans Buhid" postscript_name="NotoSansBuhid" style="normal" weight="400">NotoSansBuhid-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0-3,20-22,24,254">
-        <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font>
+        <font font_name="Noto Sans Canadian Aboriginal" postscript_name="NotoSansCanadianAboriginal" style="normal" weight="400">NotoSansCanadianAboriginal-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,19,254">
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+        <font font_name="Noto Sans Cherokee" postscript_name="NotoSansCherokee" style="normal" weight="400">NotoSansCherokee-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0-3,29,44,254">
-        <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font>
+        <font font_name="Noto Sans Coptic" postscript_name="NotoSansCoptic" style="normal" weight="400">NotoSansCoptic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,44,254">
-        <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
+        <font font_name="Noto Sans Glagolitic" postscript_name="NotoSansGlagolitic" style="normal" weight="400">NotoSansGlagolitic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
+        <font font_name="Noto Sans Hanunoo" postscript_name="NotoSansHanunoo" style="normal" weight="400">NotoSansHanunoo-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,169,254">
-        <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
+        <font font_name="Noto Sans Javanese" postscript_name="NotoSansJavanese" style="normal" weight="400">NotoSansJavanese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,169,254">
-        <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font>
+        <font font_name="Noto Sans Kayah Li" postscript_name="NotoSansKayahLi" style="normal" weight="400">NotoSansKayahLi-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,28,37,254">
-        <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
+        <font font_name="Noto Sans Lepcha" postscript_name="NotoSansLepcha" style="normal" weight="400">NotoSansLepcha-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,25,254">
-        <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font>
+        <font font_name="Noto Sans Limbu" postscript_name="NotoSansLimbu" style="normal" weight="400">NotoSansLimbu-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,2,164,254">
-        <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font>
+        <font font_name="Noto Sans Lisu" postscript_name="NotoSansLisu" style="normal" weight="400">NotoSansLisu-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,6,8,254">
-        <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font>
+        <font font_name="Noto Sans Mandaic" postscript_name="NotoSansMandaic" style="normal" weight="400">NotoSansMandaic-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,170-171,254">
-        <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font>
+        <font font_name="Noto Sans Meetei Mayek" postscript_name="NotoSansMeeteiMayek" style="normal" weight="400">NotoSansMeeteiMayek-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,25,254">
-        <font weight="400" style="normal">NotoSansNewTaiLue-Regular.ttf</font>
+        <font font_name="Noto Sans New Tai Lue" postscript_name="NotoSansNewTaiLue" style="normal" weight="400">NotoSansNewTaiLue-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,6-7,32,46,253-254">
-        <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font>
+        <font font_name="Noto Sans NKo" postscript_name="NotoSansNKo" style="normal" weight="400">NotoSansNKo-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,28,254">
-        <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font>
+        <font font_name="Noto Sans Ol Chiki" postscript_name="NotoSansOlChiki" style="normal" weight="400">NotoSansOlChiki-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,169,254">
-        <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font>
+        <font font_name="Noto Sans Rejang" postscript_name="NotoSansRejang" style="normal" weight="400">NotoSansRejang-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,168,254">
-        <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font>
+        <font font_name="Noto Sans Saurashtra" postscript_name="NotoSansSaurashtra" style="normal" weight="400">NotoSansSaurashtra-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,27-28,254">
-        <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font>
+        <font font_name="Noto Sans Sundanese" postscript_name="NotoSansSundanese" style="normal" weight="400">NotoSansSundanese-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,9,32,37,168,254">
-        <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font>
+        <font font_name="Noto Sans Syloti Nagri" postscript_name="NotoSansSylotiNagri" style="normal" weight="400">NotoSansSylotiNagri-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,6-7,32,34,37-38,254">
-        <font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font>
+        <font font_name="Noto Sans Syriac Estrangela" postscript_name="NotoSansSyriacEstrangela" style="normal" weight="400">NotoSansSyriacEstrangela-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,23,254">
-        <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
+        <font font_name="Noto Sans Tagbanwa" postscript_name="NotoSansTagbanwa" style="normal" weight="400">NotoSansTagbanwa-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,26,32,34,37,254">
-        <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Tham" postscript_name="NotoSansTaiTham" style="normal" weight="400">NotoSansTaiTham-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,32,37,167,170,254">
-        <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Viet" postscript_name="NotoSansTaiViet" style="normal" weight="400">NotoSansTaiViet-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,15,32,37,254">
-        <font weight="400" style="normal">NotoSansTibetan-Regular.ttf</font>
+        <font font_name="Noto Sans Tibetan" postscript_name="NotoSansTibetan" style="normal" weight="400">NotoSansTibetan-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,3,32,45,254">
-        <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
+        <font font_name="Noto Sans Tifinagh" postscript_name="NotoSansTifinagh" style="normal" weight="400">NotoSansTifinagh-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,165-166,254">
-        <font weight="400" style="normal">NotoSansVai-Regular.ttf</font>
+        <font font_name="Noto Sans Vai" postscript_name="NotoSansVai" style="normal" weight="400">NotoSansVai-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,160-164,254">
-        <font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
+        <font font_name="Noto Sans Yi" postscript_name="NotoSansYi" style="normal" weight="400">NotoSansYi-Regular.ttf</font>
     </family>
     <family fallback="true" pages="32-43">
-        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+        <font font_name="Noto Sans Symbols" postscript_name="NotoSansSymbols" style="normal" weight="400">NotoSansSymbols-Regular-Subsetted.ttf</font>
     </family>
     <family fallback="true" lang="zh-Hans" pages="0,2,32-39,41,43,46-159,249-250,254-255,497-498,512-513,518,524,531-533,553,565,572,574,577,584-586,597,602,606,610,612,614-615,617,619-620,632,639,644,646-647,651-652,654,662,664,671,679-682,687,689,691-696,698-702,704-718">
-        <font weight="400" style="normal">NotoSansSC-Regular.otf</font>
+        <font font_name="Noto Sans SC Regular" postscript_name="NotoSansSC-Regular" style="normal" weight="400">NotoSansSC-Regular.otf</font>
     </family>
     <family fallback="true" lang="zh-Hant" pages="0,32,34,46-48,50,52-159,249-250,254-255,498,512-657,660-661,663-678,685,760-761">
-        <font weight="400" style="normal">NotoSansTC-Regular.otf</font>
+        <font font_name="Noto Sans TC Regular" postscript_name="NotoSansTC-Regular" style="normal" weight="400">NotoSansTC-Regular.otf</font>
     </family>
     <family fallback="true" lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761">
-        <font weight="400" style="normal">NotoSansJP-Regular.otf</font>
+        <font font_name="Noto Sans JP Regular" postscript_name="NotoSansJP-Regular" style="normal" weight="400">NotoSansJP-Regular.otf</font>
     </family>
     <family fallback="true" lang="ko" pages="0,17,32,48-50,169,172-215,255">
-        <font weight="400" style="normal">NotoSansKR-Regular.otf</font>
+        <font font_name="Noto Sans KR Regular" postscript_name="NotoSansKR-Regular" style="normal" weight="400">NotoSansKR-Regular.otf</font>
     </family>
     <family fallback="true" pages="0,17,32,49-50,172-215">
-        <font weight="400" style="normal">NanumGothic.ttf</font>
+        <font font_name="NanumGothic" postscript_name="NanumGothic" style="normal" weight="400">NanumGothic.ttf</font>
     </family>
     <family fallback="true" pages="0,32-33,35-39,41,43,48,50,224,254-255,496-502,4068">
-        <font weight="400" style="normal">NotoEmoji-Regular.ttf</font>
+        <font font_name="Noto Emoji" postscript_name="NotoEmoji" style="normal" weight="400">NotoEmoji-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,14,17,32,48-51,77-159,172-215,249-250,254-255,260">
-        <font weight="400" style="normal">DroidSansFallback.ttf</font>
+        <font font_name="Droid Sans Fallback" postscript_name="DroidSansFallback" style="normal" weight="400">DroidSansFallback.ttf</font>
     </family>
     <family fallback="true" lang="ja" pages="0,2-4,32-38,48,50-51,78-159,249-250,255">
-        <font weight="400" style="normal">MTLmr3m.ttf</font>
+        <font font_name="MotoyaLMaru W3 mono" postscript_name="MotoyaLMaru-W3-90ms-RKSJ-H" style="normal" weight="400">MTLmr3m.ttf</font>
     </family>
     <!--
         Tai Le and Mongolian are intentionally kept last, to make sure they don't override
         the East Asian punctuation for Chinese.
     -->
     <family fallback="true" pages="0,16,25,48,254">
-        <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font>
+        <font font_name="Noto Sans Tai Le" postscript_name="NotoSansTaiLe" style="normal" weight="400">NotoSansTaiLe-Regular.ttf</font>
     </family>
     <family fallback="true" pages="0,24,32,36-37,48,254">
-        <font weight="400" style="normal">NotoSansMongolian-Regular.ttf</font>
+        <font font_name="Noto Sans Mongolian" postscript_name="NotoSansMongolian" style="normal" weight="400">NotoSansMongolian-Regular.ttf</font>
     </family>
 </familyset>
diff --git a/src/cobalt/content/ssl/certs/0a775a30.0 b/src/cobalt/content/ssl/certs/0a775a30.0
new file mode 100644
index 0000000..7d540a4
--- /dev/null
+++ b/src/cobalt/content/ssl/certs/0a775a30.0
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
+736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
+DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
+fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
+njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
+-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/1001acf7.0 b/src/cobalt/content/ssl/certs/1001acf7.0
new file mode 100644
index 0000000..5496703
--- /dev/null
+++ b/src/cobalt/content/ssl/certs/1001acf7.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
+MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
+QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
+MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
+cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
+f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
+mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
+zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
+fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
+vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
+Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
+zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
+Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
+k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
+lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
+Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
+d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
+XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
+gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
+d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
+J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
+DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
++SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
+F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
+SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
+E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
+-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/626dceaf.0 b/src/cobalt/content/ssl/certs/626dceaf.0
new file mode 100644
index 0000000..984f1d1
--- /dev/null
+++ b/src/cobalt/content/ssl/certs/626dceaf.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
+MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
+QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
+MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
+cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
+CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
+GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
+XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
+re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
+PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
+mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
+8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
+x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
+nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
+kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
+twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
+8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
+vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
+z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
+pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
+pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
+R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
+RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
+0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
+5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
+izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
+yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
+-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/a3418fda.0 b/src/cobalt/content/ssl/certs/a3418fda.0
new file mode 100644
index 0000000..07372d3
--- /dev/null
+++ b/src/cobalt/content/ssl/certs/a3418fda.0
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
+hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
+xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
+CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
+sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
+-----END CERTIFICATE-----
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp
index 185ee0b..32f9c0d 100644
--- a/src/cobalt/css_parser/css_parser.gyp
+++ b/src/cobalt/css_parser/css_parser.gyp
@@ -111,6 +111,7 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
+        '<(DEPTH)/nb/nb.gyp:nb',
         '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
         'css_grammar',
       ],
diff --git a/src/cobalt/css_parser/grammar.h b/src/cobalt/css_parser/grammar.h
index 7168cb1..2e21c88 100644
--- a/src/cobalt/css_parser/grammar.h
+++ b/src/cobalt/css_parser/grammar.h
@@ -55,6 +55,8 @@
 #include "cobalt/cssom/css_style_declaration.h"
 #include "cobalt/cssom/css_style_rule.h"
 #include "cobalt/cssom/css_style_sheet.h"
+#include "cobalt/cssom/filter_function.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/integer_value.h"
 #include "cobalt/cssom/keyword_value.h"
 #include "cobalt/cssom/length_value.h"
@@ -64,6 +66,7 @@
 #include "cobalt/cssom/media_feature_keyword_value.h"
 #include "cobalt/cssom/media_list.h"
 #include "cobalt/cssom/media_query.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/number_value.h"
 #include "cobalt/cssom/percentage_value.h"
 #include "cobalt/cssom/property_key_list_value.h"
@@ -78,6 +81,8 @@
 #include "cobalt/cssom/transform_function.h"
 #include "cobalt/cssom/transform_function_list_value.h"
 #include "cobalt/cssom/url_src_value.h"
+#include "third_party/glm/glm/gtc/type_ptr.hpp"
+#include "third_party/glm/glm/mat4x4.hpp"
 
 namespace cobalt {
 namespace css_parser {
@@ -100,7 +105,7 @@
 
 #if defined(OS_STARBOARD)
 #include "starboard/memory.h"
-#define YYFREE SbMemoryFree
+#define YYFREE SbMemoryDeallocate
 #define YYMALLOC SbMemoryAllocate
 #endif
 
diff --git a/src/cobalt/css_parser/grammar.y b/src/cobalt/css_parser/grammar.y
index 34a7143..e4bb9a1 100644
--- a/src/cobalt/css_parser/grammar.y
+++ b/src/cobalt/css_parser/grammar.y
@@ -114,6 +114,7 @@
 %token kColorToken                            // color
 %token kContentToken                          // content
 %token kDisplayToken                          // display
+%token kFilterToken                           // filter
 %token kFontToken                             // font
 %token kFontFamilyToken                       // font-family
 %token kFontSizeToken                         // font-size
@@ -220,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
@@ -247,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
@@ -353,6 +357,7 @@
 %token kLinearGradientFunctionToken     // linear-gradient(
 %token kLocalFunctionToken              // local(
 %token kMatrixFunctionToken             // matrix(
+%token kMatrix3dFunctionToken           // matrix3d(
 %token kNotFunctionToken                // not(
 %token kNthChildFunctionToken           // nth-child(
 %token kNthLastChildFunctionToken       // nth-last-child(
@@ -370,6 +375,7 @@
 %token kRadialGradientFunctionToken     // radial-gradient(
 %token kRGBFunctionToken                // rgb(
 %token kRGBAFunctionToken               // rgba(
+%token kCobaltMtmFunctionToken          // -cobalt-mtm(
 
 // Tokens with a string value.
 %token <string> kStringToken            // "...", '...'
@@ -582,8 +588,17 @@
                        white_space_property_value
                        width_property_value
                        z_index_property_value
+                       filter_property_value
 %destructor { SafeRelease($$); } <property_value>
 
+%union { std::vector<float>* number_matrix; }
+%type <number_matrix> number_matrix
+%destructor { delete $$; } <number_matrix>
+
+%union { glm::mat4* matrix4x4; }
+%type <matrix4x4> cobalt_mtm_transform_function
+%destructor { delete $$; } <matrix4x4>
+
 %union { MarginOrPaddingShorthand* margin_or_padding_shorthand; }
 %type <margin_or_padding_shorthand> margin_property_value padding_property_value
 %destructor { delete $$; } <margin_or_padding_shorthand>
@@ -763,6 +778,29 @@
 %type <transform_functions> transform_list
 %destructor { delete $$; } <transform_functions>
 
+%union { cssom::FilterFunction* filter_function; }
+%type <filter_function> cobalt_mtm_filter_function
+%type <filter_function> filter_function
+%destructor { delete $$; } <filter_function>
+
+%union { cssom::FilterFunctionListValue::Builder* cobalt_mtm_filter_functions; }
+%type <cobalt_mtm_filter_functions> filter_function_list
+%destructor { delete $$; } <cobalt_mtm_filter_functions>
+
+%union {
+  cssom::MTMFunction::ResolutionMatchedMeshListBuilder* cobalt_mtm_resolution_matched_meshes; }
+%type <cobalt_mtm_resolution_matched_meshes> cobalt_mtm_resolution_matched_mesh_list
+%destructor { delete $$; } <cobalt_mtm_resolution_matched_meshes>
+
+%union { cssom::MTMFunction::ResolutionMatchedMesh* cobalt_mtm_resolution_matched_mesh; }
+%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>
@@ -1371,6 +1409,10 @@
     $$ = TrivialStringPiece::FromCString(
             cssom::GetPropertyName(cssom::kDisplayProperty));
   }
+  | kFilterToken {
+    $$ = TrivialStringPiece::FromCString(
+            cssom::GetPropertyName(cssom::kFilterProperty));
+  }
   | kFontToken {
     $$ = TrivialStringPiece::FromCString(
             cssom::GetPropertyName(cssom::kFontProperty));
@@ -1718,6 +1760,9 @@
   | kMiddleToken {
     $$ = TrivialStringPiece::FromCString(cssom::kMiddleKeywordName);
   }
+  | kMonoscopicToken {
+    $$ = TrivialStringPiece::FromCString(cssom::kMonoscopicKeywordName);
+  }
   | kMonospaceToken {
     $$ = TrivialStringPiece::FromCString(cssom::kMonospaceKeywordName);
   }
@@ -1796,6 +1841,14 @@
   | kStepStartToken {
     $$ = TrivialStringPiece::FromCString(cssom::kStepStartKeywordName);
   }
+  | kStereoscopicLeftRightToken {
+    $$ = TrivialStringPiece::FromCString(
+             cssom::kStereoscopicLeftRightKeywordName);
+  }
+  | kStereoscopicTopBottomToken {
+    $$ = TrivialStringPiece::FromCString(
+             cssom::kStereoscopicTopBottomKeywordName);
+  }
   | kTealToken {
     $$ = TrivialStringPiece::FromCString(cssom::kTealKeywordName);
   }
@@ -3384,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 {
@@ -3397,15 +3450,17 @@
 
 comma_separated_box_shadow_list:
     validated_box_shadow_list {
-    if ($1) {
+    scoped_refptr<cssom::PropertyValue> shadow = MakeScopedRefPtrAndRelease($1);
+    if (shadow) {
       $$ = new cssom::PropertyListValue::Builder();
-      $$->push_back(MakeScopedRefPtrAndRelease($1));
+      $$->push_back(shadow);
     }
   }
   | comma_separated_box_shadow_list comma validated_box_shadow_list {
     $$ = $1;
-    if ($$ && $3) {
-      $$->push_back(MakeScopedRefPtrAndRelease($3));
+    scoped_refptr<cssom::PropertyValue> shadow = MakeScopedRefPtrAndRelease($3);
+    if ($$ && shadow) {
+      $$->push_back(shadow);
     }
   }
   ;
@@ -4200,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 {
@@ -5761,6 +5816,12 @@
                                       MakeScopedRefPtrAndRelease($4), $5)
             : NULL;
   }
+  | kFilterToken maybe_whitespace colon filter_property_value
+      maybe_important {
+    $$ = $4 ? new PropertyDeclaration(cssom::kFilterProperty,
+                                      MakeScopedRefPtrAndRelease($4), $5)
+            : NULL;
+  }
   | kFontToken maybe_whitespace colon font_property_value maybe_important {
     scoped_ptr<FontShorthand> font($4);
     DCHECK(font);
@@ -6406,3 +6467,122 @@
     }
   }
   ;
+
+// Filters that can be applied to the object's rendering.
+//   https://www.w3.org/TR/filter-effects-1/#FilterProperty
+filter_property_value:
+    kNoneToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetNone().get());
+  }
+  | filter_function_list {
+    scoped_ptr<cssom::FilterFunctionListValue::Builder> property_value($1);
+    $$ = AddRef(new cssom::FilterFunctionListValue(property_value->Pass()));
+  }
+  | common_values
+  ;
+
+filter_function_list:
+  // TODO: Parse list of filter_function's. This only parses one-element lists.
+    filter_function {
+    $$ = new cssom::FilterFunctionListValue::Builder();
+    $$->push_back($1);
+  }
+  ;
+
+// The set of allowed filter functions.
+//   https://www.w3.org/TR/filter-effects-1/
+filter_function:
+    cobalt_mtm_filter_function {
+    $$ = $1;
+  }
+  ;
+
+cobalt_mtm_filter_function:
+  // 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_cobalt_mtm_stereo_mode
+        ')' maybe_whitespace {
+    scoped_ptr<cssom::MTMFunction::ResolutionMatchedMeshListBuilder>
+        resolution_matched_mesh_urls($4);
+    scoped_ptr<glm::mat4> transform($9);
+
+    $$ = new cssom::MTMFunction(
+        MakeScopedRefPtrAndRelease($3),
+        resolution_matched_mesh_urls->Pass(),
+        $6,
+        $7,
+        *transform,
+        MakeScopedRefPtrAndRelease($10));
+  }
+  ;
+
+cobalt_mtm_resolution_matched_mesh_list:
+    /* empty */ {
+    $$ = new cssom::MTMFunction::ResolutionMatchedMeshListBuilder();
+  }
+  // Specifies a different mesh for a particular image resolution.
+  | cobalt_mtm_resolution_matched_mesh_list cobalt_mtm_resolution_matched_mesh {
+    $$ = $1;
+    $$->push_back($2);
+  }
+  ;
+
+cobalt_mtm_resolution_matched_mesh:
+    non_negative_integer non_negative_integer url {
+    $$ = new cssom::MTMFunction::ResolutionMatchedMesh($1, $2,
+      MakeScopedRefPtrAndRelease($3));
+  }
+  ;
+
+// The set of transform functions allowed in MTM filters, currently a separate
+// production hierarchy from the main <transform_function> and represented as a
+// glm::mat4.
+//   https://www.w3.org/TR/css-transforms-1/#three-d-transform-functions
+cobalt_mtm_transform_function:
+  // Specifies an arbitrary affine 3D transformation, currently the only
+  // supported transform in MTM.
+    kMatrix3dFunctionToken maybe_whitespace number_matrix ')' maybe_whitespace {
+    scoped_ptr<std::vector<float> > matrix($3);
+    if (matrix == NULL || matrix->size() !=  16) {
+      parser_impl->LogError(
+          @3,
+          "matrix3d function expects 16 floating-point numbers as arguments");
+      YYERROR;
+    } else {
+      // GLM and the W3 spec both use column-major order.
+      $$ = new glm::mat4(glm::make_mat4(&(*matrix)[0]));
+    }
+  }
+  ;
+
+number_matrix:
+    number {
+    $$ = new std::vector<float>(1, $1);
+  }
+  | number_matrix comma number {
+    $$ = $1;
+    $$->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.cc b/src/cobalt/css_parser/parser.cc
index d289e3c..cb4fb84 100644
--- a/src/cobalt/css_parser/parser.cc
+++ b/src/cobalt/css_parser/parser.cc
@@ -24,6 +24,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
 #include "base/hash_tables.h"
 #include "base/lazy_instance.h"
 #include "base/optional.h"
@@ -66,6 +67,7 @@
 #include "cobalt/cssom/matrix_function.h"
 #include "cobalt/cssom/media_list.h"
 #include "cobalt/cssom/media_query.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/next_sibling_combinator.h"
 #include "cobalt/cssom/not_pseudo_class.h"
 #include "cobalt/cssom/number_value.h"
@@ -83,6 +85,7 @@
 #include "cobalt/cssom/unicode_range_value.h"
 #include "cobalt/cssom/universal_selector.h"
 #include "cobalt/cssom/url_value.h"
+#include "nb/memory_scope.h"
 
 namespace cobalt {
 namespace css_parser {
@@ -252,18 +255,21 @@
       into_declaration_data_(NULL) {}
 
 scoped_refptr<cssom::CSSStyleSheet> ParserImpl::ParseStyleSheet() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kStyleSheetEntryPointToken);
   return Parse() ? style_sheet_
                  : make_scoped_refptr(new cssom::CSSStyleSheet(css_parser_));
 }
 
 scoped_refptr<cssom::CSSRule> ParserImpl::ParseRule() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kRuleEntryPointToken);
   return Parse() ? rule_ : NULL;
 }
 
 scoped_refptr<cssom::CSSDeclaredStyleData>
 ParserImpl::ParseStyleDeclarationList() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kStyleDeclarationListEntryPointToken);
   return Parse() ? style_declaration_data_
                  : make_scoped_refptr(new cssom::CSSDeclaredStyleData());
@@ -271,6 +277,7 @@
 
 scoped_refptr<cssom::CSSFontFaceDeclarationData>
 ParserImpl::ParseFontFaceDeclarationList() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kFontFaceDeclarationListEntryPointToken);
   return Parse() ? font_face_declaration_data_
                  : make_scoped_refptr(new cssom::CSSFontFaceDeclarationData());
@@ -288,6 +295,7 @@
 
 scoped_refptr<cssom::PropertyValue> ParserImpl::ParsePropertyValue(
     const std::string& property_name) {
+  TRACK_MEMORY_SCOPE("CSS");
   Token property_name_token;
   bool is_property_name_known =
       scanner_.DetectPropertyNameToken(property_name, &property_name_token);
@@ -310,6 +318,7 @@
 void ParserImpl::ParsePropertyIntoDeclarationData(
     const std::string& property_name,
     cssom::CSSDeclarationData* declaration_data) {
+  TRACK_MEMORY_SCOPE("CSS");
   Token property_name_token;
   bool is_property_name_known =
       scanner_.DetectPropertyNameToken(property_name, &property_name_token);
@@ -338,11 +347,13 @@
 }
 
 scoped_refptr<cssom::MediaList> ParserImpl::ParseMediaList() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kMediaListEntryPointToken);
   return Parse() ? media_list_ : make_scoped_refptr(new cssom::MediaList());
 }
 
 scoped_refptr<cssom::MediaQuery> ParserImpl::ParseMediaQuery() {
+  TRACK_MEMORY_SCOPE("CSS");
   scanner_.PrependToken(kMediaQueryEntryPointToken);
   return Parse() ? media_query_ : make_scoped_refptr(new cssom::MediaQuery());
 }
@@ -362,8 +373,10 @@
 }
 
 bool ParserImpl::Parse() {
+  TRACK_MEMORY_SCOPE("CSS");
   // For more information on error codes
   // see http://www.gnu.org/software/bison/manual/html_node/Parser-Function.html
+  TRACE_EVENT0("cobalt::css_parser", "ParseImpl::Parse");
   last_syntax_error_location_ = base::nullopt;
   int error_code(yyparse(this));
   switch (error_code) {
diff --git a/src/cobalt/css_parser/parser_test.cc b/src/cobalt/css_parser/parser_test.cc
index 0aaf421..2de5da7 100644
--- a/src/cobalt/css_parser/parser_test.cc
+++ b/src/cobalt/css_parser/parser_test.cc
@@ -38,6 +38,7 @@
 #include "cobalt/cssom/css_style_sheet.h"
 #include "cobalt/cssom/descendant_combinator.h"
 #include "cobalt/cssom/empty_pseudo_class.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/focus_pseudo_class.h"
 #include "cobalt/cssom/following_sibling_combinator.h"
 #include "cobalt/cssom/font_style_value.h"
@@ -52,6 +53,7 @@
 #include "cobalt/cssom/matrix_function.h"
 #include "cobalt/cssom/media_list.h"
 #include "cobalt/cssom/media_query.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/next_sibling_combinator.h"
 #include "cobalt/cssom/not_pseudo_class.h"
 #include "cobalt/cssom/number_value.h"
@@ -7973,7 +7975,7 @@
 
 TEST_F(ParserTest, ParsesFontFaceSrcLocalString) {
   scoped_refptr<cssom::CSSFontFaceDeclarationData> font_face =
-      parser_.ParseFontFaceDeclarationList("src: local('Roboto');",
+      parser_.ParseFontFaceDeclarationList("src: local('Roboto Regular');",
                                            source_location_);
 
   scoped_refptr<cssom::PropertyListValue> src_list =
@@ -7985,12 +7987,12 @@
       dynamic_cast<cssom::LocalSrcValue*>(
           src_list->get_item_modulo_size(0).get());
   ASSERT_TRUE(local_src);
-  EXPECT_EQ("Roboto", local_src->value());
+  EXPECT_EQ("Roboto Regular", local_src->value());
 }
 
 TEST_F(ParserTest, ParsesFontFaceSrcLocalIdentifier) {
   scoped_refptr<cssom::CSSFontFaceDeclarationData> font_face =
-      parser_.ParseFontFaceDeclarationList("src: local(Roboto);",
+      parser_.ParseFontFaceDeclarationList("src: local(Roboto Regular);",
                                            source_location_);
 
   scoped_refptr<cssom::PropertyListValue> src_list =
@@ -8002,7 +8004,7 @@
       dynamic_cast<cssom::LocalSrcValue*>(
           src_list->get_item_modulo_size(0).get());
   ASSERT_TRUE(local_src);
-  EXPECT_EQ("Roboto", local_src->value());
+  EXPECT_EQ("Roboto Regular", local_src->value());
 }
 
 TEST_F(ParserTest, ParsesFontFaceSrcUrlWithoutFormat) {
@@ -8262,5 +8264,240 @@
   EXPECT_TRUE(media_list->EvaluateConditionValue(size));
 }
 
+TEST_F(ParserTest, ParsesNoneFilter) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList("filter: none;", source_location_);
+
+  EXPECT_EQ(cssom::KeywordValue::GetNone(),
+            style->GetPropertyValue(cssom::kFilterProperty));
+}
+
+TEST_F(ParserTest, ParsesFilterWithKeywordInitial) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList("filter: initial;", source_location_);
+
+  EXPECT_EQ(cssom::KeywordValue::GetInitial(),
+            style->GetPropertyValue(cssom::kFilterProperty));
+}
+
+TEST_F(ParserTest, ParsesFilterWithKeywordInherit) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList("filter: inherit;", source_location_);
+
+  EXPECT_EQ(cssom::KeywordValue::GetInherit(),
+            style->GetPropertyValue(cssom::kFilterProperty));
+}
+
+TEST_F(ParserTest, ParsesMtmSingleUrlFilter) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(projection.msh),"
+          "                        180deg 1.5rad,"
+          "                        matrix3d(1, 0, 0, 0,"
+          "                                 0, 1, 0, 0,"
+          "                                 0, 0, 1, 0,"
+          "                                 0, 0, 0, 1));",
+          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(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) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList(
+          "filter: -cobalt-mtm(url(projection.msh)"
+          "                     640 480 url(p2.msh)"
+          "                     1920 1080 url(yeehaw.msh)"
+          "                     33 22 url(yoda.msh),"
+          "                    100deg 60deg,"
+          "                    matrix3d(1, 0, 0, 5,"
+          "                             0, 2, 0, 0,"
+          "                             6, 0, 3, 0,"
+          "                             0, 7, 0, 4));",
+          source_location_);
+
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_TRUE(filter_list);
+  const cssom::MTMFunction* mtm_function =
+      dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]);
+
+  const cssom::MTMFunction::ResolutionMatchedMeshListBuilder& meshes =
+      mtm_function->resolution_matched_meshes();
+
+  ASSERT_EQ(3, meshes.size());
+  EXPECT_EQ(640, meshes[0]->width_match());
+  EXPECT_EQ(22, meshes[2]->height_match());
+
+  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) {
+  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));",
+          source_location_);
+
+  scoped_refptr<cssom::FilterFunctionListValue> filter_list =
+      dynamic_cast<cssom::FilterFunctionListValue*>(
+          style->GetPropertyValue(cssom::kFilterProperty).get());
+
+  ASSERT_TRUE(filter_list);
+  const cssom::MTMFunction* mtm_function =
+      dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]);
+
+  const glm::mat4& actual = mtm_function->transform();
+  EXPECT_TRUE(
+      glm::all(glm::equal(glm::vec4(1.0f, 0.0f, 0.0f, 5.0f), actual[0])));
+  EXPECT_TRUE(
+      glm::all(glm::equal(glm::vec4(6.0f, 0.0f, 3.0f, 0.0f), actual[2])));
+  EXPECT_EQ(7.0f, actual[3][1]);
+  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
 }  // namespace cobalt
diff --git a/src/cobalt/css_parser/position_parse_structures.cc b/src/cobalt/css_parser/position_parse_structures.cc
index a3f3422..50c13f3 100644
--- a/src/cobalt/css_parser/position_parse_structures.cc
+++ b/src/cobalt/css_parser/position_parse_structures.cc
@@ -119,7 +119,7 @@
           break;
         }
         default:
-          NOTREACHED();
+          // Position value couldn't take more than 4 elements.
           return false;
       }
       break;
diff --git a/src/cobalt/css_parser/scanner.cc b/src/cobalt/css_parser/scanner.cc
index fadfebb..a19db4d 100644
--- a/src/cobalt/css_parser/scanner.cc
+++ b/src/cobalt/css_parser/scanner.cc
@@ -312,7 +312,8 @@
     // The input must be part of an identifier if "actual" or "expected"
     // contains '-'. Otherwise ToAsciiLowerUnchecked('\r') would be equal
     // to '-'.
-    DCHECK((*expected >= 'a' && *expected <= 'z') || *expected == '-');
+    DCHECK((*expected >= 'a' && *expected <= 'z') ||
+           (*expected >= '0' && *expected <= '9') || *expected == '-');
     DCHECK(*expected != '-' || IsCssLetter(*actual));
     if (ToAsciiLowerUnchecked(*actual++) != (*expected++)) {
       return false;
@@ -772,6 +773,15 @@
       // Cache the open brace.
       open_braces_.push('(');
 
+      if (!has_escape) {
+        Token function_token;
+        if (DetectKnownFunctionTokenAndMaybeChangeParsingMode(
+                name, &function_token)) {
+          ++input_iterator_;
+          return function_token;
+        }
+      }
+
       ++input_iterator_;
       token_value->string = name;
       return kInvalidFunctionToken;
@@ -1326,6 +1336,11 @@
         return true;
       }
       if (IsEqualToCssIdentifier(
+              name.begin, cssom::GetPropertyName(cssom::kFilterProperty))) {
+        *property_name_token = kFilterToken;
+        return true;
+      }
+      if (IsEqualToCssIdentifier(
               name.begin, cssom::GetPropertyName(cssom::kHeightProperty))) {
         *property_name_token = kHeightToken;
         return true;
@@ -2189,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:
@@ -2247,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;
@@ -2417,6 +2449,13 @@
       }
       return false;
 
+    case 8:
+      if (IsEqualToCssIdentifier(name.begin, "matrix3d")) {
+        *known_function_token = kMatrix3dFunctionToken;
+        return true;
+      }
+      return false;
+
     case 9:
       if (IsEqualToCssIdentifier(name.begin, "translate")) {
         *known_function_token = kTranslateFunctionToken;
@@ -2450,6 +2489,10 @@
         *known_function_token = kNthOfTypeFunctionToken;
         return true;
       }
+      if (IsEqualToCssIdentifier(name.begin, "-cobalt-mtm")) {
+        *known_function_token = kCobaltMtmFunctionToken;
+        return true;
+      }
       return false;
 
     case 12:
diff --git a/src/cobalt/css_parser/scanner_test.cc b/src/cobalt/css_parser/scanner_test.cc
index c1afabf..16b24f7 100644
--- a/src/cobalt/css_parser/scanner_test.cc
+++ b/src/cobalt/css_parser/scanner_test.cc
@@ -172,6 +172,17 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansInvalidFunctionWithNumber) {
+  Scanner scanner("sample-matrix4d()", &string_pool_);
+
+  ASSERT_EQ(kInvalidFunctionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ("sample-matrix4d", token_value_.string);
+
+  ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansFunctionLikeMediaAnd) {
   Scanner scanner("@media tv and(monochrome)", &string_pool_);
 
@@ -269,6 +280,15 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansIdentifierWithNumber) {
+  Scanner scanner("matrix3d5s7wss-47", &string_pool_);
+
+  ASSERT_EQ(kIdentifierToken, yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ("matrix3d5s7wss-47", token_value_.string);
+
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansDot) {
   Scanner scanner(".", &string_pool_);
 
@@ -414,6 +434,17 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansUnknownDashFunctionWithNumber) {
+  Scanner scanner("-cobalt-ma5555gic()", &string_pool_);
+
+  ASSERT_EQ(kInvalidFunctionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ("-cobalt-ma5555gic", token_value_.string);
+
+  ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansUnknownDashFunctionWithoutClosingAtEnd) {
   Scanner scanner("-cobalt-magic(", &string_pool_);
 
@@ -425,6 +456,26 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansKnownDashFunction) {
+  Scanner scanner("-cobalt-mtm()", &string_pool_);
+
+  ASSERT_EQ(kCobaltMtmFunctionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+
+  ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
+TEST_F(ScannerTest, ScansKnownDashFunctionWithoutClosingAtEnd) {
+  Scanner scanner("-cobalt-mtm(", &string_pool_);
+
+  ASSERT_EQ(kCobaltMtmFunctionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+
+  ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansMinusNPlusConstant) {
   Scanner scanner("nth-child(-n+2)", &string_pool_);
 
@@ -943,6 +994,15 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansMatrix3dFunction) {
+  Scanner scanner("matrix3d()", &string_pool_);
+
+  ASSERT_EQ(kMatrix3dFunctionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansNthOfTypeFunction) {
   Scanner scanner("nth-of-type()", &string_pool_);
 
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/complex_selector.cc b/src/cobalt/cssom/complex_selector.cc
index 5eb9047..b164c77 100644
--- a/src/cobalt/cssom/complex_selector.cc
+++ b/src/cobalt/cssom/complex_selector.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/cssom/complex_selector.h"
 
+#include "base/logging.h"
 #include "cobalt/cssom/combinator.h"
 #include "cobalt/cssom/compound_selector.h"
 #include "cobalt/cssom/selector_visitor.h"
@@ -23,6 +24,8 @@
 namespace cobalt {
 namespace cssom {
 
+const int ComplexSelector::kCombinatorLimit = 32;
+
 void ComplexSelector::Accept(SelectorVisitor* visitor) {
   visitor->VisitComplexSelector(this);
 }
@@ -39,6 +42,18 @@
     scoped_ptr<Combinator> combinator,
     scoped_ptr<CompoundSelector> compound_selector) {
   DCHECK(first_selector_);
+  DCHECK(last_selector_);
+
+  if (combinator_count_ >= kCombinatorLimit) {
+    if (!combinator_limit_exceeded_) {
+      LOG(WARNING)
+          << "Maximum number of calls to AppendCombinatorAndSelector exceeded."
+             "  Ignoring additional selectors.";
+      combinator_limit_exceeded_ = true;
+    }
+    return;
+  }
+
   specificity_.AddFrom(compound_selector->GetSpecificity());
 
   combinator->set_left_selector(last_selector_);
@@ -48,6 +63,8 @@
   last_selector_->set_right_combinator(combinator.Pass());
 
   last_selector_ = last_selector_->right_combinator()->right_selector();
+
+  combinator_count_++;
 }
 
 }  // namespace cssom
diff --git a/src/cobalt/cssom/complex_selector.h b/src/cobalt/cssom/complex_selector.h
index d8d2cae..e20ef8a 100644
--- a/src/cobalt/cssom/complex_selector.h
+++ b/src/cobalt/cssom/complex_selector.h
@@ -35,7 +35,12 @@
 //   https://www.w3.org/TR/selectors4/#complex
 class ComplexSelector : public Selector {
  public:
-  ComplexSelector() : last_selector_(NULL) {}
+  static const int kCombinatorLimit;
+
+  ComplexSelector()
+      : last_selector_(NULL),
+        combinator_count_(0),
+        combinator_limit_exceeded_(false) {}
   ~ComplexSelector() OVERRIDE {}
 
   // From Selector.
@@ -48,6 +53,10 @@
   CompoundSelector* first_selector() { return first_selector_.get(); }
   CompoundSelector* last_selector() { return last_selector_; }
 
+  int combinator_count() {
+    return combinator_count_;
+  }
+
   // For a chain of compound selectors separated by combinators, AppendSelector
   // should be first called with the left most compound selector, then
   // AppendCombinatorAndSelector should be called with each (combinator,
@@ -63,6 +72,9 @@
   scoped_ptr<CompoundSelector> first_selector_;
   Specificity specificity_;
 
+  int combinator_count_;
+  bool combinator_limit_exceeded_;
+
   DISALLOW_COPY_AND_ASSIGN(ComplexSelector);
 };
 
diff --git a/src/cobalt/cssom/computed_style.cc b/src/cobalt/cssom/computed_style.cc
index 9d5d0c9..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:
@@ -1688,6 +1715,13 @@
     absolute_url = url_value->Resolve(base_url_);
   }
 
+  if (!absolute_url.is_valid()) {
+    DLOG(WARNING) << "Invalid url: " << absolute_url.spec();
+    // No further process is needed if the url is invalid.
+    computed_background_image_ = KeywordValue::GetNone();
+    return;
+  }
+
   computed_background_image_ = new AbsoluteURLValue(absolute_url);
 }
 
@@ -1817,6 +1851,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNone:
     case KeywordValue::kNoRepeat:
@@ -1834,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:
@@ -2064,6 +2101,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNormal:
     case KeywordValue::kNoRepeat:
@@ -2080,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:
@@ -2448,6 +2488,7 @@
     case KeywordValue::kLeft:
     case KeywordValue::kLineThrough:
     case KeywordValue::kMiddle:
+    case KeywordValue::kMonoscopic:
     case KeywordValue::kMonospace:
     case KeywordValue::kNoRepeat:
     case KeywordValue::kNormal:
@@ -2464,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:
@@ -2912,6 +2955,7 @@
     case kBorderTopStyleProperty:
     case kColorProperty:
     case kContentProperty:
+    case kFilterProperty:
     case kFontFamilyProperty:
     case kFontStyleProperty:
     case kOpacityProperty:
@@ -3019,6 +3063,7 @@
     case kBoxShadowProperty:
     case kContentProperty:
     case kDisplayProperty:
+    case kFilterProperty:
     case kFontFamilyProperty:
     case kFontProperty:
     case kFontStyleProperty:
diff --git a/src/cobalt/cssom/css_computed_style_data.cc b/src/cobalt/cssom/css_computed_style_data.cc
index a5e2fcd..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);
@@ -195,6 +185,7 @@
     case kColorProperty:
     case kContentProperty:
     case kFontFamilyProperty:
+    case kFilterProperty:
     case kFontProperty:
     case kFontStyleProperty:
     case kFontWeightProperty:
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h
index e303868..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);
 
@@ -329,6 +319,13 @@
     SetPropertyValue(kDisplayProperty, display);
   }
 
+  const scoped_refptr<PropertyValue>& filter() const {
+    return GetPropertyValueReference(kFilterProperty);
+  }
+  void set_filter(const scoped_refptr<PropertyValue>& filter) {
+    SetPropertyValue(kFilterProperty, filter);
+  }
+
   const scoped_refptr<PropertyValue>& font_family() const {
     return GetPropertyValueReference(kFontFamilyProperty);
   }
diff --git a/src/cobalt/cssom/css_computed_style_data_test.cc b/src/cobalt/cssom/css_computed_style_data_test.cc
index 2fcb1ea..4aede0c 100644
--- a/src/cobalt/cssom/css_computed_style_data_test.cc
+++ b/src/cobalt/cssom/css_computed_style_data_test.cc
@@ -1191,5 +1191,22 @@
   ASSERT_TRUE(style2->DoDeclaredPropertiesMatch(style1));
 }
 
+TEST(CSSComputedStyleDataTest, FilterSettersAndGettersAreConsistent) {
+  scoped_refptr<CSSComputedStyleData> style = new CSSComputedStyleData();
+
+  EXPECT_EQ(GetPropertyInitialValue(kFilterProperty), style->filter());
+  EXPECT_EQ(style->filter(), style->GetPropertyValue(kFilterProperty));
+
+  style->set_filter(KeywordValue::GetInitial());
+  EXPECT_EQ(KeywordValue::GetInitial(), style->filter());
+  EXPECT_EQ(KeywordValue::GetInitial(),
+            style->GetPropertyValue(kFilterProperty));
+
+  style->SetPropertyValue(kFilterProperty, KeywordValue::GetInherit());
+  EXPECT_EQ(KeywordValue::GetInherit(), style->filter());
+  EXPECT_EQ(KeywordValue::GetInherit(),
+            style->GetPropertyValue(kFilterProperty));
+}
+
 }  // namespace cssom
 }  // namespace cobalt
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_data.cc b/src/cobalt/cssom/css_declared_style_data.cc
index 6768e50..80a05df 100644
--- a/src/cobalt/cssom/css_declared_style_data.cc
+++ b/src/cobalt/cssom/css_declared_style_data.cc
@@ -65,7 +65,14 @@
         ExpandShorthandProperty(key);
     for (LonghandPropertySet::const_iterator iter = longhand_properties.begin();
          iter != longhand_properties.end(); ++iter) {
-      ClearPropertyValueAndImportanceForLonghandProperty(*iter);
+      PropertyKey longhand_key = *iter;
+      if (IsShorthandProperty(longhand_key)) {
+        // If the property is another shorthand property, then recurse to clear
+        // it's registered longhand properties.
+        ClearPropertyValueAndImportance(longhand_key);
+      } else {
+        ClearPropertyValueAndImportanceForLonghandProperty(longhand_key);
+      }
     }
   } else {
     ClearPropertyValueAndImportanceForLonghandProperty(key);
diff --git a/src/cobalt/cssom/css_declared_style_data_test.cc b/src/cobalt/cssom/css_declared_style_data_test.cc
index 31388a8..0761c8d 100644
--- a/src/cobalt/cssom/css_declared_style_data_test.cc
+++ b/src/cobalt/cssom/css_declared_style_data_test.cc
@@ -696,5 +696,36 @@
   EXPECT_EQ(*style1 == *style2, false);
 }
 
+TEST(CSSDeclaredStyleDataTest, CanClearLongHandPropertyValueAndImportance) {
+  scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData();
+  style->SetPropertyValueAndImportance(kBorderLeftWidthProperty,
+                                       KeywordValue::GetInherit(), true);
+  style->ClearPropertyValueAndImportance(kBorderLeftWidthProperty);
+
+  scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData();
+  EXPECT_EQ(*style, *empty_style);
+}
+
+TEST(CSSDeclaredStyleDataTest, CanClearPropertyValueAndImportance) {
+  scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData();
+  style->SetPropertyValueAndImportance(kBorderLeftWidthProperty,
+                                       KeywordValue::GetInherit(), true);
+  style->ClearPropertyValueAndImportance(kBorderWidthProperty);
+
+  scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData();
+  EXPECT_EQ(*style, *empty_style);
+}
+
+TEST(CSSDeclaredStyleDataTest,
+     CanClearPropertyValueAndImportanceWithRecursion) {
+  scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData();
+  style->SetPropertyValueAndImportance(kBorderLeftWidthProperty,
+                                       KeywordValue::GetInherit(), true);
+  style->ClearPropertyValueAndImportance(kBorderProperty);
+
+  scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData();
+  EXPECT_EQ(*style, *empty_style);
+}
+
 }  // 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 c1d455c..2e9b046 100644
--- a/src/cobalt/cssom/css_declared_style_declaration.cc
+++ b/src/cobalt/cssom/css_declared_style_declaration.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/cssom/css_declared_style_declaration.h"
 
+#include "base/debug/trace_event.h"
 #include "base/lazy_instance.h"
 #include "cobalt/cssom/css_declared_style_data.h"
 #include "cobalt/cssom/css_parser.h"
@@ -63,6 +64,7 @@
 
 void CSSDeclaredStyleDeclaration::set_css_text(
     const std::string& css_text, script::ExceptionState* /*exception_state*/) {
+  TRACE_EVENT0("cobalt::cssom", "CSSDeclaredStyleDeclaration::set_css_text");
   DCHECK(css_parser_);
   scoped_refptr<CSSDeclaredStyleData> declaration =
       css_parser_->ParseStyleDeclarationList(
@@ -123,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_declared_style_declaration_test.cc b/src/cobalt/cssom/css_declared_style_declaration_test.cc
new file mode 100644
index 0000000..fb8d97d
--- /dev/null
+++ b/src/cobalt/cssom/css_declared_style_declaration_test.cc
@@ -0,0 +1,1525 @@
+/*
+ * 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_declared_style_data.h"
+#include "cobalt/cssom/css_declared_style_declaration.h"
+#include "cobalt/cssom/css_parser.h"
+#include "cobalt/cssom/css_style_rule.h"
+#include "cobalt/cssom/css_style_sheet.h"
+#include "cobalt/cssom/keyword_value.h"
+#include "cobalt/cssom/length_value.h"
+#include "cobalt/cssom/mutation_observer.h"
+#include "cobalt/cssom/percentage_value.h"
+#include "cobalt/cssom/property_definitions.h"
+#include "cobalt/cssom/testing/mock_css_parser.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 MockMutationObserver : public MutationObserver {
+ public:
+  MOCK_METHOD0(OnCSSMutation, void());
+};
+}  // namespace
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundSetter) {
+  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->set_background(background, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background_color = "#fff";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBackgroundColorProperty),
+                              background_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_background_color(background_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundImageSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background_image = "url('images/sample.png')";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBackgroundImageProperty),
+                              background_image, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_background_image(background_image, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundPositionSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background_position = "50% 50%";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBackgroundPositionProperty),
+                              background_position, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_background_position(background_position, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundRepeatSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background_repeat = "no-repeat";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBackgroundRepeatProperty),
+                              background_repeat, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_background_repeat(background_repeat, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BackgroundSizeSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string background_size = "cover";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBackgroundSizeProperty),
+                              background_size, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_background_size(background_size, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderProperty), border, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border(border, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_bottom = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderBottomProperty), border_bottom, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_bottom(border_bottom, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_bottom_color = "#010203";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderBottomColorProperty),
+                              border_bottom_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_bottom_color(border_bottom_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_bottom_style = "solid";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderBottomStyleProperty),
+                              border_bottom_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_bottom_style(border_bottom_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderBottomWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> width =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_bottom_width = "10px";
+  MockMutationObserver observer;
+  width->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderBottomWidthProperty),
+                              border_bottom_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  width->set_border_bottom_width(border_bottom_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_color = "#010203";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderColorProperty), border_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_color(border_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_left = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderLeftProperty), border_left, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_left(border_left, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_left_color = "#010203";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderLeftColorProperty),
+                              border_left_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_left_color(border_left_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_left_style = "solid";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderLeftStyleProperty),
+                              border_left_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_left_style(border_left_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderLeftWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> width =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_left_width = "10px";
+  MockMutationObserver observer;
+  width->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderLeftWidthProperty),
+                              border_left_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  width->set_border_left_width(border_left_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderRadiusSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_radius = "0.2em";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderRadiusProperty), border_radius, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_radius(border_radius, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_right = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderRightProperty), border_right, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_right(border_right, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_right_color = "#010203";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderRightColorProperty),
+                              border_right_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_right_color(border_right_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_right_style = "solid";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderRightStyleProperty),
+                              border_right_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_right_style(border_right_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderRightWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> width =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_right_width = "10px";
+  MockMutationObserver observer;
+  width->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderRightWidthProperty),
+                              border_right_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  width->set_border_right_width(border_right_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_style = "solid";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderStyleProperty), border_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_style(border_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_top = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBorderTopProperty), border_top, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_top(border_top, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_top_color = "#010203";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderTopColorProperty),
+                              border_top_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_top_color(border_top_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_top_style = "solid";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderTopStyleProperty),
+                              border_top_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_border_top_style(border_top_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BorderTopWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> width =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string border_top_width = "10px";
+  MockMutationObserver observer;
+  width->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBorderTopWidthProperty),
+                              border_top_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  width->set_border_top_width(border_top_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BottomSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string bottom = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kBottomProperty), bottom, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_bottom(bottom, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, BoxShadowSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string box_shadow = "none";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kBoxShadowProperty), box_shadow, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_box_shadow(box_shadow, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, ColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string color = "rgba(0, 0, 0, .8)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kColorProperty), color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_color(color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, ContentSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string content = "url(foo.png)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kContentProperty), content, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_content(content, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, DisplaySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string display = "inline-block";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kDisplayProperty), display, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_display(display, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, FontSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string font = "inherit";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kFontProperty), font, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_font(font, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, FontFamilySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string font_family = "Roboto";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kFontFamilyProperty), font_family, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_font_family(font_family, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, FontSizeSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string font_size = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kFontSizeProperty), font_size, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_font_size(font_size, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, FontStyleSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string font_style = "italic";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kFontStyleProperty), font_style, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_font_style(font_style, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, FontWeightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string font_weight = "normal";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kFontWeightProperty), font_weight, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_font_weight(font_weight, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, HeightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string height = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kHeightProperty), height, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_height(height, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, LeftSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string left = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kLeftProperty), left, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_left(left, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, LineHeightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string line_height = "1.5em";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kLineHeightProperty), line_height, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_line_height(line_height, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MarginSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string margin = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kMarginProperty), margin, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_margin(margin, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MarginBottomSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string margin_bottom = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMarginBottomProperty), margin_bottom, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_margin_bottom(margin_bottom, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MarginLeftSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string margin_left = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMarginLeftProperty), margin_left, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_margin_left(margin_left, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MarginRightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string margin_right = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMarginRightProperty), margin_right, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_margin_right(margin_right, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MarginTopSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string margin_top = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMarginTopProperty), margin_top, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_margin_top(margin_top, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MaxHeightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string max_height = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMaxHeightProperty), max_height, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_max_height(max_height, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MaxWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string max_width = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMaxWidthProperty), max_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_max_width(max_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MinHeightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string min_height = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMinHeightProperty), min_height, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_min_height(min_height, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, MinWidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string min_width = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kMinWidthProperty), min_width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_min_width(min_width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, OpacitySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string opacity = "0.5";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kOpacityProperty), opacity, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_opacity(opacity, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, OverflowSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string overflow = "visible";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kOverflowProperty), overflow, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_overflow(overflow, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, OverflowWrapSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string overflow_wrap = "break-word";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kOverflowWrapProperty), overflow_wrap, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_overflow_wrap(overflow_wrap, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PaddingSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string padding = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kPaddingProperty), padding, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_padding(padding, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PaddingBottomSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string padding_bottom = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kPaddingBottomProperty),
+                              padding_bottom, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_padding_bottom(padding_bottom, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PaddingLeftSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string padding_left = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kPaddingLeftProperty), padding_left, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_padding_left(padding_left, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PaddingRightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string padding_right = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kPaddingRightProperty), padding_right, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_padding_right(padding_right, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PaddingTopSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string padding_top = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kPaddingTopProperty), padding_top, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_padding_top(padding_top, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, PositionSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string position = "static";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kPositionProperty), position, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_position(position, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, RightSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string right = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kRightProperty), right, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_right(right, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextAlignSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_align = "center";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTextAlignProperty), text_align, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_text_align(text_align, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextDecorationColorSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_decoration_color = "rgba(0, 0, 0, .8)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTextDecorationColorProperty),
+                              text_decoration_color, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_decoration_color(text_decoration_color, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextDecorationLineSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_decoration_line = "line-through";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTextDecorationLineProperty),
+                              text_decoration_line, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_decoration_line(text_decoration_line, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextIndentSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_indent = "4em";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTextIndentProperty), text_indent, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_indent(text_indent, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextOverflowSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_overflow = "ellipsis";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTextOverflowProperty), text_overflow, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_overflow(text_overflow, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextShadowSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_shadow = "inherit";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTextShadowProperty), text_shadow, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_shadow(text_shadow, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TextTransformSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string text_transform = "uppercase";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTextTransformProperty),
+                              text_transform, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_text_transform(text_transform, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TopSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string top = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTopProperty), top, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_top(top, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransformSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transform = "scale(1.5)";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTransformProperty), transform, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transform(transform, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransformOriginSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transform_origin = "20% 40%";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTransformOriginProperty),
+                              transform_origin, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transform_origin(transform_origin, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransitionSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transition = "inherit";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTransitionProperty), transition, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transition(transition, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransitionDelaySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transition_delay = "2s";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTransitionDelayProperty),
+                              transition_delay, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transition_delay(transition_delay, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransitionDurationSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transition_duration = "2s";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTransitionDurationProperty),
+                              transition_duration, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transition_duration(transition_duration, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransitionPropertySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transition_property = "width";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kTransitionPropertyProperty),
+                              transition_property, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transition_property(transition_property, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, TransitionTimingFunctionSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string transition_timing_function = "linear";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kTransitionTimingFunctionProperty),
+                  transition_timing_function, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_transition_timing_function(transition_timing_function, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, VerticalAlignSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string vertical_align = "middle";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kVerticalAlignProperty),
+                              vertical_align, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_vertical_align(vertical_align, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, VisibilitySetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string visibility = "hidden";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kVisibilityProperty), visibility, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_visibility(visibility, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, WhiteSpaceSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string white_space = "pre";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kWhiteSpaceProperty), white_space, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_white_space(white_space, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, WidthSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string width = "100px";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kWidthProperty), width, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_width(width, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, WordWrapSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string word_wrap = "break-word";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser,
+              ParsePropertyIntoDeclarationData(
+                  GetPropertyName(kWordWrapProperty), word_wrap, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_word_wrap(word_wrap, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, ZIndexSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string z_index = "-1";
+  MockMutationObserver observer;
+  style->set_mutation_observer(&observer);
+
+  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
+                              GetPropertyName(kZIndexProperty), z_index, _, _));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+
+  style->set_z_index(z_index, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  const std::string css_text = "font-size: 100px; color: #0047ab;";
+
+  EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _))
+      .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>()));
+  style->set_css_text(css_text, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetterEmptyString) {
+  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);
+
+  const std::string css_text = "";
+
+  EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _))
+      .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>()));
+  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
+  style->set_css_text(css_text, NULL);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, CSSTextGetter) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<PercentageValue> background_size = new PercentageValue(0.50f);
+  scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit);
+
+  scoped_refptr<CSSDeclaredStyleData> style_data = new CSSDeclaredStyleData();
+  style_data->SetPropertyValueAndImportance(kBackgroundSizeProperty,
+                                            background_size, false);
+  style_data->SetPropertyValueAndImportance(kBottomProperty, bottom, true);
+
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(style_data, &css_parser);
+  EXPECT_EQ(style->css_text(NULL),
+            "background-size: 50%; bottom: 16px !important;");
+}
+
+// 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(CSSDeclaredStyleDeclarationTest, PropertyValueSetter) {
+  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, 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();
+  initial_style->SetPropertyValueAndImportance(
+      kTextAlignProperty, KeywordValue::GetCenter(), false);
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
+
+  EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)),
+            "center");
+}
+
+TEST(CSSDeclaredStyleDeclarationTest,
+     UnknownDeclaredStylePropertyValueShouldBeEmpty) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleData> initial_style =
+      new CSSDeclaredStyleData();
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
+
+  EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), "");
+}
+
+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);
+
+  EXPECT_EQ(style->length(), 0);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, LengthAttributeGetterNotEmpty) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleData> initial_style =
+      new CSSDeclaredStyleData();
+  initial_style->SetPropertyValueAndImportance(
+      kDisplayProperty, KeywordValue::GetInline(), false);
+  initial_style->SetPropertyValueAndImportance(
+      kTextAlignProperty, KeywordValue::GetCenter(), false);
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
+
+  EXPECT_EQ(style->length(), 2);
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, ItemGetterEmpty) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(&css_parser);
+
+  EXPECT_FALSE(style->Item(0));
+}
+
+TEST(CSSDeclaredStyleDeclarationTest, ItemGetterNotEmpty) {
+  testing::MockCSSParser css_parser;
+  scoped_refptr<CSSDeclaredStyleData> initial_style =
+      new CSSDeclaredStyleData();
+  initial_style->SetPropertyValueAndImportance(
+      kDisplayProperty, KeywordValue::GetInline(), false);
+  initial_style->SetPropertyValueAndImportance(
+      kTextAlignProperty, KeywordValue::GetCenter(), false);
+  scoped_refptr<CSSDeclaredStyleDeclaration> style =
+      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
+
+  EXPECT_TRUE(style->Item(0));
+  EXPECT_TRUE(style->Item(1));
+  EXPECT_FALSE(style->Item(2));
+
+  // 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 {
+    EXPECT_EQ(style->Item(0).value(), GetPropertyName(kTextAlignProperty));
+    EXPECT_EQ(style->Item(1).value(), GetPropertyName(kDisplayProperty));
+  }
+}
+
+}  // namespace cssom
+}  // namespace cobalt
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/css_style_declaration_test.cc b/src/cobalt/cssom/css_style_declaration_test.cc
deleted file mode 100644
index 0e71d24..0000000
--- a/src/cobalt/cssom/css_style_declaration_test.cc
+++ /dev/null
@@ -1,1455 +0,0 @@
-/*
- * Copyright 2014 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_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"
-#include "cobalt/cssom/css_style_rule.h"
-#include "cobalt/cssom/css_style_sheet.h"
-#include "cobalt/cssom/keyword_value.h"
-#include "cobalt/cssom/length_value.h"
-#include "cobalt/cssom/mutation_observer.h"
-#include "cobalt/cssom/percentage_value.h"
-#include "cobalt/cssom/property_definitions.h"
-#include "cobalt/cssom/testing/mock_css_parser.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;
-
-class MockMutationObserver : public MutationObserver {
- public:
-  MOCK_METHOD0(OnCSSMutation, void());
-};
-
-TEST(CSSStyleDeclarationTest, BackgroundSetter) {
-  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->set_background(background, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BackgroundColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string background_color = "#fff";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBackgroundColorProperty),
-                              background_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_background_color(background_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BackgroundImageSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string background_image = "url('images/sample.png')";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBackgroundImageProperty),
-                              background_image, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_background_image(background_image, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BackgroundPositionSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string background_position = "50% 50%";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBackgroundPositionProperty),
-                              background_position, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_background_position(background_position, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BackgroundRepeatSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string background_repeat = "no-repeat";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBackgroundRepeatProperty),
-                              background_repeat, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_background_repeat(background_repeat, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BackgroundSizeSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string background_size = "cover";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBackgroundSizeProperty),
-                              background_size, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_background_size(background_size, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderProperty), border, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border(border, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderBottomSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_bottom = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderBottomProperty), border_bottom, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_bottom(border_bottom, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderBottomColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_bottom_color = "#010203";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderBottomColorProperty),
-                              border_bottom_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_bottom_color(border_bottom_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderBottomStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_bottom_style = "solid";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderBottomStyleProperty),
-                              border_bottom_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_bottom_style(border_bottom_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderBottomWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> width =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_bottom_width = "10px";
-  MockMutationObserver observer;
-  width->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderBottomWidthProperty),
-                              border_bottom_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  width->set_border_bottom_width(border_bottom_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_color = "#010203";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderColorProperty), border_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_color(border_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderLeftSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_left = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderLeftProperty), border_left, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_left(border_left, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderLeftColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_left_color = "#010203";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderLeftColorProperty),
-                              border_left_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_left_color(border_left_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderLeftStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_left_style = "solid";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderLeftStyleProperty),
-                              border_left_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_left_style(border_left_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderLeftWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> width =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_left_width = "10px";
-  MockMutationObserver observer;
-  width->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderLeftWidthProperty),
-                              border_left_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  width->set_border_left_width(border_left_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderRadiusSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_radius = "0.2em";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderRadiusProperty), border_radius, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_radius(border_radius, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderRightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_right = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderRightProperty), border_right, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_right(border_right, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderRightColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_right_color = "#010203";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderRightColorProperty),
-                              border_right_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_right_color(border_right_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderRightStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_right_style = "solid";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderRightStyleProperty),
-                              border_right_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_right_style(border_right_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderRightWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> width =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_right_width = "10px";
-  MockMutationObserver observer;
-  width->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderRightWidthProperty),
-                              border_right_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  width->set_border_right_width(border_right_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_style = "solid";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderStyleProperty), border_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_style(border_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderTopSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_top = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBorderTopProperty), border_top, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_top(border_top, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderTopColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_top_color = "#010203";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderTopColorProperty),
-                              border_top_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_top_color(border_top_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderTopStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_top_style = "solid";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderTopStyleProperty),
-                              border_top_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_border_top_style(border_top_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BorderTopWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> width =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string border_top_width = "10px";
-  MockMutationObserver observer;
-  width->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBorderTopWidthProperty),
-                              border_top_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  width->set_border_top_width(border_top_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BottomSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string bottom = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kBottomProperty), bottom, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_bottom(bottom, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, BoxShadowSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string box_shadow = "none";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kBoxShadowProperty), box_shadow, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_box_shadow(box_shadow, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, ColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string color = "rgba(0, 0, 0, .8)";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kColorProperty), color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_color(color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, ContentSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string content = "url(foo.png)";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kContentProperty), content, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_content(content, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, DisplaySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string display = "inline-block";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kDisplayProperty), display, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_display(display, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, FontSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string font = "inherit";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kFontProperty), font, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_font(font, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, FontFamilySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string font_family = "Roboto";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kFontFamilyProperty), font_family, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_font_family(font_family, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, FontSizeSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string font_size = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kFontSizeProperty), font_size, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_font_size(font_size, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, FontStyleSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string font_style = "italic";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kFontStyleProperty), font_style, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_font_style(font_style, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, FontWeightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string font_weight = "normal";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kFontWeightProperty), font_weight, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_font_weight(font_weight, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, HeightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string height = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kHeightProperty), height, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_height(height, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, LeftSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string left = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kLeftProperty), left, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_left(left, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, LineHeightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string line_height = "1.5em";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kLineHeightProperty), line_height, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_line_height(line_height, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MarginSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string margin = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kMarginProperty), margin, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_margin(margin, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MarginBottomSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string margin_bottom = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMarginBottomProperty), margin_bottom, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_margin_bottom(margin_bottom, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MarginLeftSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string margin_left = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMarginLeftProperty), margin_left, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_margin_left(margin_left, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MarginRightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string margin_right = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMarginRightProperty), margin_right, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_margin_right(margin_right, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MarginTopSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string margin_top = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMarginTopProperty), margin_top, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_margin_top(margin_top, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MaxHeightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string max_height = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMaxHeightProperty), max_height, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_max_height(max_height, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MaxWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string max_width = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMaxWidthProperty), max_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_max_width(max_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MinHeightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string min_height = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMinHeightProperty), min_height, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_min_height(min_height, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, MinWidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string min_width = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kMinWidthProperty), min_width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_min_width(min_width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, OpacitySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string opacity = "0.5";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kOpacityProperty), opacity, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_opacity(opacity, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, OverflowSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string overflow = "visible";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kOverflowProperty), overflow, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_overflow(overflow, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, OverflowWrapSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string overflow_wrap = "break-word";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kOverflowWrapProperty), overflow_wrap, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_overflow_wrap(overflow_wrap, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PaddingSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string padding = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kPaddingProperty), padding, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_padding(padding, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PaddingBottomSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string padding_bottom = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kPaddingBottomProperty),
-                              padding_bottom, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_padding_bottom(padding_bottom, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PaddingLeftSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string padding_left = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kPaddingLeftProperty), padding_left, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_padding_left(padding_left, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PaddingRightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string padding_right = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kPaddingRightProperty), padding_right, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_padding_right(padding_right, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PaddingTopSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string padding_top = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kPaddingTopProperty), padding_top, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_padding_top(padding_top, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, PositionSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string position = "static";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kPositionProperty), position, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_position(position, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, RightSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string right = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kRightProperty), right, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_right(right, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextAlignSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_align = "center";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTextAlignProperty), text_align, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_text_align(text_align, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextDecorationColorSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_decoration_color = "rgba(0, 0, 0, .8)";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTextDecorationColorProperty),
-                              text_decoration_color, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_decoration_color(text_decoration_color, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextDecorationLineSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_decoration_line = "line-through";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTextDecorationLineProperty),
-                              text_decoration_line, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_decoration_line(text_decoration_line, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextIndentSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_indent = "4em";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTextIndentProperty), text_indent, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_indent(text_indent, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextOverflowSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_overflow = "ellipsis";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTextOverflowProperty), text_overflow, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_overflow(text_overflow, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextShadowSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_shadow = "inherit";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTextShadowProperty), text_shadow, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_shadow(text_shadow, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TextTransformSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string text_transform = "uppercase";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTextTransformProperty),
-                              text_transform, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_text_transform(text_transform, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TopSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string top = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTopProperty), top, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_top(top, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransformSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transform = "scale(1.5)";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTransformProperty), transform, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transform(transform, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransformOriginSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transform_origin = "20% 40%";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTransformOriginProperty),
-                              transform_origin, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transform_origin(transform_origin, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransitionSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transition = "inherit";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTransitionProperty), transition, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transition(transition, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransitionDelaySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transition_delay = "2s";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTransitionDelayProperty),
-                              transition_delay, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transition_delay(transition_delay, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransitionDurationSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transition_duration = "2s";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTransitionDurationProperty),
-                              transition_duration, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transition_duration(transition_duration, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransitionPropertySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transition_property = "width";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kTransitionPropertyProperty),
-                              transition_property, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transition_property(transition_property, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, TransitionTimingFunctionSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string transition_timing_function = "linear";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kTransitionTimingFunctionProperty),
-                  transition_timing_function, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_transition_timing_function(transition_timing_function, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, VerticalAlignSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string vertical_align = "middle";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kVerticalAlignProperty),
-                              vertical_align, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_vertical_align(vertical_align, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, VisibilitySetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string visibility = "hidden";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kVisibilityProperty), visibility, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_visibility(visibility, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, WhiteSpaceSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string white_space = "pre";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kWhiteSpaceProperty), white_space, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_white_space(white_space, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, WidthSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string width = "100px";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kWidthProperty), width, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_width(width, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, WordWrapSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string word_wrap = "break-word";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser,
-              ParsePropertyIntoDeclarationData(
-                  GetPropertyName(kWordWrapProperty), word_wrap, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_word_wrap(word_wrap, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, ZIndexSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string z_index = "-1";
-  MockMutationObserver observer;
-  style->set_mutation_observer(&observer);
-
-  EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData(
-                              GetPropertyName(kZIndexProperty), z_index, _, _));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-
-  style->set_z_index(z_index, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, CSSTextSetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  const std::string css_text = "font-size: 100px; color: #0047ab;";
-
-  EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _))
-      .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>()));
-  style->set_css_text(css_text, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, CSSTextSetterEmptyString) {
-  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);
-
-  const std::string css_text = "";
-
-  EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _))
-      .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>()));
-  EXPECT_CALL(observer, OnCSSMutation()).Times(1);
-  style->set_css_text(css_text, NULL);
-}
-
-TEST(CSSStyleDeclarationTest, CSSTextGetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<PercentageValue> background_size = new PercentageValue(0.50f);
-  scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit);
-
-  scoped_refptr<CSSDeclaredStyleData> style_data = new CSSDeclaredStyleData();
-  style_data->SetPropertyValueAndImportance(kBackgroundSizeProperty,
-                                            background_size, false);
-  style_data->SetPropertyValueAndImportance(kBottomProperty, bottom, true);
-
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(style_data, &css_parser);
-  EXPECT_EQ(style->css_text(NULL),
-            "background-size: 50%; bottom: 16px !important;");
-}
-
-// 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) {
-  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(CSSStyleDeclarationTest, PropertyValueGetter) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleData> initial_style =
-      new CSSDeclaredStyleData();
-  initial_style->SetPropertyValueAndImportance(
-      kTextAlignProperty, KeywordValue::GetCenter(), false);
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
-
-  EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)),
-            "center");
-}
-
-TEST(CSSStyleDeclarationTest, UnknownDeclaredStylePropertyValueShouldBeEmpty) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleData> initial_style =
-      new CSSDeclaredStyleData();
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
-
-  EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), "");
-}
-
-TEST(CSSStyleDeclarationTest, LengthAttributeGetterEmpty) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  EXPECT_EQ(style->length(), 0);
-}
-
-TEST(CSSStyleDeclarationTest, LengthAttributeGetterNotEmpty) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleData> initial_style =
-      new CSSDeclaredStyleData();
-  initial_style->SetPropertyValueAndImportance(
-      kDisplayProperty, KeywordValue::GetInline(), false);
-  initial_style->SetPropertyValueAndImportance(
-      kTextAlignProperty, KeywordValue::GetCenter(), false);
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
-
-  EXPECT_EQ(style->length(), 2);
-}
-
-TEST(CSSStyleDeclarationTest, ItemGetterEmpty) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(&css_parser);
-
-  EXPECT_FALSE(style->Item(0));
-}
-
-TEST(CSSStyleDeclarationTest, ItemGetterNotEmpty) {
-  testing::MockCSSParser css_parser;
-  scoped_refptr<CSSDeclaredStyleData> initial_style =
-      new CSSDeclaredStyleData();
-  initial_style->SetPropertyValueAndImportance(
-      kDisplayProperty, KeywordValue::GetInline(), false);
-  initial_style->SetPropertyValueAndImportance(
-      kTextAlignProperty, KeywordValue::GetCenter(), false);
-  scoped_refptr<CSSDeclaredStyleDeclaration> style =
-      new CSSDeclaredStyleDeclaration(initial_style, &css_parser);
-
-  EXPECT_TRUE(style->Item(0));
-  EXPECT_TRUE(style->Item(1));
-  EXPECT_FALSE(style->Item(2));
-
-  // The order is not important, as long as with properties are represented.
-  if (style->Item(0).value() == GetPropertyName(kDisplayProperty)) {
-    EXPECT_EQ(style->Item(1).value(), GetPropertyName(kTextAlignProperty));
-  } else {
-    EXPECT_EQ(style->Item(0).value(), GetPropertyName(kTextAlignProperty));
-    EXPECT_EQ(style->Item(1).value(), GetPropertyName(kDisplayProperty));
-  }
-}
-
-}  // namespace cssom
-}  // namespace cobalt
diff --git a/src/cobalt/cssom/cssom.gyp b/src/cobalt/cssom/cssom.gyp
index 63be9ea..a3af6c4 100644
--- a/src/cobalt/cssom/cssom.gyp
+++ b/src/cobalt/cssom/cssom.gyp
@@ -100,6 +100,9 @@
         'descendant_combinator.h',
         'empty_pseudo_class.cc',
         'empty_pseudo_class.h',
+        'filter_function.h',
+        'filter_function_list_value.cc',
+        'filter_function_list_value.h',
         'focus_pseudo_class.cc',
         'focus_pseudo_class.h',
         'following_sibling_combinator.cc',
@@ -143,6 +146,8 @@
         'media_query.h',
         'media_type_names.cc',
         'media_type_names.h',
+        'mtm_function.cc',
+        'mtm_function.h',
         'mutation_observer.h',
         'next_sibling_combinator.cc',
         'next_sibling_combinator.h',
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/filter_function.h b/src/cobalt/cssom/filter_function.h
new file mode 100644
index 0000000..c3053c0
--- /dev/null
+++ b/src/cobalt/cssom/filter_function.h
@@ -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.
+ */
+
+#ifndef COBALT_CSSOM_FILTER_FUNCTION_H_
+#define COBALT_CSSOM_FILTER_FUNCTION_H_
+
+#include <string>
+
+#include "cobalt/base/polymorphic_equatable.h"
+
+namespace cobalt {
+namespace cssom {
+
+// A base class for filter functions. Filters manipulate an element's rendering.
+//   https://www.w3.org/TR/filter-effects-1/
+class FilterFunction : public base::PolymorphicEquatable {
+ public:
+  virtual std::string ToString() const = 0;
+
+  virtual ~FilterFunction() {}
+};
+
+}  // namespace cssom
+}  // namespace cobalt
+
+#endif  // COBALT_CSSOM_FILTER_FUNCTION_H_
diff --git a/src/cobalt/cssom/filter_function_list_value.cc b/src/cobalt/cssom/filter_function_list_value.cc
new file mode 100644
index 0000000..443832b
--- /dev/null
+++ b/src/cobalt/cssom/filter_function_list_value.cc
@@ -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.
+ */
+
+#include "cobalt/cssom/filter_function_list_value.h"
+
+namespace cobalt {
+namespace cssom {
+
+std::string FilterFunctionListValue::ToString() const {
+  std::string result;
+  for (size_t i = 0; i < value().size(); ++i) {
+    if (!result.empty()) result.append(" ");
+    result.append(value()[i]->ToString());
+  }
+  return result;
+}
+
+}  // namespace cssom
+}  // namespace cobalt
diff --git a/src/cobalt/cssom/filter_function_list_value.h b/src/cobalt/cssom/filter_function_list_value.h
new file mode 100644
index 0000000..c1f137b
--- /dev/null
+++ b/src/cobalt/cssom/filter_function_list_value.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_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_
+#define COBALT_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "cobalt/base/polymorphic_equatable.h"
+#include "cobalt/cssom/filter_function.h"
+#include "cobalt/cssom/property_value_visitor.h"
+#include "cobalt/cssom/scoped_list_value.h"
+
+namespace cobalt {
+namespace cssom {
+
+// A list of composed filter functions.
+class FilterFunctionListValue : public ScopedListValue<FilterFunction> {
+ public:
+  explicit FilterFunctionListValue(
+      ScopedListValue<FilterFunction>::Builder value)
+      : ScopedListValue(value.Pass()) {}
+
+  void Accept(PropertyValueVisitor* visitor) OVERRIDE {
+    visitor->VisitFilterFunctionList(this);
+  }
+
+  std::string ToString() const OVERRIDE;
+
+  DEFINE_POLYMORPHIC_EQUATABLE_TYPE(FilterFunctionListValue);
+
+ private:
+  virtual ~FilterFunctionListValue() OVERRIDE {}
+};
+
+}  // namespace cssom
+}  // namespace cobalt
+
+#endif  // COBALT_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_
diff --git a/src/cobalt/cssom/interpolate_property_value.cc b/src/cobalt/cssom/interpolate_property_value.cc
index 061332d..d9bea8b 100644
--- a/src/cobalt/cssom/interpolate_property_value.cc
+++ b/src/cobalt/cssom/interpolate_property_value.cc
@@ -70,6 +70,8 @@
 
   void VisitAbsoluteURL(AbsoluteURLValue* start_absolute_url_value) OVERRIDE;
   void VisitCalc(CalcValue* start_calc_value) OVERRIDE;
+  void VisitFilterFunctionList(
+      FilterFunctionListValue* start_filter_function_list_value) OVERRIDE;
   void VisitFontStyle(FontStyleValue* start_font_style_value) OVERRIDE;
   void VisitFontWeight(FontWeightValue* start_font_weight_value) OVERRIDE;
   void VisitInteger(IntegerValue* integer_value) OVERRIDE;
@@ -356,6 +358,12 @@
   interpolated_value_ = end_value_;
 }
 
+void InterpolateVisitor::VisitFilterFunctionList(
+    FilterFunctionListValue* /*start_filter_function_list_value*/) {
+  NOTIMPLEMENTED();
+  interpolated_value_ = end_value_;
+}
+
 void InterpolateVisitor::VisitFontStyle(
     FontStyleValue* /*start_font_style_value*/) {
   NOTIMPLEMENTED();
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
new file mode 100644
index 0000000..661060b
--- /dev/null
+++ b/src/cobalt/cssom/mtm_function.cc
@@ -0,0 +1,152 @@
+/*
+ * 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/mtm_function.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "cobalt/cssom/filter_function_list_value.h"
+#include "cobalt/cssom/keyword_value.h"
+#include "cobalt/cssom/property_value_visitor.h"
+
+namespace cobalt {
+namespace cssom {
+
+MTMFunction::MTMFunction(
+    const scoped_refptr<PropertyValue>& mesh_url,
+    ResolutionMatchedMeshListBuilder resolution_matched_meshes,
+    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),
+      stereo_mode_(stereo_mode) {
+  DCHECK(mesh_url_);
+  DCHECK(stereo_mode_);
+}
+
+std::string MTMFunction::ToString() const {
+  std::string result = "-cobalt-mtm(";
+
+  result.append(mesh_url()->ToString());
+
+  const ResolutionMatchedMeshListBuilder& meshes = resolution_matched_meshes();
+  for (size_t mesh_index = 0; mesh_index < meshes.size(); ++mesh_index) {
+    result.push_back(' ');
+    result.append(base::IntToString(meshes[mesh_index]->width_match()));
+    result.push_back(' ');
+    result.append(base::IntToString(meshes[mesh_index]->height_match()));
+    result.push_back(' ');
+    result.append(meshes[mesh_index]->mesh_url()->ToString());
+  }
+
+  result.append(base::StringPrintf(", %.7grad", horizontal_fov()));
+  result.append(base::StringPrintf(" %.7grad, ", vertical_fov()));
+
+  result.append("matrix3d(");
+  for (int col = 0; col <= 3; ++col) {
+    for (int row = 0; row <= 3; ++row) {
+      if (col > 0 || row > 0) {
+        result.append(", ");
+      }
+      result.append(base::StringPrintf("%.7g", transform()[col][row]));
+    }
+  }
+
+  result.append("), ");
+  result.append(stereo_mode()->ToString());
+  result.append(")");
+
+  return result;
+}
+
+MTMFunction::ResolutionMatchedMesh::ResolutionMatchedMesh(
+    int width_match, int height_match,
+    const scoped_refptr<PropertyValue>& mesh_url)
+    : width_match_(width_match),
+      height_match_(height_match),
+      mesh_url_(mesh_url) {
+  DCHECK(mesh_url_);
+}
+
+bool MTMFunction::ResolutionMatchedMesh::operator==(
+    const MTMFunction::ResolutionMatchedMesh& rhs) const {
+  return mesh_url()->Equals(*rhs.mesh_url()) &&
+         width_match() == rhs.width_match() &&
+         height_match() == rhs.height_match();
+}
+
+bool MTMFunction::ResolutionMatchedMesh::operator!=(
+    const MTMFunction::ResolutionMatchedMesh& rhs) const {
+  return !(*this == rhs);
+}
+
+const MTMFunction* MTMFunction::ExtractFromFilterList(
+    PropertyValue* filter_value) {
+  if (filter_value && filter_value != cssom::KeywordValue::GetNone()) {
+    const cssom::FilterFunctionListValue::Builder& filter_list =
+        base::polymorphic_downcast<cssom::FilterFunctionListValue*>(
+            filter_value)
+            ->value();
+
+    for (size_t list_index = 0; list_index < filter_list.size(); ++list_index) {
+      if (filter_list[list_index]->GetTypeId() ==
+          base::GetTypeId<cssom::MTMFunction>()) {
+        return base::polymorphic_downcast<const cssom::MTMFunction*>(
+            filter_list[list_index]);
+      }
+    }
+  }
+
+  return NULL;
+}
+
+bool MTMFunction::operator==(const MTMFunction& rhs) const {
+  if (!mesh_url()->Equals(*rhs.mesh_url()) ||
+      horizontal_fov() != rhs.horizontal_fov() ||
+      horizontal_fov() != rhs.horizontal_fov() ||
+      !stereo_mode()->Equals(*rhs.stereo_mode())) {
+    return false;
+  }
+  const ResolutionMatchedMeshListBuilder& lhs_meshes =
+      resolution_matched_meshes();
+  const ResolutionMatchedMeshListBuilder& rhs_meshes =
+      rhs.resolution_matched_meshes();
+
+  if (lhs_meshes.size() != rhs_meshes.size()) {
+    return false;
+  }
+
+  for (size_t i = 0; i < lhs_meshes.size(); ++i) {
+    if (*lhs_meshes[i] != *rhs_meshes[i]) {
+      return false;
+    }
+  }
+
+  for (int col = 0; col <= 3; ++col) {
+    if (!glm::all(glm::equal(transform()[col], rhs.transform()[col]))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace cssom
+}  // namespace cobalt
diff --git a/src/cobalt/cssom/mtm_function.h b/src/cobalt/cssom/mtm_function.h
new file mode 100644
index 0000000..764c0cb
--- /dev/null
+++ b/src/cobalt/cssom/mtm_function.h
@@ -0,0 +1,99 @@
+/*
+ * 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_CSSOM_MTM_FUNCTION_H_
+#define COBALT_CSSOM_MTM_FUNCTION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#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"
+
+namespace cobalt {
+namespace cssom {
+
+// Represent an MTM filter.
+class MTMFunction : public FilterFunction {
+ public:
+  // A resolution-matched mesh specifier.
+  class ResolutionMatchedMesh {
+   public:
+    ResolutionMatchedMesh(int width_match, int height_match,
+                          const scoped_refptr<PropertyValue>& mesh_url);
+    int width_match() const { return width_match_; }
+    int height_match() const { return height_match_; }
+    const scoped_refptr<PropertyValue>& mesh_url() const { return mesh_url_; }
+    bool operator==(const MTMFunction::ResolutionMatchedMesh& rhs) const;
+    bool operator!=(const MTMFunction::ResolutionMatchedMesh& rhs) const;
+
+   private:
+    const int width_match_;
+    const int height_match_;
+    const scoped_refptr<PropertyValue> mesh_url_;
+  };
+  typedef ScopedVector<ResolutionMatchedMesh> ResolutionMatchedMeshListBuilder;
+
+  MTMFunction(const scoped_refptr<PropertyValue>& mesh_url,
+              ResolutionMatchedMeshListBuilder resolution_matched_meshes,
+              float horizontal_fov, float vertical_fov,
+              const glm::mat4& transform,
+              const scoped_refptr<KeywordValue>& stereo_mode);
+
+  ~MTMFunction() OVERRIDE {}
+
+  const scoped_refptr<PropertyValue>& mesh_url() const { return mesh_url_; }
+  const ResolutionMatchedMeshListBuilder& resolution_matched_meshes() const {
+    return resolution_matched_meshes_;
+  }
+
+  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;
+
+  bool operator==(const MTMFunction& rhs) const;
+
+  static const MTMFunction* ExtractFromFilterList(PropertyValue* filter_list);
+
+  DEFINE_POLYMORPHIC_EQUATABLE_TYPE(MTMFunction);
+
+ private:
+  const scoped_refptr<PropertyValue> mesh_url_;
+  const ResolutionMatchedMeshListBuilder resolution_matched_meshes_;
+  const float horizontal_fov_;
+  const float vertical_fov_;
+  const glm::mat4 transform_;
+  const scoped_refptr<KeywordValue> stereo_mode_;
+
+  DISALLOW_COPY_AND_ASSIGN(MTMFunction);
+};
+
+}  // namespace cssom
+}  // namespace cobalt
+
+#endif  // COBALT_CSSOM_MTM_FUNCTION_H_
diff --git a/src/cobalt/cssom/property_definitions.cc b/src/cobalt/cssom/property_definitions.cc
index 8133992..1bcdb2a 100644
--- a/src/cobalt/cssom/property_definitions.cc
+++ b/src/cobalt/cssom/property_definitions.cc
@@ -386,6 +386,12 @@
                         kImpactsBoxCrossReferencesNo,
                         KeywordValue::GetInline());
 
+  // https://www.w3.org/TR/filter-effects-1/#FilterProperty
+  SetPropertyDefinition(kFilterProperty, "filter", kInheritedNo, kAnimatableNo,
+                        kImpactsChildDeclaredStyleNo, kImpactsBoxGenerationNo,
+                        kImpactsBoxSizesNo, kImpactsBoxCrossReferencesNo,
+                        KeywordValue::GetNone());
+
   // Varies by platform in Chromium, Roboto in Cobalt.
   //   https://www.w3.org/TR/css3-fonts/#font-family-prop
   SetPropertyDefinition(
@@ -1003,6 +1009,10 @@
         return kBottomProperty;
       }
       if (LowerCaseEqualsASCII(property_name,
+                               GetPropertyName(kFilterProperty))) {
+        return kFilterProperty;
+      }
+      if (LowerCaseEqualsASCII(property_name,
                                GetPropertyName(kHeightProperty))) {
         return kHeightProperty;
       }
diff --git a/src/cobalt/cssom/property_definitions.h b/src/cobalt/cssom/property_definitions.h
index 14a1c39..f4c9333 100644
--- a/src/cobalt/cssom/property_definitions.h
+++ b/src/cobalt/cssom/property_definitions.h
@@ -72,6 +72,7 @@
   kColorProperty,
   kContentProperty,
   kDisplayProperty,
+  kFilterProperty,
   kFontFamilyProperty,
   kFontSizeProperty,
   kFontStyleProperty,
diff --git a/src/cobalt/cssom/property_value_is_equal_test.cc b/src/cobalt/cssom/property_value_is_equal_test.cc
index b2f4f88..263634b 100644
--- a/src/cobalt/cssom/property_value_is_equal_test.cc
+++ b/src/cobalt/cssom/property_value_is_equal_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "cobalt/cssom/absolute_url_value.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/font_style_value.h"
 #include "cobalt/cssom/font_weight_value.h"
 #include "cobalt/cssom/integer_value.h"
@@ -23,6 +24,7 @@
 #include "cobalt/cssom/linear_gradient_value.h"
 #include "cobalt/cssom/matrix_function.h"
 #include "cobalt/cssom/media_feature_keyword_value.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/number_value.h"
 #include "cobalt/cssom/percentage_value.h"
 #include "cobalt/cssom/property_definitions.h"
@@ -596,5 +598,65 @@
   EXPECT_FALSE(value_a->Equals(*value_b));
 }
 
+TEST(PropertyValueIsEqualTest, FilterListsAreEqual) {
+  FilterFunctionListValue::Builder filter_list_a;
+  filter_list_a.push_back(new MTMFunction(
+      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),
+      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),
+      KeywordValue::GetStereoscopicLeftRight()));
+  scoped_refptr<FilterFunctionListValue> value_a(
+      new FilterFunctionListValue(filter_list_a.Pass()));
+
+  FilterFunctionListValue::Builder filter_list_b;
+  filter_list_b.push_back(new MTMFunction(
+      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),
+      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),
+      KeywordValue::GetStereoscopicLeftRight()));
+  scoped_refptr<FilterFunctionListValue> value_b(
+      new FilterFunctionListValue(filter_list_b.Pass()));
+
+  EXPECT_TRUE(value_a->Equals(*value_b));
+}
+
+TEST(PropertyValueIsEqualTest, FilterListsAreNotEqual) {
+  FilterFunctionListValue::Builder filter_list_a;
+  filter_list_a.push_back(new MTMFunction(
+      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),
+      KeywordValue::GetMonoscopic()));
+  scoped_refptr<FilterFunctionListValue> value_a(
+      new FilterFunctionListValue(filter_list_a.Pass()));
+
+  FilterFunctionListValue::Builder filter_list_b;
+  filter_list_b.push_back(new MTMFunction(
+      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),
+      KeywordValue::GetMonoscopic()));
+  scoped_refptr<FilterFunctionListValue> value_b(
+      new FilterFunctionListValue(filter_list_b.Pass()));
+
+  EXPECT_FALSE(value_a->Equals(*value_b));
+}
+
 }  // namespace cssom
 }  // namespace cobalt
diff --git a/src/cobalt/cssom/property_value_to_string_test.cc b/src/cobalt/cssom/property_value_to_string_test.cc
index 6c90ec6..7d18941 100644
--- a/src/cobalt/cssom/property_value_to_string_test.cc
+++ b/src/cobalt/cssom/property_value_to_string_test.cc
@@ -18,6 +18,7 @@
 
 #include "base/time.h"
 #include "cobalt/cssom/absolute_url_value.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/font_style_value.h"
 #include "cobalt/cssom/font_weight_value.h"
 #include "cobalt/cssom/integer_value.h"
@@ -26,6 +27,7 @@
 #include "cobalt/cssom/linear_gradient_value.h"
 #include "cobalt/cssom/matrix_function.h"
 #include "cobalt/cssom/media_feature_keyword_value.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/number_value.h"
 #include "cobalt/cssom/percentage_value.h"
 #include "cobalt/cssom/property_definitions.h"
@@ -52,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) {
@@ -346,5 +348,89 @@
   EXPECT_EQ(property->ToString(), "url(foo.png)");
 }
 
+TEST(PropertyValueToStringTest, MTMFunctionSingleMesh) {
+  scoped_ptr<MTMFunction> function(new MTMFunction(
+      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),
+      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), "
+      "monoscopic)",
+      function->ToString());
+}
+
+TEST(PropertyValueToStringTest, MTMFunctionWithResolutionMatchedMeshes) {
+  MTMFunction::ResolutionMatchedMeshListBuilder meshes;
+  meshes.push_back(new MTMFunction::ResolutionMatchedMesh(
+      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),
+                      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), "
+      "stereoscopic-left-right)",
+      function->ToString());
+}
+
+TEST(PropertyValueToStringTest, FilterFunctionListValue) {
+  FilterFunctionListValue::Builder filter_list;
+  filter_list.push_back(new MTMFunction(
+      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),
+      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),
+      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()));
+
+  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), "
+      "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), "
+      "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());
+}
+
 }  // namespace cssom
 }  // namespace cobalt
diff --git a/src/cobalt/cssom/property_value_visitor.cc b/src/cobalt/cssom/property_value_visitor.cc
index cba67cb..12d234b 100644
--- a/src/cobalt/cssom/property_value_visitor.cc
+++ b/src/cobalt/cssom/property_value_visitor.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "cobalt/cssom/absolute_url_value.h"
 #include "cobalt/cssom/calc_value.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/font_style_value.h"
 #include "cobalt/cssom/font_weight_value.h"
 #include "cobalt/cssom/integer_value.h"
@@ -57,6 +58,11 @@
   VisitDefault(calc_value);
 }
 
+void DefaultingPropertyValueVisitor::VisitFilterFunctionList(
+    FilterFunctionListValue* filter_function_list_value) {
+  VisitDefault(filter_function_list_value);
+}
+
 void DefaultingPropertyValueVisitor::VisitFontStyle(
     FontStyleValue* font_style_value) {
   VisitDefault(font_style_value);
diff --git a/src/cobalt/cssom/property_value_visitor.h b/src/cobalt/cssom/property_value_visitor.h
index d363630..3457a9f 100644
--- a/src/cobalt/cssom/property_value_visitor.h
+++ b/src/cobalt/cssom/property_value_visitor.h
@@ -24,6 +24,7 @@
 
 class AbsoluteURLValue;
 class CalcValue;
+class FilterFunctionListValue;
 class FontStyleValue;
 class FontWeightValue;
 class IntegerValue;
@@ -58,6 +59,7 @@
  public:
   virtual void VisitAbsoluteURL(AbsoluteURLValue* url_value) = 0;
   virtual void VisitCalc(CalcValue* calc_value) = 0;
+  virtual void VisitFilterFunctionList(FilterFunctionListValue*) = 0;
   virtual void VisitFontStyle(FontStyleValue* font_style_value) = 0;
   virtual void VisitFontWeight(FontWeightValue* font_weight_value) = 0;
   virtual void VisitInteger(IntegerValue* integer_value) = 0;
@@ -102,6 +104,8 @@
  public:
   void VisitAbsoluteURL(AbsoluteURLValue* url_value) OVERRIDE;
   void VisitCalc(CalcValue* calc_value) OVERRIDE;
+  void VisitFilterFunctionList(
+      FilterFunctionListValue* filter_function_list_value) OVERRIDE;
   void VisitFontStyle(FontStyleValue* font_style_value) OVERRIDE;
   void VisitFontWeight(FontWeightValue* font_weight_value) OVERRIDE;
   void VisitKeyword(KeywordValue* keyword_value) OVERRIDE;
diff --git a/src/cobalt/cssom/property_value_visitor_test.cc b/src/cobalt/cssom/property_value_visitor_test.cc
index 953dd12..1ff138d 100644
--- a/src/cobalt/cssom/property_value_visitor_test.cc
+++ b/src/cobalt/cssom/property_value_visitor_test.cc
@@ -20,6 +20,7 @@
 
 #include "cobalt/cssom/absolute_url_value.h"
 #include "cobalt/cssom/calc_value.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/font_style_value.h"
 #include "cobalt/cssom/font_weight_value.h"
 #include "cobalt/cssom/integer_value.h"
@@ -28,6 +29,7 @@
 #include "cobalt/cssom/linear_gradient_value.h"
 #include "cobalt/cssom/local_src_value.h"
 #include "cobalt/cssom/media_feature_keyword_value.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/cssom/number_value.h"
 #include "cobalt/cssom/percentage_value.h"
 #include "cobalt/cssom/property_key_list_value.h"
@@ -59,6 +61,8 @@
   MOCK_METHOD1(VisitAbsoluteURL, void(AbsoluteURLValue* absolute_url_value));
   MOCK_METHOD1(VisitCalc, void(CalcValue* calc_value));
   MOCK_METHOD1(VisitFontStyle, void(FontStyleValue* font_style_value));
+  MOCK_METHOD1(VisitFilterFunctionList,
+               void(FilterFunctionListValue* filter_list_value));
   MOCK_METHOD1(VisitFontWeight, void(FontWeightValue* font_weight_value));
   MOCK_METHOD1(VisitInteger, void(IntegerValue* integer_value));
   MOCK_METHOD1(VisitKeyword, void(KeywordValue* keyword_value));
@@ -110,6 +114,24 @@
   calc_value->Accept(&mock_visitor);
 }
 
+TEST(PropertyValueVisitorTest, VisitsFilterListValue) {
+  MTMFunction::ResolutionMatchedMeshListBuilder resMs;
+  resMs.push_back(
+      new MTMFunction::ResolutionMatchedMesh(22, 22, new URLValue("a.msh")));
+
+  FilterFunctionListValue::Builder builder;
+  builder.push_back(new MTMFunction(new URLValue("p.msh"), resMs.Pass(), 120,
+                                    60, glm::mat4(1.0f),
+                                    KeywordValue::GetMonoscopic()));
+
+  scoped_refptr<FilterFunctionListValue> filter_list_value =
+      new FilterFunctionListValue(builder.Pass());
+
+  MockPropertyValueVisitor mock_visitor;
+  EXPECT_CALL(mock_visitor, VisitFilterFunctionList(filter_list_value.get()));
+  filter_list_value->Accept(&mock_visitor);
+}
+
 TEST(PropertyValueVisitorTest, VisitsFontStyleValue) {
   scoped_refptr<FontStyleValue> font_style_value = FontStyleValue::GetNormal();
   MockPropertyValueVisitor mock_visitor;
diff --git a/src/cobalt/cssom/selector_test.cc b/src/cobalt/cssom/selector_test.cc
index 090d9d7..c120db8 100644
--- a/src/cobalt/cssom/selector_test.cc
+++ b/src/cobalt/cssom/selector_test.cc
@@ -115,5 +115,42 @@
   EXPECT_EQ(Specificity(1, 2, 3), complex_selector->GetSpecificity());
 }
 
+TEST(SelectorTest, ComplexSelectorAppendCallLimit) {
+  {
+    scoped_ptr<ComplexSelector> complex_selector(new ComplexSelector());
+    complex_selector->AppendSelector(
+        make_scoped_ptr<CompoundSelector>(new CompoundSelector()));
+
+    for (int i = 0; i < ComplexSelector::kCombinatorLimit;
+         i++) {
+      scoped_ptr<CompoundSelector> compound_selector(new CompoundSelector());
+      scoped_ptr<ChildCombinator> child_combinator(new ChildCombinator());
+      complex_selector->AppendCombinatorAndSelector(
+          child_combinator.PassAs<Combinator>(), compound_selector.Pass());
+    }
+
+    EXPECT_EQ(complex_selector->combinator_count(),
+              ComplexSelector::kCombinatorLimit);
+  }
+
+  {
+    scoped_ptr<ComplexSelector> complex_selector(new ComplexSelector());
+    complex_selector->AppendSelector(
+        make_scoped_ptr<CompoundSelector>(new CompoundSelector()));
+
+    for (int i = 0;
+         i < 2 * ComplexSelector::kCombinatorLimit + 1;
+         i++) {
+      scoped_ptr<CompoundSelector> compound_selector(new CompoundSelector());
+      scoped_ptr<ChildCombinator> child_combinator(new ChildCombinator());
+      complex_selector->AppendCombinatorAndSelector(
+          child_combinator.PassAs<Combinator>(), compound_selector.Pass());
+    }
+
+    EXPECT_EQ(complex_selector->combinator_count(),
+              ComplexSelector::kCombinatorLimit);
+  }
+}
+
 }  // namespace cssom
 }  // namespace cobalt
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/MediaSource.idl b/src/cobalt/dom/MediaSource.idl
index a53419c..65eeb35 100644
--- a/src/cobalt/dom/MediaSource.idl
+++ b/src/cobalt/dom/MediaSource.idl
@@ -28,4 +28,6 @@
   readonly attribute DOMString readyState;
 
   [RaisesException] void endOfStream(optional DOMString error);
+
+  [CallWith=EnvironmentSettings] static boolean isTypeSupported(DOMString type);
 };
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..71ab518 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',
@@ -257,6 +258,7 @@
         '<(DEPTH)/cobalt/storage/storage.gyp:storage',
         '<(DEPTH)/cobalt/web_animations/web_animations.gyp:web_animations',
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
+        '<(DEPTH)/nb/nb.gyp:nb',
       ],
     },
 
diff --git a/src/cobalt/dom/dom_settings.cc b/src/cobalt/dom/dom_settings.cc
index 6588e93..e0bb25a 100644
--- a/src/cobalt/dom/dom_settings.cc
+++ b/src/cobalt/dom/dom_settings.cc
@@ -21,21 +21,23 @@
 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,
-                         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),
+      microphone_options_(options.microphone_options),
       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) {
   if (array_buffer_allocator_) {
diff --git a/src/cobalt/dom/dom_settings.h b/src/cobalt/dom/dom_settings.h
index 8366e67..e2099c7 100644
--- a/src/cobalt/dom/dom_settings.h
+++ b/src/cobalt/dom/dom_settings.h
@@ -20,9 +20,12 @@
 #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"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/speech/microphone.h"
 
 namespace cobalt {
 
@@ -55,6 +58,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;
+    // Microphone options.
+    speech::Microphone::Options microphone_options;
   };
 
   DOMSettings(const int max_dom_element_depth,
@@ -62,12 +67,17 @@
               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,
               const Options& options = Options());
   ~DOMSettings() OVERRIDE;
 
   int max_dom_element_depth() { return max_dom_element_depth_; }
+  const speech::Microphone::Options& microphone_options() const {
+    return microphone_options_;
+  }
 
   void set_window(const scoped_refptr<Window>& window) { window_ = window; }
   scoped_refptr<Window> window() const { return window_; }
@@ -95,18 +105,25 @@
   MediaSource::Registry* media_source_registry() const {
     return media_source_registry_;
   }
+  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 speech::Microphone::Options microphone_options_;
   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 a399a4e..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',
@@ -41,6 +42,7 @@
         'event_test.cc',
         'float32_array_test.cc',
         'float64_array_test.cc',
+        'font_cache_test.cc',
         'html_element_factory_test.cc',
         'html_element_test.cc',
         'keyboard_event_test.cc',
@@ -70,6 +72,7 @@
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom_testing',
         '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
+        '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
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/event_target.cc b/src/cobalt/dom/event_target.cc
index 87553ff..39175e3 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -107,6 +107,9 @@
 void EventTarget::PostToDispatchEventAndRunCallback(
     const tracked_objects::Location& location, base::Token event_name,
     const base::Closure& callback) {
+  if (!MessageLoop::current()) {
+    return;
+  }
   MessageLoop::current()->PostTask(
       location,
       base::Bind(base::IgnoreResult(&EventTarget::DispatchEventAndRunCallback),
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index 462d7a6..3fc4df9 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -68,12 +68,14 @@
   void DispatchEventAndRunCallback(base::Token event_name,
                                    const base::Closure& dispatched_callback);
 
-  // Posts a task on the current message loop to dispatch event.
+  // Posts a task on the current message loop to dispatch event. It does nothing
+  // if there is no current message loop.
   void PostToDispatchEvent(const tracked_objects::Location& location,
                            base::Token event_name);
 
   // Posts a task on the current message loop to dispatch event, and runs
-  // dispatched_callback after finish.
+  // dispatched_callback after finish.  It does nothing if there is no current
+  // message loop.
   void PostToDispatchEventAndRunCallback(
       const tracked_objects::Location& location, base::Token event_name,
       const base::Closure& dispatched_callback);
diff --git a/src/cobalt/dom/font_cache.cc b/src/cobalt/dom/font_cache.cc
index 0ee045c..9c9750e 100644
--- a/src/cobalt/dom/font_cache.cc
+++ b/src/cobalt/dom/font_cache.cc
@@ -155,7 +155,7 @@
         return TryGetRemoteFont(source_iterator->GetUrl(), size, state);
       } else {
         scoped_refptr<render_tree::Font> font =
-            TryGetLocalFont(source_iterator->GetName(), style, size, state);
+            TryGetLocalFontByFaceName(source_iterator->GetName(), size, state);
         if (font != NULL) {
           return font;
         }
@@ -283,6 +283,7 @@
 
 const scoped_refptr<render_tree::Typeface>& FontCache::GetCachedLocalTypeface(
     const scoped_refptr<render_tree::Typeface>& typeface) {
+  DCHECK(typeface);
   // Check to see if a typeface with a matching id is already in the cache. If
   // it is not, then add the passed in typeface to the cache.
   scoped_refptr<render_tree::Typeface>& cached_typeface =
@@ -308,6 +309,7 @@
   // completed or the timer expires.
   if (requested_remote_typeface_iterator ==
       requested_remote_typeface_cache_.end()) {
+    DLOG(INFO) << "Requested remote font from " << url;
     // Create the remote typeface load event's callback. This callback occurs on
     // successful loads, failed loads, and when the request's timer expires.
     base::Closure typeface_load_event_callback = base::Bind(
@@ -317,7 +319,7 @@
     // the iterator from the return value of the map insertion.
     requested_remote_typeface_iterator =
         requested_remote_typeface_cache_
-            .insert(std::make_pair(
+            .insert(RequestedRemoteTypefaceMap::value_type(
                 url, new RequestedRemoteTypefaceInfo(
                          cached_remote_typeface, typeface_load_event_callback)))
             .first;
@@ -346,6 +348,7 @@
     const std::string& family, render_tree::FontStyle style, float size,
     FontListFont::State* state) {
   DCHECK(resource_provider());
+  DCHECK(resource_provider() != NULL);
   // Only request the local font from the resource provider if the family is
   // empty or the resource provider actually has the family. The reason for this
   // is that the resource provider's |GetLocalTypeface()| is guaranteed to
@@ -365,6 +368,28 @@
   }
 }
 
+scoped_refptr<render_tree::Font> FontCache::TryGetLocalFontByFaceName(
+    const std::string& font_face, float size, FontListFont::State* state) {
+  do {
+    if (font_face.empty()) {
+      break;
+    }
+    const scoped_refptr<render_tree::Typeface>& typeface(
+        resource_provider()->GetLocalTypefaceByFaceNameIfAvailable(font_face));
+    if (!typeface) {
+      break;
+    }
+    const scoped_refptr<render_tree::Typeface>& typeface_cached(
+        GetCachedLocalTypeface(typeface));
+
+    *state = FontListFont::kLoadedState;
+    return GetFontFromTypefaceAndSize(typeface_cached, size);
+  } while (false);
+
+  *state = FontListFont::kUnavailableState;
+  return NULL;
+}
+
 void FontCache::OnRemoteTypefaceLoadEvent(const GURL& url) {
   DCHECK(thread_checker_.CalledOnValidThread());
   RequestedRemoteTypefaceMap::iterator requested_remote_typeface_iterator =
diff --git a/src/cobalt/dom/font_cache.h b/src/cobalt/dom/font_cache.h
index 89566cf..8e5afc5 100644
--- a/src/cobalt/dom/font_cache.h
+++ b/src/cobalt/dom/font_cache.h
@@ -39,20 +39,21 @@
 namespace dom {
 
 // The font cache is typically owned by dom::Document and handles the following:
+//   - Tracking of font faces, which it uses to determine if a specified
+//     font family is local or remote, and for url determination in requesting
+//     remote typefaces.
 //   - Creation and caching of font lists, which it provides to the used
 //     style provider as requested. Font lists handle most layout-related font
 //     cache interactions. Layout objects only interact with the font cache
 //     through their font lists.
-//   - Tracking of font faces, which it uses to determine if a specified
-//     font family is local or remote, and for url determination in requesting
-//     remote typefaces.
 //   - Retrieval of typefaces, either locally from the resource provider or
-//     remotely from the remote typeface cache.
-//   - Caching the indices of the glyphs that the typeface provides for specific
-//     characters, so that only the first query of a specific font character
-//     necessitates the glyph lookup.
-//   - Determination of the fallback typeface for a specific character, and
-//     caching of that information for subsequent lookups.
+//     remotely from the remote typeface cache, and caching of both typefaces
+//     and fonts to facilitate sharing of them across font lists.
+//   - Determination of the fallback typeface for a specific character using a
+//     specific font style, and caching of that information for subsequent
+//     lookups.
+//   - Creation of glyph buffers, which is accomplished by passing the request
+//    to the resource provider.
 // NOTE: The font cache is not thread-safe and must only used within a single
 // thread.
 class FontCache {
@@ -253,19 +254,22 @@
   // the constructor and an |OnRemoteFontLoadEvent| callback provided by the
   // font are registered with the remote typeface cache to be called when the
   // load finishes.
-  // If the font is loading but not currently available, |maybe_is_font_loading|
-  // will be set to true.
   scoped_refptr<render_tree::Font> TryGetRemoteFont(const GURL& url, float size,
                                                     FontListFont::State* state);
 
   // Returns NULL if the requested family is not empty and is not available in
   // the resource provider. Otherwise, returns the best matching local font.
-  // |maybe_is_font_loading| is always set to false.
   scoped_refptr<render_tree::Font> TryGetLocalFont(const std::string& family,
                                                    render_tree::FontStyle style,
                                                    float size,
                                                    FontListFont::State* state);
 
+  // Lookup by a typeface (aka font_face), typeface is defined as font family +
+  // style (weight, width, and style).
+  // Returns NULL if the requested font face is not found.
+  scoped_refptr<render_tree::Font> TryGetLocalFontByFaceName(
+      const std::string& font_face, float size, FontListFont::State* state);
+
   // Called when a remote typeface either successfully loads or fails to load.
   // In either case, the event can impact the fonts contained within the font
   // lists. As a result, the font lists need to have their loading fonts reset
diff --git a/src/cobalt/dom/font_cache_test.cc b/src/cobalt/dom/font_cache_test.cc
new file mode 100644
index 0000000..a46b631
--- /dev/null
+++ b/src/cobalt/dom/font_cache_test.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/dom/font_cache.h"
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/csp/content_security_policy.h"
+#include "cobalt/dom/font_face.h"
+#include "cobalt/loader/font/remote_typeface_cache.h"
+#include "cobalt/loader/font/typeface_decoder.h"
+#include "cobalt/loader/loader.h"
+#include "cobalt/loader/mock_loader_factory.h"
+#include "cobalt/render_tree/mock_resource_provider.h"
+#include "cobalt/render_tree/resource_provider.h"
+#include "cobalt/render_tree/resource_provider_stub.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+using ::testing::_;
+using ::testing::Return;
+
+const render_tree::FontStyle kNormalUpright(
+    render_tree::FontStyle::kNormalWeight,
+    render_tree::FontStyle::kUprightSlant);
+
+scoped_ptr<FontCache::FontFaceMap> CreateFontFaceMapHelper(
+    const std::string& family_name, const base::StringPiece local_font_name) {
+  scoped_ptr<FontCache::FontFaceMap> ffm(new FontCache::FontFaceMap());
+  dom::FontFaceStyleSet ffss;
+  dom::FontFaceStyleSet::Entry entry;
+  entry.sources.push_back(
+      FontFaceSource(local_font_name.as_string()));  // local()
+  entry.sources.push_back(FontFaceSource(
+      GURL("https://example.com/Dancing-Regular.woff")));  // url()
+  ffss.AddEntry(entry);
+  ffm->insert(FontCache::FontFaceMap::value_type(family_name, ffss));
+  return ffm.Pass();
+}
+
+class FontCacheTest : public ::testing::Test {
+ public:
+  FontCacheTest();
+  ~FontCacheTest() OVERRIDE {}
+
+  void DummyOnTypefaceLoadEvent() {}
+
+ protected:
+  ::testing::StrictMock<loader::MockLoaderFactory> loader_factory_;
+  scoped_refptr<cobalt::render_tree::Typeface> sample_typeface_;
+  ::testing::StrictMock<cobalt::render_tree::MockResourceProvider>
+      mock_resource_provider_;
+  cobalt::render_tree::ResourceProvider* mrp;
+  scoped_ptr<loader::font::RemoteTypefaceCache> rtc;
+  scoped_ptr<dom::FontCache> font_cache_;
+
+  MessageLoop message_loop_;
+};
+
+FontCacheTest::FontCacheTest()
+    : sample_typeface_(new render_tree::TypefaceStub(NULL)),
+      mrp(dynamic_cast<cobalt::render_tree::ResourceProvider*>(
+          &mock_resource_provider_)),
+      rtc(new loader::font::RemoteTypefaceCache(
+          "test_cache", 32 * 1024 /* 32 KB */,
+          base::Bind(&loader::MockLoaderFactory::CreateTypefaceLoader,
+                     base::Unretained(&loader_factory_)))),
+      font_cache_(
+          new dom::FontCache(&mrp,       // resource_provider
+                             rtc.get(),  // remotetypefacecache
+                             ALLOW_THIS_IN_INITIALIZER_LIST(base::Bind(
+                                 &FontCacheTest::DummyOnTypefaceLoadEvent,
+                                 base::Unretained(this))),
+                             "en-US")) {}
+
+TEST_F(FontCacheTest, FindPostscriptFont) {
+  const std::string family_name("Dancing Script");
+  const std::string postscript_font_name("DancingScript");
+  scoped_ptr<FontCache::FontFaceMap> ffm =
+      CreateFontFaceMapHelper(family_name, postscript_font_name);
+  font_cache_->SetFontFaceMap(ffm.Pass());
+
+  EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _))
+      .Times(0);
+
+  EXPECT_CALL(mock_resource_provider_,
+              GetLocalTypefaceIfAvailableMock(postscript_font_name))
+      .WillOnce(Return(sample_typeface_));
+
+  FontListFont::State state;
+  scoped_refptr<render_tree::Font> f =
+      font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state);
+
+  EXPECT_TRUE(f);
+}
+
+TEST_F(FontCacheTest, UseRemote) {
+  std::string invalid_postscript_font_name = "DancingScriptInvalidName";
+  const std::string family_name("Dancing Script");
+  scoped_ptr<FontCache::FontFaceMap> ffm =
+      CreateFontFaceMapHelper(family_name, invalid_postscript_font_name);
+  font_cache_->SetFontFaceMap(ffm.Pass());
+
+  EXPECT_CALL(mock_resource_provider_,
+              GetLocalTypefaceIfAvailableMock(invalid_postscript_font_name))
+      .Times(1);
+  EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _));
+
+  FontListFont::State state;
+  scoped_refptr<render_tree::Font> f =
+      font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state);
+  EXPECT_FALSE(f);
+}
+
+}  // namespace dom
+}  // namespace cobalt
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/font_list.cc b/src/cobalt/dom/font_list.cc
index f91dc45..69fde52 100644
--- a/src/cobalt/dom/font_list.cc
+++ b/src/cobalt/dom/font_list.cc
@@ -240,6 +240,7 @@
       }
     }
   }
+  DCHECK(primary_font_);
 
   return primary_font_;
 }
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc
index fd85733..7a4802c 100644
--- a/src/cobalt/dom/html_link_element.cc
+++ b/src/cobalt/dom/html_link_element.cc
@@ -26,6 +26,7 @@
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element_context.h"
 #include "googleurl/src/gurl.h"
+#include "nb/memory_scope.h"
 
 namespace cobalt {
 namespace dom {
@@ -54,6 +55,7 @@
 // Algorithm for Obtain:
 //   https://www.w3.org/TR/html5/document-metadata.html#concept-link-obtain
 void HTMLLinkElement::Obtain() {
+  TRACK_MEMORY_SCOPE("DOM");
   TRACE_EVENT0("cobalt::dom", "HTMLLinkElement::Obtain()");
   // Custom, not in any spec.
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -107,6 +109,7 @@
 }
 
 void HTMLLinkElement::OnLoadingDone(const std::string& content) {
+  TRACK_MEMORY_SCOPE("DOM");
   DCHECK(thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("cobalt::dom", "HTMLLinkElement::OnLoadingDone()");
 
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index e0f632c..762a0c3 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -53,6 +53,18 @@
 
 namespace {
 
+#define LOG_MEDIA_ELEMENT_ACTIVITIES 0
+
+#if LOG_MEDIA_ELEMENT_ACTIVITIES
+
+#define MLOG() LOG(INFO) << __FUNCTION__ << ": "
+
+#else  // LOG_MEDIA_ELEMENT_ACTIVITIES
+
+#define MLOG() EAT_STREAM_PARAMETERS
+
+#endif  // LOG_MEDIA_ELEMENT_ACTIVITIES
+
 void RaiseMediaKeyException(WebMediaPlayer::MediaKeyException exception,
                             script::ExceptionState* exception_state) {
   DCHECK_NE(exception, WebMediaPlayer::kMediaKeyExceptionNoError);
@@ -119,25 +131,35 @@
       pending_load_(false),
       sent_stalled_event_(false),
       sent_end_event_(false) {
+  MLOG();
   html_media_element_count_log.Get().count++;
 }
 
 HTMLMediaElement::~HTMLMediaElement() {
+  MLOG();
   SetSourceState(MediaSource::kReadyStateClosed);
   html_media_element_count_log.Get().count--;
 }
 
+scoped_refptr<MediaError> HTMLMediaElement::error() const {
+  MLOG() << error_->code();
+  return error_;
+}
+
 std::string HTMLMediaElement::src() const {
+  MLOG() << GetAttribute("src").value_or("");
   return GetAttribute("src").value_or("");
 }
 
 void HTMLMediaElement::set_src(const std::string& src) {
+  MLOG() << src;
   SetAttribute("src", src);
   ClearMediaPlayer();
   ScheduleLoad();
 }
 
 uint16_t HTMLMediaElement::network_state() const {
+  MLOG() << network_state_;
   return static_cast<uint16_t>(network_state_);
 }
 
@@ -145,13 +167,17 @@
   scoped_refptr<TimeRanges> buffered = new TimeRanges;
 
   if (!player_) {
+    MLOG() << "(empty)";
     return buffered;
   }
 
   const ::media::Ranges<base::TimeDelta>& player_buffered =
       player_->GetBufferedTimeRanges();
 
+  MLOG() << "================================";
   for (int i = 0; i < static_cast<int>(player_buffered.size()); ++i) {
+    MLOG() << player_buffered.start(i).InSecondsF() << " - "
+           << player_buffered.end(i).InSecondsF();
     buffered->Add(player_buffered.start(i).InSecondsF(),
                   player_buffered.end(i).InSecondsF());
   }
@@ -177,6 +203,7 @@
   std::string result =
       html_element_context()->can_play_type_handler()->CanPlayType(mime_type,
                                                                    key_system);
+  MLOG() << "(" << mime_type << ", " << key_system << ") => " << result;
   DLOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
              << key_system << ") -> " << result;
   return result;
@@ -186,15 +213,18 @@
     const std::string& key_system,
     const base::optional<scoped_refptr<Uint8Array> >& init_data,
     script::ExceptionState* exception_state) {
+  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() << "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() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -211,6 +241,7 @@
   }
 
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
@@ -220,21 +251,25 @@
     const base::optional<scoped_refptr<Uint8Array> >& init_data,
     const base::optional<std::string>& session_id,
     script::ExceptionState* exception_state) {
+  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() << "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() << "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() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -253,6 +288,7 @@
   }
 
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
@@ -264,11 +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() << "syntax error";
     DOMException::Raise(DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   if (!player_) {
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
@@ -277,29 +315,38 @@
   WebMediaPlayer::MediaKeyException exception =
       player_->CancelKeyRequest(key_system, session_id.value_or(""));
   if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) {
+    MLOG() << "exception: " << exception;
     RaiseMediaKeyException(exception, exception_state);
   }
 }
 
-WebMediaPlayer::ReadyState HTMLMediaElement::ready_state() const {
-  return ready_state_;
+uint16_t HTMLMediaElement::ready_state() const {
+  MLOG() << ready_state_;
+  return static_cast<uint16_t>(ready_state_);
 }
 
-bool HTMLMediaElement::seeking() const { return seeking_; }
+bool HTMLMediaElement::seeking() const {
+  MLOG() << seeking_;
+  return seeking_;
+}
 
 float HTMLMediaElement::current_time(
     script::ExceptionState* exception_state) const {
   UNREFERENCED_PARAMETER(exception_state);
 
   if (!player_) {
+    MLOG() << 0 << " (because player is NULL)";
     return 0;
   }
 
   if (seeking_) {
+    MLOG() << last_seek_time_ << " (seeking)";
     return last_seek_time_;
   }
 
-  return player_->GetCurrentTime();
+  float time = player_->GetCurrentTime();
+  MLOG() << time << " (from player)";
+  return time;
 }
 
 void HTMLMediaElement::set_current_time(
@@ -310,36 +357,50 @@
   // WebMediaPlayer::kReadyStateHaveNothing, then raise an INVALID_STATE_ERR
   // exception.
   if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing || !player_) {
+    MLOG() << "invalid state error";
     DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
     return;
   }
+  MLOG() << "seek to " << time;
   Seek(time);
 }
 
 float HTMLMediaElement::duration() const {
   if (player_ && ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata) {
-    return player_->GetDuration();
+    float duration = player_->GetDuration();
+    MLOG() << "player duration: " << duration;
+    return duration;
   }
 
+  MLOG() << "NaN";
   return std::numeric_limits<float>::quiet_NaN();
 }
 
-bool HTMLMediaElement::paused() const { return paused_; }
+bool HTMLMediaElement::paused() const {
+  MLOG() << paused_;
+  return paused_;
+}
 
 float HTMLMediaElement::default_playback_rate() const {
+  MLOG() << default_playback_rate_;
   return default_playback_rate_;
 }
 
 void HTMLMediaElement::set_default_playback_rate(float rate) {
+  MLOG() << rate;
   if (default_playback_rate_ != rate) {
     default_playback_rate_ = rate;
     ScheduleEvent(base::Tokens::ratechange());
   }
 }
 
-float HTMLMediaElement::playback_rate() const { return playback_rate_; }
+float HTMLMediaElement::playback_rate() const {
+  MLOG() << playback_rate_;
+  return playback_rate_;
+}
 
 void HTMLMediaElement::set_playback_rate(float rate) {
+  MLOG() << rate;
   if (playback_rate_ != rate) {
     playback_rate_ = rate;
     ScheduleEvent(base::Tokens::ratechange());
@@ -351,6 +412,7 @@
 }
 
 const scoped_refptr<TimeRanges>& HTMLMediaElement::played() {
+  MLOG();
   if (playing_) {
     float time = current_time(NULL);
     if (time > last_seek_time_) {
@@ -367,8 +429,11 @@
 
 scoped_refptr<TimeRanges> HTMLMediaElement::seekable() const {
   if (player_ && player_->GetMaxTimeSeekable() != 0) {
-    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() << "(empty)";
   return new TimeRanges;
 }
 
@@ -376,12 +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.
-  return EndedPlayback() && playback_rate_ > 0;
+  bool playback_ended = EndedPlayback() && playback_rate_ > 0;
+  MLOG() << playback_ended;
+  return playback_ended;
 }
 
-bool HTMLMediaElement::autoplay() const { return HasAttribute("autoplay"); }
+bool HTMLMediaElement::autoplay() const {
+  MLOG() << HasAttribute("autoplay");
+  return HasAttribute("autoplay");
+}
 
 void HTMLMediaElement::set_autoplay(bool autoplay) {
+  MLOG() << autoplay;
   // The value of 'autoplay' is true when the 'autoplay' attribute is present.
   // The value of the attribute is irrelevant.
   if (autoplay) {
@@ -391,11 +462,18 @@
   }
 }
 
-bool HTMLMediaElement::loop() const { return loop_; }
+bool HTMLMediaElement::loop() const {
+  MLOG() << loop_;
+  return loop_;
+}
 
-void HTMLMediaElement::set_loop(bool loop) { loop_ = loop; }
+void HTMLMediaElement::set_loop(bool loop) {
+  MLOG() << loop;
+  loop_ = loop;
+}
 
 void HTMLMediaElement::Play() {
+  MLOG();
   // 4.8.10.9. Playing the media resource
   if (!player_ || network_state_ == kNetworkEmpty) {
     ScheduleLoad();
@@ -421,6 +499,7 @@
 }
 
 void HTMLMediaElement::Pause() {
+  MLOG();
   // 4.8.10.9. Playing the media resource
   if (!player_ || network_state_ == kNetworkEmpty) {
     ScheduleLoad();
@@ -437,36 +516,45 @@
   UpdatePlayState();
 }
 
-bool HTMLMediaElement::controls() const { return controls_; }
+bool HTMLMediaElement::controls() const {
+  MLOG() << controls_;
+  return controls_;
+}
 
 void HTMLMediaElement::set_controls(bool controls) {
+  MLOG() << controls_;
   controls_ = controls;
   ConfigureMediaControls();
 }
 
 float HTMLMediaElement::volume(script::ExceptionState* exception_state) const {
   UNREFERENCED_PARAMETER(exception_state);
-
+  MLOG() << volume_;
   return volume_;
 }
 
-void HTMLMediaElement::set_volume(float vol,
+void HTMLMediaElement::set_volume(float volume,
                                   script::ExceptionState* exception_state) {
-  if (vol < 0.0f || vol > 1.0f) {
+  MLOG() << volume;
+  if (volume < 0.0f || volume > 1.0f) {
     DOMException::Raise(DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
-  if (volume_ != vol) {
-    volume_ = vol;
+  if (volume_ != volume) {
+    volume_ = volume;
     UpdateVolume();
     ScheduleEvent(base::Tokens::volumechange());
   }
 }
 
-bool HTMLMediaElement::muted() const { return muted_; }
+bool HTMLMediaElement::muted() const {
+  MLOG() << muted_;
+  return muted_;
+}
 
 void HTMLMediaElement::set_muted(bool muted) {
+  MLOG() << muted;
   if (muted_ != muted) {
     muted_ = muted;
     // Avoid recursion when the player reports volume changes.
@@ -489,6 +577,7 @@
 }
 
 void HTMLMediaElement::CreateMediaPlayer() {
+  MLOG();
   if (src().empty()) {
     reduced_image_cache_capacity_request_ = base::nullopt;
   } else if (html_element_context()
@@ -726,6 +815,7 @@
 }
 
 void HTMLMediaElement::ClearMediaPlayer() {
+  MLOG();
   SetSourceState(MediaSource::kReadyStateClosed);
 
   player_.reset(NULL);
@@ -744,6 +834,7 @@
 }
 
 void HTMLMediaElement::NoneSupported() {
+  MLOG();
   StopPeriodicTimers();
   load_state_ = kWaitingForSource;
 
@@ -874,6 +965,7 @@
 }
 
 void HTMLMediaElement::ScheduleEvent(base::Token event_name) {
+  MLOG() << event_name;
   scoped_refptr<Event> event =
       new Event(event_name, Event::kNotBubbles, Event::kCancelable);
   event->set_target(this);
@@ -1227,6 +1319,7 @@
 }
 
 void HTMLMediaElement::MediaEngineError(scoped_refptr<MediaError> error) {
+  MLOG() << error->code();
   DLOG(WARNING) << "HTMLMediaElement::MediaEngineError " << error->code();
 
   // 1 - The user agent should cancel the fetching process.
@@ -1370,6 +1463,7 @@
 
 void HTMLMediaElement::KeyAdded(const std::string& key_system,
                                 const std::string& session_id) {
+  MLOG() << key_system;
   event_queue_.Enqueue(new MediaKeyCompleteEvent(key_system, session_id));
 }
 
@@ -1377,6 +1471,7 @@
                                 const std::string& session_id,
                                 MediaKeyErrorCode error_code,
                                 uint16 system_code) {
+  MLOG() << key_system;
   MediaKeyError::Code code;
   switch (error_code) {
     case kUnknownError:
@@ -1411,6 +1506,7 @@
                                   const unsigned char* message,
                                   unsigned int message_length,
                                   const std::string& default_url) {
+  MLOG() << key_system;
   event_queue_.Enqueue(new MediaKeyMessageEvent(
       key_system, session_id,
       new Uint8Array(NULL, message, message_length, NULL), default_url));
@@ -1420,12 +1516,14 @@
                                  const std::string& session_id,
                                  const unsigned char* init_data,
                                  unsigned int init_data_length) {
+  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() << ready_state;
   if (!media_source_) {
     return;
   }
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h
index 7bd360e..90bfa7c 100644
--- a/src/cobalt/dom/html_media_element.h
+++ b/src/cobalt/dom/html_media_element.h
@@ -53,7 +53,7 @@
   // Web API: HTMLMediaElement
   //
   // Error state
-  scoped_refptr<MediaError> error() const { return error_; }
+  scoped_refptr<MediaError> error() const;
 
   // Network state
   std::string src() const;
@@ -95,7 +95,7 @@
     kHaveEnoughData = WebMediaPlayer::kReadyStateHaveEnoughData,
   };
 
-  WebMediaPlayer::ReadyState ready_state() const;
+  uint16_t ready_state() const;
   bool seeking() const;
 
   // Playback state
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index fcfdf35..5584557 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -32,6 +32,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_runner.h"
 #include "googleurl/src/gurl.h"
+#include "nb/memory_scope.h"
 
 namespace cobalt {
 namespace dom {
@@ -53,7 +54,8 @@
       load_option_(0),
       inline_script_location_(GetSourceLocationName(), 1, 1),
       is_sync_load_successful_(false),
-      prevent_garbage_collection_count_(0) {
+      prevent_garbage_collection_count_(0),
+      should_execute_(true) {
   DCHECK(document->html_element_context()->script_runner());
 }
 
@@ -110,6 +112,7 @@
 // Algorithm for Prepare:
 //   https://www.w3.org/TR/html5/scripting-1.html#prepare-a-script
 void HTMLScriptElement::Prepare() {
+  TRACK_MEMORY_SCOPE("DOM");
   // Custom, not in any spec.
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(MessageLoop::current());
@@ -260,12 +263,9 @@
                      base::Unretained(this)));
 
       if (is_sync_load_successful_) {
-        html_element_context()->script_runner()->Execute(
-            content_, base::SourceLocation(url_.spec(), 1, 1));
-
-        // If the script is from an external file, fire a simple event named
-        // load at the script element.
-        DispatchEvent(new Event(base::Tokens::load()));
+        PreventGarbageCollection();
+        ExecuteExternal();
+        AllowGarbageCollection();
       } else {
         // Executing the script block must just consist of firing a simple event
         // named error at the element.
@@ -497,7 +497,20 @@
 void HTMLScriptElement::Execute(const std::string& content,
                                 const base::SourceLocation& script_location,
                                 bool is_external) {
-  TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::Execute()");
+  TRACK_MEMORY_SCOPE("DOM");
+  // If should_execute_ is set to false for whatever reason, then we
+  // immediately exit.
+  // When inserted using the document.write() method, script elements execute
+  // (typically synchronously), but when inserted using innerHTML and
+  // outerHTML attributes, they do not execute at all.
+  // https://www.w3.org/TR/html5/scripting-1.html#the-script-element.
+  if (!should_execute_) {
+    return;
+  }
+
+  TRACE_EVENT2("cobalt::dom", "HTMLScriptElement::Execute()", "file_path",
+               script_location.file_path, "line_number",
+               script_location.line_number);
   // Since error is already handled, it is guaranteed the load is successful.
 
   // 1. 2. 3. Not needed by Cobalt.
diff --git a/src/cobalt/dom/html_script_element.h b/src/cobalt/dom/html_script_element.h
index 17ab6d4..aa523db 100644
--- a/src/cobalt/dom/html_script_element.h
+++ b/src/cobalt/dom/html_script_element.h
@@ -62,6 +62,10 @@
     SetAttributeEventListener(base::Tokens::readystatechange(), event_listener);
   }
 
+  void set_should_execute(bool should_execute) {
+    should_execute_ = should_execute;
+  }
+
   // Custom, not in any spec.
   //
   // From Node.
@@ -133,6 +137,9 @@
   std::string content_;
   // Active requests disabling garbage collection.
   int prevent_garbage_collection_count_;
+
+  // Whether or not the script should execute at all.
+  bool should_execute_;
 };
 
 }  // namespace dom
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/local_storage_database.cc b/src/cobalt/dom/local_storage_database.cc
index e5f2d11..6b7a4cc 100644
--- a/src/cobalt/dom/local_storage_database.cc
+++ b/src/cobalt/dom/local_storage_database.cc
@@ -19,6 +19,7 @@
 #include "base/debug/trace_event.h"
 #include "cobalt/dom/storage_area.h"
 #include "cobalt/storage/storage_manager.h"
+#include "nb/memory_scope.h"
 #include "sql/statement.h"
 
 namespace cobalt {
@@ -30,6 +31,7 @@
 const int kLatestLocalStorageSchemaVersion = 1;
 
 void SqlInit(storage::SqlContext* sql_context) {
+  TRACK_MEMORY_SCOPE("Storage");
   TRACE_EVENT0("cobalt::storage", "LocalStorage::SqlInit()");
   sql::Connection* conn = sql_context->sql_connection();
   int schema_version;
@@ -74,6 +76,7 @@
 void SqlReadValues(const std::string& id,
                    const LocalStorageDatabase::ReadCompletionCallback& callback,
                    storage::SqlContext* sql_context) {
+  TRACK_MEMORY_SCOPE("Storage");
   scoped_ptr<StorageArea::StorageMap> values(new StorageArea::StorageMap);
   sql::Connection* conn = sql_context->sql_connection();
   sql::Statement get_values(conn->GetCachedStatement(
@@ -91,6 +94,7 @@
 
 void SqlWrite(const std::string& id, const std::string& key,
               const std::string& value, storage::SqlContext* sql_context) {
+  TRACK_MEMORY_SCOPE("Storage");
   sql::Connection* conn = sql_context->sql_connection();
   sql::Statement write_statement(conn->GetCachedStatement(
       SQL_FROM_HERE,
@@ -105,6 +109,7 @@
 
 void SqlDelete(const std::string& id, const std::string& key,
                storage::SqlContext* sql_context) {
+  TRACK_MEMORY_SCOPE("Storage");
   sql::Connection* conn = sql_context->sql_connection();
   sql::Statement delete_statement(
       conn->GetCachedStatement(SQL_FROM_HERE,
@@ -117,6 +122,7 @@
 }
 
 void SqlClear(const std::string& id, storage::SqlContext* sql_context) {
+  TRACK_MEMORY_SCOPE("Storage");
   sql::Connection* conn = sql_context->sql_connection();
   sql::Statement clear_statement(
       conn->GetCachedStatement(SQL_FROM_HERE,
@@ -142,12 +148,14 @@
 
 void LocalStorageDatabase::ReadAll(const std::string& id,
                                    const ReadCompletionCallback& callback) {
+  TRACK_MEMORY_SCOPE("Storage");
   Init();
   storage_->GetSqlContext(base::Bind(&SqlReadValues, id, callback));
 }
 
 void LocalStorageDatabase::Write(const std::string& id, const std::string& key,
                                  const std::string& value) {
+  TRACK_MEMORY_SCOPE("Storage");
   Init();
   storage_->GetSqlContext(base::Bind(&SqlWrite, id, key, value));
   storage_->Flush();
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index fa881bf..9807b48 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -21,14 +21,18 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/debug/trace_event.h"
 #include "base/hash_tables.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/string_split.h"
 #include "base/string_util.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/event.h"
+#include "cobalt/media/can_play_type_handler.h"
 
 namespace cobalt {
 namespace dom {
@@ -70,35 +74,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),
@@ -252,6 +227,19 @@
   player_->SourceEndOfStream(eos_status);
 }
 
+// static
+bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings,
+                                  const std::string& type) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(settings);
+  DCHECK(dom_settings);
+  media::CanPlayTypeHandler* handler = dom_settings->can_play_type_handler();
+  DCHECK(handler);
+  std::string result = handler->CanPlayType(type, "");
+  DLOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> " << result;
+  return result == "probably";
+}
+
 void MediaSource::SetPlayer(WebMediaPlayer* player) {
   // It is possible to reuse a MediaSource object but unlikely. DCHECK it until
   // it is used in this way.
@@ -316,6 +304,8 @@
     SetReadyState(kReadyStateOpen);
   }
 
+  TRACE_EVENT1("media_stack", "MediaSource::Append()", "size", size);
+
   // If size is greater than kMaxAppendSize, we will append the data in multiple
   // small chunks with size less than or equal to kMaxAppendSize.  This can
   // avoid memory allocation spike as ChunkDemuxer may try to allocator memory
diff --git a/src/cobalt/dom/media_source.h b/src/cobalt/dom/media_source.h
index ec769e1..b8aa55a 100644
--- a/src/cobalt/dom/media_source.h
+++ b/src/cobalt/dom/media_source.h
@@ -25,6 +25,8 @@
 #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"
 
@@ -41,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.
   //
@@ -87,6 +69,9 @@
   void EndOfStream(const std::string& error,
                    script::ExceptionState* exception_state);
 
+  static bool IsTypeSupported(script::EnvironmentSettings* settings,
+                              const std::string& type);
+
   // Custom, not in any spec.
   //
   // The player is set when the media source is attached to a media element.
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index 1ec1d0d..cac03aa 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -37,6 +37,13 @@
 #include "cobalt/dom/node_list_live.h"
 #include "cobalt/dom/rule_matching.h"
 #include "cobalt/dom/text.h"
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#define HANDLE_CORE_DUMP
+#include "starboard/ps4/core_dump_handler.h"
+#endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#endif  // defined(OS_STARBOARD)
 
 namespace cobalt {
 namespace dom {
@@ -49,8 +56,25 @@
   NodeCountLog() : count(0) {
     base::UserLog::Register(base::UserLog::kNodeCountIndex, "NodeCnt", &count,
                             sizeof(count));
+#if defined(HANDLE_CORE_DUMP)
+    SbCoreDumpRegisterHandler(CoreDumpHandler, this);
+#endif
   }
-  ~NodeCountLog() { base::UserLog::Deregister(base::UserLog::kNodeCountIndex); }
+
+  ~NodeCountLog() {
+#if defined(HANDLE_CORE_DUMP)
+    SbCoreDumpUnregisterHandler(CoreDumpHandler, this);
+#endif
+    base::UserLog::Deregister(base::UserLog::kNodeCountIndex);
+  }
+
+#if defined(HANDLE_CORE_DUMP)
+  static void CoreDumpHandler(void* context) {
+    SbCoreDumpLogInteger(
+        "Total number of nodes",
+        static_cast<NodeCountLog*>(context)->count);
+  }
+#endif
 
   int count;
 
diff --git a/src/cobalt/dom/parser.h b/src/cobalt/dom/parser.h
index 554643e..3bf7392 100644
--- a/src/cobalt/dom/parser.h
+++ b/src/cobalt/dom/parser.h
@@ -45,7 +45,8 @@
   virtual ~Parser() {}
   // Synchronous parsing interfaces.
   //
-  // Parses an HTML document and returns the created Document.
+  // Parses an HTML document and returns the created Document.  No
+  // script elements within the HTML document will be executed.
   virtual scoped_refptr<Document> ParseDocument(
       const std::string& input, HTMLElementContext* html_element_context,
       const base::SourceLocation& input_location) = 0;
@@ -56,7 +57,8 @@
       const base::SourceLocation& input_location) = 0;
 
   // Parses an HTML input and inserts new nodes in document under parent_node
-  // before reference_node.
+  // before reference_node.  No script elements within the HTML document will
+  // be executed.
   virtual void ParseDocumentFragment(
       const std::string& input, const scoped_refptr<Document>& document,
       const scoped_refptr<Node>& parent_node,
@@ -74,7 +76,8 @@
   // Asynchronous parsing interfaces.
   //
   // Parses an HTML document asynchronously, returns the decoder that can be
-  // used in the parsing.
+  // used in the parsing.  Script elements in the HTML document will be
+  // executed.
   virtual scoped_ptr<loader::Decoder> ParseDocumentAsync(
       const scoped_refptr<Document>& document,
       const base::SourceLocation& input_location) = 0;
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/typed_array.h b/src/cobalt/dom/typed_array.h
index 266e002..e901a70 100644
--- a/src/cobalt/dom/typed_array.h
+++ b/src/cobalt/dom/typed_array.h
@@ -17,14 +17,16 @@
 #ifndef COBALT_DOM_TYPED_ARRAY_H_
 #define COBALT_DOM_TYPED_ARRAY_H_
 
-#include <memory.h>  // for memcpy
-
 #include "base/logging.h"
 #include "base/stringprintf.h"
 #include "cobalt/dom/array_buffer_view.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 
+#if defined(STARBOARD)
+#include "starboard/memory.h"
+#endif
+
 namespace cobalt {
 namespace dom {
 
@@ -50,7 +52,11 @@
              uint32 length)
       : ArrayBufferView(new ArrayBuffer(settings, length * kBytesPerElement)) {
     DCHECK_EQ(this->length(), length);
+#if defined(STARBOARD)
+    SbMemoryCopy(this->data(), data, length * kBytesPerElement);
+#else
     memcpy(this->data(), data, length * kBytesPerElement);
+#endif
   }
 
   // Creates a new TypedArray and copies the elements of 'other' into this.
@@ -123,8 +129,13 @@
     }
     uint32 source_offset = 0;
     while (source_offset < source->length()) {
+#if defined(STARBOARD)
+      SbMemoryCopy(data() + offset, source->data() + source_offset,
+             sizeof(ElementType));
+#else
       memcpy(data() + offset, source->data() + source_offset,
              sizeof(ElementType));
+#endif
       ++offset;
       ++source_offset;
     }
@@ -138,14 +149,22 @@
   // Write a single element of the array.
   void Set(uint32 index, ElementType val) {
     if (index < length()) {
+#if defined(STARBOARD)
+      SbMemoryCopy(data() + index, &val, sizeof(ElementType));
+#else
       memcpy(data() + index, &val, sizeof(ElementType));
+#endif
     }
   }
 
   ElementType Get(uint32 index) const {
     if (index < length()) {
       ElementType val;
+#if defined(STARBOARD)
+      SbMemoryCopy(&val, data() + index, sizeof(ElementType));
+#else
       memcpy(&val, data() + index, sizeof(ElementType));
+#endif
       return val;
     } else {
       // TODO: an out of bounds index should return undefined.
diff --git a/src/cobalt/dom/url.cc b/src/cobalt/dom/url.cc
index 7388773..e7b256e 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,33 @@
 
   // 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);
+
+  *size = 0;
+  *data = NULL;
+
+  dom::Blob* blob = registry->Retrieve(url.spec()).get();
+
+  if (blob) {
+    *size = static_cast<size_t>(blob->size());
+
+    if (*size > 0) {
+      *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/url_utils.cc b/src/cobalt/dom/url_utils.cc
index a31a07f..0ff0097 100644
--- a/src/cobalt/dom/url_utils.cc
+++ b/src/cobalt/dom/url_utils.cc
@@ -89,9 +89,15 @@
   return url_.has_ref() ? "#" + url_.ref() : "";
 }
 
+// Algorithm for set_hash:
+//   https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-urlutils-hash
 void URLUtils::set_hash(const std::string& hash) {
+  // 3. Let input be the given value with a single leading "#" removed, if any.
+  std::string hash_value =
+      !hash.empty() && hash[0] == '#' ? hash.substr(1) : hash;
+
   GURL::Replacements replacements;
-  replacements.SetRefStr(hash);
+  replacements.SetRefStr(hash_value);
   GURL new_url = url_.ReplaceComponents(replacements);
   RunPreUpdateSteps(new_url, "");
 }
@@ -100,9 +106,15 @@
   return url_.has_query() ? "?" + url_.query() : "";
 }
 
+// Algorithm for set_search:
+//   https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-urlutils-search
 void URLUtils::set_search(const std::string& search) {
+  // 3. Let input be the given value with a single leading "?" removed, if any.
+  std::string search_value =
+      !search.empty() && search[0] == '?' ? search.substr(1) : search;
+
   GURL::Replacements replacements;
-  replacements.SetQueryStr(search);
+  replacements.SetQueryStr(search_value);
   GURL new_url = url_.ReplaceComponents(replacements);
   RunPreUpdateSteps(new_url, "");
 }
@@ -111,6 +123,8 @@
 //   https://www.w3.org/TR/2014/WD-url-1-20141209/#pre-update-steps
 void URLUtils::RunPreUpdateSteps(const GURL& new_url,
                                  const std::string& value) {
+  DLOG(INFO) << "Update URL to " << new_url.spec();
+
   // 1. If value is not given, let value be the result of serializing the
   // associated url.
   // 2, Run the update steps with value.
diff --git a/src/cobalt/dom/url_utils_test.cc b/src/cobalt/dom/url_utils_test.cc
index 133b8b9..29c4721 100644
--- a/src/cobalt/dom/url_utils_test.cc
+++ b/src/cobalt/dom/url_utils_test.cc
@@ -92,6 +92,11 @@
   EXPECT_TRUE(url_utils.url().is_valid());
   EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash",
             url_utils.href());
+
+  url_utils.set_hash("#hash2");
+  EXPECT_TRUE(url_utils.url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash2",
+            url_utils.href());
 }
 
 TEST(URLUtilsTest, SetSearchShouldWorkAsExpected) {
@@ -100,6 +105,11 @@
   EXPECT_TRUE(url_utils.url().is_valid());
   EXPECT_EQ("https://user:pass@google.com:99/foo;bar?b=c#ref",
             url_utils.href());
+
+  url_utils.set_search("?d=e");
+  EXPECT_TRUE(url_utils.url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?d=e#ref",
+            url_utils.href());
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom_parser/html_decoder.cc b/src/cobalt/dom_parser/html_decoder.cc
index ddda365..f0b5585 100644
--- a/src/cobalt/dom_parser/html_decoder.cc
+++ b/src/cobalt/dom_parser/html_decoder.cc
@@ -30,12 +30,14 @@
     const scoped_refptr<dom::Node>& reference_node,
     const int dom_max_element_depth, const base::SourceLocation& input_location,
     const base::Closure& done_callback,
-    const base::Callback<void(const std::string&)>& error_callback)
+    const base::Callback<void(const std::string&)>& error_callback,
+    const bool should_run_scripts)
     : libxml_html_parser_wrapper_(new LibxmlHTMLParserWrapper(
           document, parent_node, reference_node, dom_max_element_depth,
-          input_location, error_callback)),
+          input_location, error_callback, should_run_scripts)),
       document_(document),
-      done_callback_(done_callback) {}
+      done_callback_(done_callback),
+      should_run_scripts_(should_run_scripts) {}
 
 HTMLDecoder::~HTMLDecoder() {}
 
diff --git a/src/cobalt/dom_parser/html_decoder.h b/src/cobalt/dom_parser/html_decoder.h
index f9d4da0..cf57cf9 100644
--- a/src/cobalt/dom_parser/html_decoder.h
+++ b/src/cobalt/dom_parser/html_decoder.h
@@ -50,7 +50,8 @@
               const int dom_max_element_depth,
               const base::SourceLocation& input_location,
               const base::Closure& done_callback,
-              const base::Callback<void(const std::string&)>& error_callback);
+              const base::Callback<void(const std::string&)>& error_callback,
+              const bool should_run_scripts);
 
   ~HTMLDecoder();
 
@@ -74,6 +75,8 @@
   base::ThreadChecker thread_checker_;
   const base::Closure done_callback_;
 
+  const bool should_run_scripts_;
+
   DISALLOW_COPY_AND_ASSIGN(HTMLDecoder);
 };
 
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc
index ab5f6a1..70019a1 100644
--- a/src/cobalt/dom_parser/html_decoder_test.cc
+++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -82,7 +82,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -104,7 +105,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp));
   html_decoder_->Finish();
 
@@ -122,7 +124,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -140,7 +143,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -158,7 +162,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -176,7 +181,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -199,7 +205,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -214,7 +221,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -238,7 +246,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -258,7 +267,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -276,7 +286,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -295,7 +306,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp));
   html_decoder_->Finish();
 
@@ -308,7 +320,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -335,7 +348,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -354,7 +368,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -367,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
 //
@@ -376,7 +419,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -402,7 +446,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -420,7 +465,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -434,7 +480,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, root_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
@@ -457,7 +504,8 @@
   html_decoder_.reset(new HTMLDecoder(
       document_, document_, NULL, kDOMMaxElementDepth, source_location_,
       base::Closure(), base::Bind(&MockErrorCallback::Run,
-                                  base::Unretained(&mock_error_callback_))));
+                                  base::Unretained(&mock_error_callback_)),
+      true));
   html_decoder_->DecodeChunk(input.c_str(), input.length());
   html_decoder_->Finish();
 
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
index 5a0b703..7a05cb8 100644
--- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
@@ -18,6 +18,8 @@
 
 #include "base/basictypes.h"
 #include "base/string_util.h"
+#include "cobalt/dom/element.h"
+#include "cobalt/dom/html_script_element.h"
 
 namespace cobalt {
 namespace dom_parser {
@@ -89,6 +91,17 @@
   if (!IsFullDocument() && (name == "html" || name == "body")) {
     return;
   }
+
+  // If the top if the node stack is an html script element, then set its
+  // should_execute_ field to be our should_run_scripts_ field.
+  DCHECK(!node_stack().empty());
+  if (name == "script") {
+    scoped_refptr<dom::HTMLScriptElement> html_script_element =
+        node_stack().top()->AsElement()->AsHTMLElement()->AsHTMLScriptElement();
+    DCHECK(html_script_element);
+    html_script_element->set_should_execute(should_run_scripts_);
+  }
+
   LibxmlParserWrapper::OnEndElement(name);
 }
 
@@ -97,7 +110,10 @@
     return;
   }
 
-  if (CheckInputAndUpdateSeverity(data, size) == kFatal) {
+  std::string current_chunk;
+  PreprocessChunk(data, size, &current_chunk);
+
+  if (max_severity() == kFatal) {
     return;
   }
 
@@ -107,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[] =
@@ -122,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_html_parser_wrapper.h b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
index f553818..7114443 100644
--- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
+++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
@@ -40,11 +40,13 @@
       const scoped_refptr<dom::Node>& reference_node,
       const int dom_max_element_depth,
       const base::SourceLocation& input_location,
-      const base::Callback<void(const std::string&)>& error_callback)
+      const base::Callback<void(const std::string&)>& error_callback,
+      const bool should_run_scripts)
       : LibxmlParserWrapper(document, parent_node, reference_node,
                             dom_max_element_depth, input_location,
                             error_callback),
-        html_parser_context_(NULL) {
+        html_parser_context_(NULL),
+        should_run_scripts_(should_run_scripts) {
     DCHECK(!document->IsXMLDocument());
   }
   ~LibxmlHTMLParserWrapper() OVERRIDE;
@@ -61,6 +63,8 @@
 
   htmlParserCtxtPtr html_parser_context_;
 
+  const bool should_run_scripts_;
+
   DISALLOW_COPY_AND_ASSIGN(LibxmlHTMLParserWrapper);
 };
 
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
index 035405b..a86753e 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -19,15 +19,88 @@
 #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"
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/text.h"
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#define HANDLE_CORE_DUMP
+#include "base/lazy_instance.h"
+#include "starboard/ps4/core_dump_handler.h"
+#endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#endif  // defined(OS_STARBOARD)
 
 namespace cobalt {
 namespace dom_parser {
 namespace {
 
+#if defined(HANDLE_CORE_DUMP)
+
+class LibxmlParserWrapperLog {
+ public:
+  LibxmlParserWrapperLog()
+      : total_parsed_bytes_(0),
+        total_warning_count_(0),
+        total_error_count_(0),
+        total_fatal_count_(0) {
+    SbCoreDumpRegisterHandler(CoreDumpHandler, this);
+  }
+  ~LibxmlParserWrapperLog() {
+    SbCoreDumpUnregisterHandler(CoreDumpHandler, this);
+  }
+
+  static void CoreDumpHandler(void* context) {
+    SbCoreDumpLogInteger(
+        "LibxmlParserWrapper total parsed bytes",
+        static_cast<LibxmlParserWrapperLog*>(context)->total_parsed_bytes_);
+    SbCoreDumpLogInteger(
+        "LibxmlParserWrapper total warning count",
+        static_cast<LibxmlParserWrapperLog*>(context)->total_warning_count_);
+    SbCoreDumpLogInteger(
+        "LibxmlParserWrapper total error count",
+        static_cast<LibxmlParserWrapperLog*>(context)->total_error_count_);
+    SbCoreDumpLogInteger(
+        "LibxmlParserWrapper total fatal error count",
+        static_cast<LibxmlParserWrapperLog*>(context)->total_fatal_count_);
+    SbCoreDumpLogString("LibxmlParserWrapper last fatal error",
+                        static_cast<LibxmlParserWrapperLog*>(context)
+                            ->last_fatal_message_.c_str());
+  }
+
+  void IncrementParsedBytes(int length) { total_parsed_bytes_ += length; }
+  void LogParsingIssue(LibxmlParserWrapper::IssueSeverity severity,
+                       const std::string& message) {
+    if (severity == LibxmlParserWrapper::kWarning) {
+      total_warning_count_++;
+    } else if (severity == LibxmlParserWrapper::kError) {
+      total_error_count_++;
+    } else if (severity == LibxmlParserWrapper::kFatal) {
+      total_fatal_count_++;
+      last_fatal_message_ = message;
+    } else {
+      NOTREACHED();
+    }
+  }
+
+ private:
+  int total_parsed_bytes_;
+  int total_warning_count_;
+  int total_error_count_;
+  int total_fatal_count_;
+  std::string last_fatal_message_;
+  DISALLOW_COPY_AND_ASSIGN(LibxmlParserWrapperLog);
+};
+
+base::LazyInstance<LibxmlParserWrapperLog> libxml_parser_wrapper_log =
+    LAZY_INSTANCE_INITIALIZER;
+
+#endif  // defined(HANDLE_CORE_DUMP)
+
 /////////////////////////////////////////////////////////////////////////////
 // Helpers
 /////////////////////////////////////////////////////////////////////////////
@@ -150,6 +223,10 @@
   if (!node_stack_.empty() && !error_callback_.is_null()) {
     error_callback_.Run("Node stack not empty at end of document.");
   }
+
+  if (IsFullDocument()) {
+    document_->PostToDispatchEvent(FROM_HERE, base::Tokens::domcontentloaded());
+  }
 }
 
 void LibxmlParserWrapper::OnStartElement(
@@ -221,42 +298,50 @@
   if (severity < LibxmlParserWrapper::kFatal) {
     LOG(WARNING) << "Libxml "
                  << (severity == kWarning ? "Warning: " : "Error: ") << message;
-  } else {
+  } else if (severity == LibxmlParserWrapper::kFatal) {
     LOG(ERROR) << "Libxml Fatal Error: " << message;
     if (!error_callback_.is_null()) {
       error_callback_.Run(message);
     }
+  } else {
+    NOTREACHED();
   }
+
+#if defined(HANDLE_CORE_DUMP)
+  libxml_parser_wrapper_log.Get().LogParsingIssue(severity, message);
+#endif
 }
 
 void LibxmlParserWrapper::OnCDATABlock(const std::string& value) {
   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;
   }
 
-  return max_severity_;
+#if defined(HANDLE_CORE_DUMP)
+  libxml_parser_wrapper_log.Get().IncrementParsedBytes(static_cast<int>(size));
+#endif
 }
 
 }  // 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/dom_parser/parser.cc b/src/cobalt/dom_parser/parser.cc
index 507c187..5be05a6 100644
--- a/src/cobalt/dom_parser/parser.cc
+++ b/src/cobalt/dom_parser/parser.cc
@@ -32,7 +32,8 @@
   scoped_refptr<dom::Document> document =
       new dom::Document(html_element_context, dom::Document::Options());
   HTMLDecoder html_decoder(document, document, NULL, dom_max_element_depth_,
-                           input_location, base::Closure(), error_callback_);
+                           input_location, base::Closure(), error_callback_,
+                           false);
   html_decoder.DecodeChunk(input.c_str(), input.length());
   html_decoder.Finish();
   return document;
@@ -60,7 +61,7 @@
     const base::SourceLocation& input_location) {
   HTMLDecoder html_decoder(document, parent_node, reference_node,
                            dom_max_element_depth_, input_location,
-                           base::Closure(), error_callback_);
+                           base::Closure(), error_callback_, false);
   html_decoder.DecodeChunk(input.c_str(), input.length());
   html_decoder.Finish();
 }
@@ -84,7 +85,7 @@
     const base::SourceLocation& input_location) {
   return scoped_ptr<loader::Decoder>(
       new HTMLDecoder(document, document, NULL, dom_max_element_depth_,
-                      input_location, base::Closure(), error_callback_));
+                      input_location, base::Closure(), error_callback_, true));
 }
 
 scoped_ptr<loader::Decoder> Parser::ParseXMLDocumentAsync(
diff --git a/src/cobalt/h5vcc/h5vcc_runtime.cc b/src/cobalt/h5vcc/h5vcc_runtime.cc
index 82437b4..43d71cc 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/src/cobalt/h5vcc/h5vcc_runtime.cc
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
+#include "cobalt/h5vcc/h5vcc_runtime.h"
+
 #include "cobalt/base/deep_link_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/h5vcc/h5vcc_runtime.h"
 #include "cobalt/system_window/application_event.h"
 
 namespace cobalt {
@@ -79,8 +80,10 @@
 void H5vccRuntime::OnDeepLinkEvent(const base::Event* event) {
   const base::DeepLinkEvent* deep_link_event =
       base::polymorphic_downcast<const base::DeepLinkEvent*>(event);
-  DLOG(INFO) << "Got deep link event: " << deep_link_event->link();
-  on_deep_link()->DispatchEvent(deep_link_event->link());
+  if (!deep_link_event->IsH5vccLink()) {
+    DLOG(INFO) << "Got deep link event: " << deep_link_event->link();
+    on_deep_link()->DispatchEvent(deep_link_event->link());
+  }
 }
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 4a57476..a04ced7 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/layout/box.h"
 
+#include <algorithm>
 #include <limits>
 
 #include "base/logging.h"
@@ -472,10 +473,17 @@
   // 'visibility: visible'.
   //   https://www.w3.org/TR/CSS21/visufx.html#propdef-visibility
   if (computed_style()->visibility() == cssom::KeywordValue::GetVisible()) {
-    RenderAndAnimateBackgroundColor(
-        padding_rounded_corners, &border_node_builder, &animate_node_builder);
-    RenderAndAnimateBackgroundImage(
-        padding_rounded_corners, &border_node_builder, &animate_node_builder);
+    RenderAndAnimateBackgroundImageResult background_image_result =
+        RenderAndAnimateBackgroundImage(padding_rounded_corners);
+    // If the background image is opaque, then it will occlude the background
+    // color and so we do not need to render the background color.
+    if (!background_image_result.is_opaque) {
+      RenderAndAnimateBackgroundColor(
+          padding_rounded_corners, &border_node_builder, &animate_node_builder);
+    }
+    if (background_image_result.node) {
+      border_node_builder.AddChild(background_image_result.node);
+    }
     RenderAndAnimateBorder(border_radius_provider.rounded_corners(),
                            &border_node_builder, &animate_node_builder);
     RenderAndAnimateBoxShadow(border_radius_provider.rounded_corners(),
@@ -900,7 +908,7 @@
                       style->opacity().get())->value();
 
   if (opacity < 1.0f) {
-    filter_node_builder->opacity_filter.emplace(opacity);
+    filter_node_builder->opacity_filter.emplace(std::max(0.0f, opacity));
   } else {
     // If opacity is 1, then no opacity filter should be applied, so the
     // source render tree should appear fully opaque.
@@ -1066,11 +1074,16 @@
   }
 }
 
-void Box::RenderAndAnimateBackgroundImage(
-    const base::optional<RoundedCorners>& rounded_corners,
-    CompositionNode::Builder* border_node_builder,
-    AnimateNode::Builder* animate_node_builder) {
-  UNREFERENCED_PARAMETER(animate_node_builder);
+Box::RenderAndAnimateBackgroundImageResult Box::RenderAndAnimateBackgroundImage(
+    const base::optional<RoundedCorners>& rounded_corners) {
+  RenderAndAnimateBackgroundImageResult result;
+  // We track a single render tree node because most of the time there will only
+  // be one.  If there is more, we set |single_node| to NULL and instead
+  // populate |composition|.  The code here tries to avoid using CompositionNode
+  // if possible to avoid constructing an std::vector.
+  scoped_refptr<render_tree::Node> single_node = NULL;
+  base::optional<CompositionNode::Builder> composition;
+  result.is_opaque = false;
 
   math::RectF image_frame(
       math::PointF(border_left_width().toFloat(), border_top_width().toFloat()),
@@ -1105,9 +1118,34 @@
         background_node = new FilterNode(filter_node_builder);
       }
 
-      border_node_builder->AddChild(background_node);
+      // If any of the background image layers are opaque, we set that the
+      // background image is opaque.  This is used to avoid setting up the
+      // background color if the background image is just going to cover it
+      // anyway.
+      result.is_opaque |= background_node_provider.is_opaque();
+
+      // If this is not the first node to return, then our |single_node|
+      // shortcut won't work, copy that single node into |composition| before
+      // continuing.
+      if (single_node) {
+        composition.emplace();
+        composition->AddChild(single_node);
+        single_node = NULL;
+      }
+      if (!composition) {
+        single_node = background_node;
+      } else {
+        composition->AddChild(background_node);
+      }
     }
   }
+
+  if (single_node) {
+    result.node = single_node;
+  } else if (composition) {
+    result.node = new CompositionNode(composition->Pass());
+  }
+  return result;
 }
 
 scoped_refptr<render_tree::Node> Box::RenderAndAnimateOpacity(
@@ -1118,7 +1156,7 @@
     FilterNode::Builder filter_node_builder(border_node);
 
     if (opacity < 1.0f) {
-      filter_node_builder.opacity_filter = OpacityFilter(opacity);
+      filter_node_builder.opacity_filter.emplace(std::max(0.0f, opacity));
     }
 
     scoped_refptr<FilterNode> filter_node = new FilterNode(filter_node_builder);
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 676b04d..60e9ccb 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -603,10 +603,17 @@
       const base::optional<render_tree::RoundedCorners>& rounded_corners,
       render_tree::CompositionNode::Builder* border_node_builder,
       render_tree::animations::AnimateNode::Builder* animate_node_builder);
-  void RenderAndAnimateBackgroundImage(
-      const base::optional<render_tree::RoundedCorners>& rounded_corners,
-      render_tree::CompositionNode::Builder* border_node_builder,
-      render_tree::animations::AnimateNode::Builder* animate_node_builder);
+  struct RenderAndAnimateBackgroundImageResult {
+    // The node representing the background image (may be a CompositionNode if
+    // there are multiple layers).
+    scoped_refptr<render_tree::Node> node;
+    // Returns whether the background image opaquely fills the entire frame.
+    // If true, then we don't need to even consider rendering the background
+    // color, since it will be occluded by the image.
+    bool is_opaque;
+  };
+  RenderAndAnimateBackgroundImageResult RenderAndAnimateBackgroundImage(
+      const base::optional<render_tree::RoundedCorners>& rounded_corners);
   void RenderAndAnimateBoxShadow(
       const base::optional<render_tree::RoundedCorners>& rounded_corners,
       render_tree::CompositionNode::Builder* border_node_builder,
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/container_box.cc b/src/cobalt/layout/container_box.cc
index 1d55190..a2c79bb 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -400,7 +400,7 @@
 
 Vector2dLayoutUnit GetOffsetFromContainingBlockToStackingContext(
     Box* child_box) {
-  DCHECK(child_box->IsPositioned());
+  DCHECK(child_box->IsPositioned() || child_box->IsTransformed());
 
   Vector2dLayoutUnit relative_position;
   for (Box *containing_block = child_box->GetContainingBlock(),
@@ -454,7 +454,7 @@
     if (!current_box) {
       // Positioned elements may have their containing block farther
       // up the hierarchy than the stacking context, so handle this case here.
-      DCHECK(child_box->IsPositioned());
+      DCHECK(child_box->IsPositioned() || child_box->IsTransformed());
       return -GetOffsetFromContainingBlockToStackingContext(child_box);
     }
 #if !defined(NDEBUG)
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc
index 46ca1c1..bd61bde 100644
--- a/src/cobalt/layout/layout.cc
+++ b/src/cobalt/layout/layout.cc
@@ -78,7 +78,7 @@
   *initial_containing_block = initial_containing_block_creation_results.box;
 
   // Generate boxes.
-  {
+  if (document->html()) {
     TRACE_EVENT0("cobalt::layout", kBenchmarkStatBoxGeneration);
     base::StopWatch stop_watch_box_generation(
         LayoutStatTracker::kStopWatchTypeBoxGeneration,
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index 1b1c865..7bcb96a 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -20,7 +20,9 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "cobalt/cssom/filter_function_list_value.h"
 #include "cobalt/cssom/keyword_value.h"
+#include "cobalt/cssom/mtm_function.h"
 #include "cobalt/layout/container_box.h"
 #include "cobalt/layout/letterboxed_image.h"
 #include "cobalt/layout/used_style.h"
@@ -42,10 +44,11 @@
 using render_tree::CompositionNode;
 using render_tree::FilterNode;
 using render_tree::ImageNode;
+using render_tree::MapToMeshFilter;
+using render_tree::Node;
 using render_tree::PunchThroughVideoNode;
 using render_tree::RectNode;
 using render_tree::SolidColorBrush;
-using render_tree::MapToMeshFilter;
 
 namespace {
 
@@ -57,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(
@@ -169,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);
@@ -197,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) {
@@ -209,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
@@ -231,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()) {
@@ -254,14 +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()));
 
-  // TODO: Apply map to mesh filter if present.
-  border_node_builder->AddChild(
-      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());
+
+  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..26cc44d 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:
@@ -560,7 +566,8 @@
       background_size_(background_size),
       background_position_(background_position),
       background_repeat_(background_repeat),
-      used_style_provider_(used_style_provider) {}
+      used_style_provider_(used_style_provider),
+      is_opaque_(false) {}
 
 void UsedBackgroundNodeProvider::VisitAbsoluteURL(
     cssom::AbsoluteURLValue* url_value) {
@@ -589,10 +596,16 @@
             &used_background_size_provider, &used_background_position_provider,
             &used_background_repeat_provider, frame_, single_image_size);
 
+    math::RectF image_rect(image_transform_data.composition_node_translation,
+                           image_transform_data.image_node_size);
+
+    is_opaque_ = used_background_image->IsOpaque() &&
+                 image_rect.x() <= frame_.x() && image_rect.y() <= frame_.y() &&
+                 image_rect.right() >= frame_.right() &&
+                 image_rect.bottom() >= frame_.bottom();
+
     background_node_ = new render_tree::ImageNode(
-        used_background_image,
-        math::RectF(image_transform_data.composition_node_translation,
-                    image_transform_data.image_node_size),
+        used_background_image, image_rect,
         image_transform_data.image_node_transform_matrix);
   }
 }
@@ -1163,6 +1176,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 +1194,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 +1357,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 +1375,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 +1430,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 +1447,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/used_style.h b/src/cobalt/layout/used_style.h
index ca32a1c..8c964a1 100644
--- a/src/cobalt/layout/used_style.h
+++ b/src/cobalt/layout/used_style.h
@@ -134,6 +134,8 @@
     return background_node_;
   }
 
+  bool is_opaque() const { return is_opaque_; }
+
  private:
   const math::RectF frame_;
   const scoped_refptr<cssom::PropertyValue> background_size_;
@@ -143,6 +145,8 @@
 
   scoped_refptr<render_tree::Node> background_node_;
 
+  bool is_opaque_;
+
   DISALLOW_COPY_AND_ASSIGN(UsedBackgroundNodeProvider);
 };
 
diff --git a/src/cobalt/layout_tests/layout_benchmarks.cc b/src/cobalt/layout_tests/layout_benchmarks.cc
index 164741c..bace1ed 100644
--- a/src/cobalt/layout_tests/layout_benchmarks.cc
+++ b/src/cobalt/layout_tests/layout_benchmarks.cc
@@ -45,14 +45,18 @@
   RendererBenchmarkRunner()
       : done_gathering_samples_(true, false),
         system_window_(system_window::CreateSystemWindow(
-            &event_dispatcher_, math::Size(kViewportWidth, kViewportHeight))),
-        renderer_module_(system_window_.get(),
-                         renderer::RendererModule::Options()) {}
+            &event_dispatcher_, math::Size(kViewportWidth, kViewportHeight))) {
+    // Since we'd like to measure the renderer, we force it to rasterize each
+    // frame despite the fact that the render tree may not be changing.
+    renderer::RendererModule::Options renderer_options;
+    renderer_options.submit_even_if_render_tree_is_unchanged = true;
+    renderer_module_.emplace(system_window_.get(), renderer_options);
+  }
 
   // Return the resource provider from the internal renderer so that it can
   // be used during layout.
   render_tree::ResourceProvider* GetResourceProvider() {
-    return renderer_module_.pipeline()->GetResourceProvider();
+    return renderer_module_->pipeline()->GetResourceProvider();
   }
 
   // Run the renderer benchmarks and perform the measurements.
@@ -67,11 +71,11 @@
     submission_with_callback.on_rasterized_callback = base::Bind(
         &RendererBenchmarkRunner::OnSubmitComplete, base::Unretained(this));
 
-    renderer_module_.pipeline()->Submit(submission_with_callback);
+    renderer_module_->pipeline()->Submit(submission_with_callback);
 
     done_gathering_samples_.Wait();
 
-    renderer_module_.pipeline()->Clear();
+    renderer_module_->pipeline()->Clear();
   }
 
  private:
@@ -93,7 +97,7 @@
   base::EventDispatcher event_dispatcher_;
 
   scoped_ptr<system_window::SystemWindow> system_window_;
-  renderer::RendererModule renderer_module_;
+  base::optional<renderer::RendererModule> renderer_module_;
 };
 
 }  // namespace
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc
index c2e63c0..38adb86 100644
--- a/src/cobalt/layout_tests/layout_tests.cc
+++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -92,7 +92,7 @@
   scoped_refptr<render_tree::animations::AnimateNode> animate_node =
       new render_tree::animations::AnimateNode(layout_results.render_tree);
   scoped_refptr<render_tree::Node> animated_tree =
-      animate_node->Apply(layout_results.layout_time);
+      animate_node->Apply(layout_results.layout_time).animated;
 
   bool results =
       pixel_tester.TestTree(animated_tree, GetParam().base_file_path);
@@ -189,6 +189,11 @@
     AnimationTimingAPILayoutTests, LayoutTest,
     ::testing::ValuesIn(EnumerateLayoutTests("animation-timing")));
 
+// Problematic test cases found through cluster-fuzz.
+INSTANTIATE_TEST_CASE_P(
+    ClusterFuzzLayoutTests, LayoutTest,
+    ::testing::ValuesIn(EnumerateLayoutTests("cluster-fuzz")));
+
 // Disable on Windows until network stack is implemented.
 #if !defined(COBALT_WIN)
 // Content Security Policy test cases.
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png
new file mode 100644
index 0000000..f1b767f
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html
new file mode 100644
index 0000000..9f6ed1a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html
@@ -0,0 +1,17 @@
+cm/000000086.jpg" target="_blank"><img src="./picm/000000086.jpg" width="277" style="border: 0px solid ;"></a>
+<div class="blockImageCaption"><br>�s�d�k�F�i�O�W�V�j�W�V�W�|�Q�P�W�W�i���j<br>�e�`�w�F�i�O�W�V�j�W�V�W�|�Q�V�V�P</p>
+<!--  -->
+<!-- /blockText --></div>
+<!--  -->
+<!-- /blockText --></div>
+<!-- /block2column2 --></div>
+<br class="blockEdge">
+<!-- /CB0200_4 --></div>
+>
+<!--  -->
+
+<!--  --><div class="blockImage">
+<!--  -->
+ class="blockImageCaption"><small></small></div>
+>
+<!-- 
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt b/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt
new file mode 100644
index 0000000..12a4567
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt
@@ -0,0 +1 @@
+fuzz-222
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html b/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html
index 498e1a7..2c692ea 100644
--- a/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html
+++ b/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html
@@ -16,12 +16,12 @@
   that max element depth is 32. -->
 
 <script>
-  var parent = document.body;
+  var parent_element = document.body;
   for (var i = 1; i <= 100; i++) {
     var new_child = document.createElement("span");
     new_child.textContent = i + " ";
-    parent.appendChild(new_child);
-    parent = new_child;
+    parent_element.appendChild(new_child);
+    parent_element = new_child;
   }
 </script>
 
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/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png
new file mode 100644
index 0000000..6418185
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html
new file mode 100644
index 0000000..49defbc
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+ | Tests that opaque images render correctly and exercises an optimization
+ | code path where a background color does not need to be rendered if an
+ | opaque image covers an element's frame instead.
+ -->
+<html>
+<head>
+  <style>
+    div {
+      width: 300px;
+      height: 80px;
+      background-color: #f88;
+      background-repeat: no-repeat;
+    }
+  </style>
+  <script>
+    if (window.testRunner) {
+      window.testRunner.waitUntilDone();
+    }
+
+    var image = new Image();
+    var image_name = 'cobalt_opaque.jpg';
+
+    image.onload = function() {
+      var image_elements = document.getElementsByClassName('image');
+      for (var i = 0; i < image_elements.length; ++i) {
+        image_elements[i].style.backgroundImage =
+            'url(' + image_name + ')';
+      }
+
+      if (window.testRunner) {
+        window.testRunner.notifyDone();
+      }
+    }
+
+    image.src = image_name;
+
+</script>
+</head>
+<body>
+  <div class='image' style="background-repeat: repeat;"></div>
+  <div class='image' style="width: 100px; background-size: cover;"></div>
+  <div class='image' style="background-size: contain;"></div>
+  <!-- Matches the image's dimensions exactly. -->
+  <div class='image'
+       style="width: 87px; height: 100px; background-size: contain;"></div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg b/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg
new file mode 100644
index 0000000..a1dc59e
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt
index e5e5ccb..45cffd4 100644
--- a/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt
@@ -27,6 +27,7 @@
 14-2-1-background-with-image-repeat-color-position
 14-2-1-background-with-image-repeat-position
 14-2-1-background-with-image-repeat-position-color
+14-2-1-background-with-opaque-image-color
 14-2-1-background-with-position-color
 14-2-1-background-with-position-color-image
 14-2-1-background-with-position-color-image-repeat
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html
index ce75b15..d50693a 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html
@@ -34,11 +34,12 @@
     }
     @font-face {
       font-family: Roboto;
-      src: local(cursive);
+      src: local(DancingScript);  /* Try using a Postscript name of a typeface.
+                                   */
     }
     @font-face {
       font-family: serif;
-      src: local(casual);
+      src: local(Coming Soon);  /* Try using a Full Name of a typeface. */
     }
   </style>
 </head>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html
index 18b941d..0d87b10 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html
@@ -37,25 +37,25 @@
     }
     @font-face {
       font-family: font-face-1;
-      src: local(cursive);
+      src: local(DancingScript);  /* Lookup using font's postscript name. */
     }
     @font-face {
       font-family: font-face-2;
       src: local(invalid1),
-           local(casual);
+           local(Coming Soon);  /* Lookup using font's full name. */
     }
     @font-face {
       font-family: font-face-3;
       src: local(invalid1),
            local(invalid2),
-           local(sans-serif-smallcaps);
+           local(CarroisGothicSC-Regular);
     }
     @font-face {
       font-family: font-face-4;
       src: local(invalid1),
-           local(cursive),
+           local(DancingScript),  /* Looking using font's postscript name. */
            local(invalid2),
-           local(casual),
+           local(Coming Soon),  /* Lookup using font's full name. */
            local(invalid3);
     }
   </style>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html
index 593ccdf..fe1ac19 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html
@@ -39,12 +39,12 @@
     }
     @font-face {
       font-family: font-face-1;
-      src: local(cursive);
+      src: local(DancingScript);  /* Lookup using font's postscript name. */
     }
     @font-face {
       font-family: font-face-2;
       src: local(invalid1),
-           local(casual);
+           local(Coming Soon);  /* Lookup using font's full name. */
     }
     @font-face {
       font-family: font-face-3;
@@ -55,9 +55,9 @@
     @font-face {
       font-family: font-face-4;
       src: local(invalid1),
-           local(cursive),
+           local(DancingScript),  /* Lookup using font's postscript name. */
            local(invalid2),
-           local(casual),
+           local(Coming Soon),  /* Lookup using font's full name. */
            local(invalid3);
     }
   </style>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png
index 0e42727..e894c76 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html
index 74a3b50..b9bd6d1 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html
@@ -67,29 +67,29 @@
     }
     @font-face {
       font-family: font-face-1;
-      src: local(Roboto);
+      src: local(Roboto-Regular);
     }
     @font-face {
       font-family: font-face-2;
-      src: local(cursive);
+      src: local(DancingScript);
       font-weight: normal;
       font-style: normal;
     }
     @font-face {
       font-family: font-face-2;
-      src: local(casual);
+      src: local(Coming Soon);
       font-weight: normal;
       font-style: italic;
     }
     @font-face {
       font-family: font-face-2;
-      src: local(sans-serif-smallcaps);
+      src: local(CarroisGothicSC-Regular);
       font-weight: bold;
       font-style: normal;
     }
     @font-face {
       font-family: font-face-2;
-      src: local(monospace);
+      src: local(Droid Sans Mono);
       font-weight: bold;
       font-style: italic;
     }
@@ -98,9 +98,13 @@
 <body>
   <div class="containing-block">
     <div class="font-face-1-normal-normal">The Hegemony Consul</div>
+    <!-- Note: font-face-1 selects a local font file, which only has an regular
+      style.  So, for the next three cases, a normal style is rendered.
+    -->
     <div class="font-face-1-normal-italic">The Hegemony Consul</div>
     <div class="font-face-1-bold-normal">The Hegemony Consul</div>
     <div class="font-face-1-bold-italic">The Hegemony Consul</div>
+
     <div class="font-face-2-normal-normal">The Hegemony Consul</div>
     <div class="font-face-2-normal-italic">The Hegemony Consul</div>
     <div class="font-face-2-bold-normal">The Hegemony Consul</div>
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png
new file mode 100644
index 0000000..39205b9
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html
new file mode 100644
index 0000000..4972e3d
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to left, yellow 20%, purple 70%);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png
new file mode 100644
index 0000000..c2bdcce
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png
new file mode 100644
index 0000000..c2bdcce
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html
new file mode 100644
index 0000000..03019b7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to left, yellow, purple, green);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png
new file mode 100644
index 0000000..ac7cae8
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html
new file mode 100644
index 0000000..36cdbcb
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to bottom, yellow, transparent);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png
new file mode 100644
index 0000000..996008f
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html
new file mode 100644
index 0000000..b161dbd
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to left, yellow, transparent);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png
new file mode 100644
index 0000000..c96bfe7
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html
new file mode 100644
index 0000000..e56d490
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to right, yellow, transparent);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png
new file mode 100644
index 0000000..e4ec68c
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html
new file mode 100644
index 0000000..683c1da
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    div {
+      background: linear-gradient(to top, yellow, transparent);
+      width: 100px;
+      height: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
+
diff --git a/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt
index d2988a2..6abfa12 100644
--- a/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt
@@ -1,9 +1,15 @@
 4-1-2-corner-to-corner-linear-gradient
+4-1-2-gradient-in-middle
 4-1-2-linear-gradient-with-same-stop-positions
 4-1-2-linear-gradient-with-transparency
+4-1-2-multistop-one-dimensional-gradient
 4-1-2-simple-yellow-to-blue-arbitrary-angle-linear-gradient
 4-1-2-simple-yellow-to-blue-vertical-linear-gradient
 4-1-2-three-color-linear-gradient
+4-1-2-to-bottom-linear-gradient
+4-1-2-to-left-linear-gradient
+4-1-2-to-right-linear-gradient
+4-1-2-to-top-linear-gradient
 4-2-4-closest-side-radial-gradient
 4-2-4-radial-gradient-non-centered
 4-2-4-radial-gradient-with-transparency
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..9b40c3f
--- /dev/null
+++ b/src/cobalt/loader/blob_fetcher.h
@@ -0,0 +1,61 @@
+/*
+ * 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:
+  // This callback avoids a dependency from the fetcher to the actual blob
+  // implementation.
+  // If the blob is succesfully fetched:
+  //   1. Writes the size of its buffer to |size|.
+  //   2. If |size| > 0, then it also writes the address of the non-empty buffer
+  //      to |data|, otherwise writes NULL to |data|.
+  //   3. Returns true.
+  // If the fetch fails:
+  //   1. Writes 0 to |size|
+  //   2. Writes NULL to |data|
+  //   3. Returns false.
+  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..e2baea3
--- /dev/null
+++ b/src/cobalt/loader/blob_fetcher_test.cc
@@ -0,0 +1,140 @@
+/*
+ * 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);
+
+  *size = 0;
+  *data = NULL;
+
+  TestRegistry::const_iterator match = registry.find(url.spec());
+  if (match != registry.end()) {
+    const std::vector<char>& buffer = match->second;
+    *size = buffer.size();
+
+    if (!buffer.empty()) {
+      *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/embedded_resources/splash_screen.css b/src/cobalt/loader/embedded_resources/splash_screen.css
index 0af40bf..3cba6e4 100644
--- a/src/cobalt/loader/embedded_resources/splash_screen.css
+++ b/src/cobalt/loader/embedded_resources/splash_screen.css
@@ -4,11 +4,11 @@
 }
 
 #splash {
-  background-color: #e62d27;
+  background-color: #e62117;
   background-image: url("h5vcc-embedded://you_tube_logo.png");
   background-position: center center;
   background-repeat: no-repeat;
-  background-size: 60%;
+  background-size: 100%;
   height: 100%;
   left: 0;
   position: absolute;
@@ -22,13 +22,12 @@
   width: 100%;
 }
 
-@keyframes loadwait {
-  0% {opacity: 0}
-  100% {opacity: 1}
-}
-
 #spinner {
-  animation: loadwait 2s steps(1, end);
+  /* The spinner starts with display set to none, and JavaScript will set this
+     to 'block' after some time has passed, if the splash screen is still
+     visible. */
+  display: none;
+
   height: 5.33em;
   margin: 0 auto;
   position: relative;
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.html b/src/cobalt/loader/embedded_resources/splash_screen.html
index 2a572ed..0e57e3b 100644
--- a/src/cobalt/loader/embedded_resources/splash_screen.html
+++ b/src/cobalt/loader/embedded_resources/splash_screen.html
@@ -3,8 +3,9 @@
 
 <head>
 <meta http-equiv="Content-Security-Policy" content="default-src 'none';
+    script-src h5vcc-embedded://*/splash_screen.js;
     style-src h5vcc-embedded://*/splash_screen.css;
-    img-src h5vcc-embedded://*/you_tube_logo.png">
+    img-src h5vcc-embedded://*/you_tube_logo.png;">
 <link rel="stylesheet" type="text/css" href="h5vcc-embedded://splash_screen.css">
 </head>
 
@@ -24,6 +25,8 @@
       <div class="dot" id="dot8"></div>
     </div>
   </div>
+  <script type="text/javascript" src="h5vcc-embedded://splash_screen.js">
+  </script>
 </body>
 
 </html>
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.js b/src/cobalt/loader/embedded_resources/splash_screen.js
new file mode 100644
index 0000000..0d30edc
--- /dev/null
+++ b/src/cobalt/loader/embedded_resources/splash_screen.js
@@ -0,0 +1,5 @@
+  // Enable the spinner after 3 seconds have passed.
+  window.setTimeout(function() {
+    var spinner = document.getElementById('spinner');
+    spinner.style.display = 'block';
+  }, 3000);
diff --git a/src/cobalt/loader/embedded_resources/you_tube_logo.png b/src/cobalt/loader/embedded_resources/you_tube_logo.png
index 0d2082d..a964097 100644
--- a/src/cobalt/loader/embedded_resources/you_tube_logo.png
+++ b/src/cobalt/loader/embedded_resources/you_tube_logo.png
Binary files differ
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.cc b/src/cobalt/loader/file_fetcher.cc
index 1cbe61f..83ee7a6 100644
--- a/src/cobalt/loader/file_fetcher.cc
+++ b/src/cobalt/loader/file_fetcher.cc
@@ -40,7 +40,7 @@
 
   // Ensure the request does not attempt to navigate outside the whitelisted
   // directory.
-  if (file_path_.ReferencesParent()) {
+  if (file_path_.ReferencesParent() || file_path_.IsAbsolute()) {
     handler->OnError(this, PlatformFileErrorToString(
                                base::PLATFORM_FILE_ERROR_ACCESS_DENIED));
     return;
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/dummy_gif_image_decoder.h b/src/cobalt/loader/image/dummy_gif_image_decoder.h
index 5c3775c..3ea5b38 100644
--- a/src/cobalt/loader/image/dummy_gif_image_decoder.h
+++ b/src/cobalt/loader/image/dummy_gif_image_decoder.h
@@ -38,11 +38,6 @@
   // From ImageDataDecoder
   std::string GetTypeString() const OVERRIDE { return "DummyGIFImageDecoder"; }
 
-  // Returns true if the signature is valid for the particular image type.
-  static bool IsValidSignature(const uint8* header) {
-    return !memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6);
-  }
-
  private:
   // From ImageDataDecoder
   size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
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..ac19988 100644
--- a/src/cobalt/loader/image/image_data_decoder.h
+++ b/src/cobalt/loader/image/image_data_decoder.h
@@ -22,6 +22,9 @@
 
 #include "cobalt/render_tree/image.h"
 #include "cobalt/render_tree/resource_provider.h"
+#if defined(STARBOARD)
+#include "starboard/decode_target.h"
+#endif
 
 namespace cobalt {
 namespace loader {
@@ -44,6 +47,23 @@
     return image_data_.Pass();
   }
 
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  // Starboard version 3 adds support for hardware accelerated image decoding.
+  // In order to make use of this feature, subclasses of ImageDataDecoder may
+  // override this method in order to return an SbDecodeTarget, rather than a
+  // render_tree::ImageData, which could potentially save a copy from CPU
+  // memory to GPU memory, depending on the render_tree implementation and
+  // hardware image decoding functionality available.  If
+  // |RetrieveSbDecodeTarget| returns any value other than the default
+  // |kSbDecodeInvalid|, ImageDecoder will interpret it as a preference
+  // towards SbDecodeTarget rather than render_tree::ImageData.
+  virtual SbDecodeTarget RetrieveSbDecodeTarget() {
+    return kSbDecodeTargetInvalid;
+  }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   void DecodeChunk(const uint8* data, size_t size);
   // Return true if decoding succeeded.
   bool FinishWithSuccess();
@@ -63,7 +83,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/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index 5161dd3..5a55b9c 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -20,6 +20,7 @@
 
 #include "base/debug/trace_event.h"
 #include "cobalt/loader/image/dummy_gif_image_decoder.h"
+#include "cobalt/loader/image/image_decoder_starboard.h"
 #if defined(__LB_PS3__)
 #include "cobalt/loader/image/jpeg_image_decoder_ps3.h"
 #else  // defined(__LB_PS3__)
@@ -30,6 +31,9 @@
 #include "cobalt/loader/image/webp_image_decoder.h"
 #include "net/base/mime_util.h"
 #include "net/http/http_status_code.h"
+#if defined(STARBOARD)
+#include "starboard/image.h"
+#endif
 
 namespace cobalt {
 namespace loader {
@@ -49,6 +53,41 @@
   result->append(message);
 }
 
+// The various types of images we support decoding.
+enum ImageType {
+  kImageTypeInvalid,
+  kImageTypeGIF,
+  kImageTypeJPEG,
+  kImageTypePNG,
+  kImageTypeWebP,
+};
+
+// Determine the ImageType of an image from its signature.
+ImageType DetermineImageType(const uint8* header) {
+  if (!memcmp(header, "\xFF\xD8\xFF", 3)) {
+    return kImageTypeJPEG;
+  } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) {
+    return kImageTypeGIF;
+  } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) {
+    return kImageTypePNG;
+  } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) {
+    return kImageTypeWebP;
+  } else {
+    return kImageTypeInvalid;
+  }
+}
+
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+// clang-format off
+SbDecodeTargetFormat kPreferredFormats[] = {
+    kSbDecodeTargetFormat1PlaneRGBA,
+    kSbDecodeTargetFormat1PlaneBGRA,
+};
+// clang-format on
+#endif
+#endif
+
 }  // namespace
 
 ImageDecoder::ImageDecoder(render_tree::ResourceProvider* resource_provider,
@@ -60,6 +99,7 @@
       failure_callback_(failure_callback),
       error_callback_(error_callback),
       state_(kWaitingForHeader) {
+  TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::ImageDecoder()");
   signature_cache_.position = 0;
 
   if (!resource_provider_) {
@@ -70,6 +110,7 @@
 LoadResponseType ImageDecoder::OnResponseStarted(
     Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) {
   UNREFERENCED_PARAMETER(fetcher);
+  TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::OnResponseStarted()");
 
   if (state_ == kSuspended) {
     DLOG(WARNING) << __FUNCTION__ << "[" << this << "] while suspended.";
@@ -91,9 +132,8 @@
     CacheMessage(&failure_message_, "No content returned.");
   }
 
-  std::string mime_style;
-  bool success = headers->GetMimeType(&mime_style);
-  if (!success || !net::IsSupportedImageMimeType(mime_style)) {
+  bool success = headers->GetMimeType(&mime_type_);
+  if (!success || !net::IsSupportedImageMimeType(mime_type_)) {
     state_ = kNotApplicable;
     CacheMessage(&failure_message_, "Not an image mime type.");
   }
@@ -118,11 +158,22 @@
     case kDecoding:
       DCHECK(decoder_);
       if (decoder_->FinishWithSuccess()) {
-        scoped_ptr<render_tree::ImageData> image_data =
-            decoder_->RetrieveImageData();
-        success_callback_.Run(
-            image_data ? resource_provider_->CreateImage(image_data.Pass())
-                       : NULL);
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+        SbDecodeTarget target = decoder_->RetrieveSbDecodeTarget();
+        if (SbDecodeTargetIsValid(target)) {
+          success_callback_.Run(
+              resource_provider_->CreateImageFromSbDecodeTarget(target));
+        } else  // NOLINT
+#endif
+#endif
+        {
+          scoped_ptr<render_tree::ImageData> image_data =
+              decoder_->RetrieveImageData();
+          success_callback_.Run(
+              image_data ? resource_provider_->CreateImage(image_data.Pass())
+                         : NULL);
+        }
       } else {
         error_callback_.Run(decoder_->GetTypeString() +
                             " failed to decode image.");
@@ -153,6 +204,7 @@
 }
 
 bool ImageDecoder::Suspend() {
+  TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Suspend()");
   if (state_ == kDecoding) {
     DCHECK(decoder_);
     decoder_.reset();
@@ -164,6 +216,7 @@
 }
 
 void ImageDecoder::Resume(render_tree::ResourceProvider* resource_provider) {
+  TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Resume()");
   if (state_ != kSuspended) {
     DCHECK_EQ(resource_provider_, resource_provider);
     return;
@@ -178,6 +231,7 @@
 }
 
 void ImageDecoder::DecodeChunkInternal(const uint8* input_bytes, size_t size) {
+  TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::DecodeChunkInternal()");
   switch (state_) {
     case kWaitingForHeader: {
       size_t consumed_size = 0;
@@ -214,6 +268,8 @@
 bool ImageDecoder::InitializeInternalDecoder(const uint8* input_bytes,
                                              size_t size,
                                              size_t* consumed_size) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "ImageDecoder::InitializeInternalDecoder()");
   const size_t index = signature_cache_.position;
   size_t fill_size = std::min(kLengthOfLongestSignature - index, size);
   memcpy(signature_cache_.data + index, input_bytes, fill_size);
@@ -224,26 +280,50 @@
     return false;
   }
 
+  const ImageType image_type = DetermineImageType(signature_cache_.data);
+
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  const char* mime_type_c_string = mime_type_.c_str();
+
+  // Find out if any of our preferred formats are supported for this mime
+  // type.
+  SbDecodeTargetFormat format = kSbDecodeTargetFormatInvalid;
+  for (size_t i = 0; i < SB_ARRAY_SIZE(kPreferredFormats); ++i) {
+    if (SbImageIsDecodeSupported(mime_type_c_string, kPreferredFormats[i])) {
+      format = kPreferredFormats[i];
+      break;
+    }
+  }
+
+  if (SbDecodeTargetIsFormatValid(format) &&
+      resource_provider_->SupportsSbDecodeTarget()) {
+    decoder_ = make_scoped_ptr<ImageDataDecoder>(new ImageDecoderStarboard(
+        resource_provider_, mime_type_c_string, format));
+    return true;
+  }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   // Call different types of decoders by matching the image signature.
   if (s_use_stub_image_decoder) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new StubImageDecoder(resource_provider_));
+  } else if (image_type == kImageTypeJPEG) {
 #if defined(__LB_PS3__)
-  } else if (JPEGImageDecoderPS3::IsValidSignature(signature_cache_.data)) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new JPEGImageDecoderPS3(resource_provider_));
 #else   // defined(__LB_PS3__)
-  } else if (JPEGImageDecoder::IsValidSignature(signature_cache_.data)) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new JPEGImageDecoder(resource_provider_));
 #endif  // defined(__LB_PS3__)
-  } else if (PNGImageDecoder::IsValidSignature(signature_cache_.data)) {
+  } else if (image_type == kImageTypePNG) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new PNGImageDecoder(resource_provider_));
-  } else if (WEBPImageDecoder::IsValidSignature(signature_cache_.data)) {
+  } else if (image_type == kImageTypeWebP) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new WEBPImageDecoder(resource_provider_));
-  } else if (DummyGIFImageDecoder::IsValidSignature(signature_cache_.data)) {
+  } else if (image_type == kImageTypeGIF) {
     decoder_ = make_scoped_ptr<ImageDataDecoder>(
         new DummyGIFImageDecoder(resource_provider_));
   } else {
diff --git a/src/cobalt/loader/image/image_decoder.h b/src/cobalt/loader/image/image_decoder.h
index 610d27b..00902bc 100644
--- a/src/cobalt/loader/image/image_decoder.h
+++ b/src/cobalt/loader/image/image_decoder.h
@@ -89,6 +89,7 @@
   SignatureCache signature_cache_;
   State state_;
   std::string failure_message_;
+  std::string mime_type_;
 };
 
 }  // namespace image
diff --git a/src/cobalt/loader/image/image_decoder_starboard.cc b/src/cobalt/loader/image/image_decoder_starboard.cc
new file mode 100644
index 0000000..1fdb7bf
--- /dev/null
+++ b/src/cobalt/loader/image/image_decoder_starboard.cc
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#if defined(STARBOARD)
+
+#include "cobalt/loader/image/image_decoder_starboard.h"
+
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "starboard/decode_target.h"
+#include "starboard/image.h"
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+ImageDecoderStarboard::ImageDecoderStarboard(
+    render_tree::ResourceProvider* resource_provider, const char* mime_type,
+    SbDecodeTargetFormat format)
+    : ImageDataDecoder(resource_provider),
+      mime_type_(mime_type),
+      format_(format),
+      provider_(resource_provider->GetSbDecodeTargetProvider()),
+      target_(kSbDecodeTargetInvalid) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "ImageDecoderStarboard::ImageDecoderStarboard()");
+}
+
+ImageDecoderStarboard::~ImageDecoderStarboard() {}
+
+size_t ImageDecoderStarboard::DecodeChunkInternal(const uint8* data,
+                                                  size_t input_byte) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "ImageDecoderStarboard::DecodeChunkInternal()");
+
+  buffer_.insert(buffer_.end(), data, data + input_byte);
+  return input_byte;
+}
+
+void ImageDecoderStarboard::FinishInternal() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "ImageDecoderStarboard::FinishInternal()");
+  DCHECK(!buffer_.empty());
+  DCHECK(SbImageIsDecodeSupported(mime_type_, format_));
+  target_ =
+      SbImageDecode(provider_, &buffer_[0], static_cast<int>(buffer_.size()),
+                    mime_type_, format_);
+  if (SbDecodeTargetIsValid(target_)) {
+    set_state(kDone);
+  } else {
+    set_state(kError);
+  }
+}
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+#endif  // #if defined(STARBOARD)
diff --git a/src/cobalt/loader/image/image_decoder_starboard.h b/src/cobalt/loader/image/image_decoder_starboard.h
new file mode 100644
index 0000000..ac14d4c
--- /dev/null
+++ b/src/cobalt/loader/image/image_decoder_starboard.h
@@ -0,0 +1,68 @@
+/*
+ * 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_IMAGE_DECODER_STARBOARD_H_
+#define COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
+
+#if defined(STARBOARD)
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/loader/image/image_data_decoder.h"
+#include "starboard/decode_target.h"
+
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+class ImageDecoderStarboard : public ImageDataDecoder {
+ public:
+  explicit ImageDecoderStarboard(
+      render_tree::ResourceProvider* resource_provider, const char* mime_type,
+      SbDecodeTargetFormat format);
+  ~ImageDecoderStarboard() OVERRIDE;
+
+  // From ImageDataDecoder
+  std::string GetTypeString() const OVERRIDE { return "ImageDecoderStarboard"; }
+
+  SbDecodeTarget RetrieveSbDecodeTarget() OVERRIDE { return target_; }
+
+ private:
+  // From ImageDataDecoder
+  size_t DecodeChunkInternal(const uint8* data, size_t size) OVERRIDE;
+  void FinishInternal() OVERRIDE;
+
+  const char* mime_type_;
+  SbDecodeTargetFormat format_;
+  std::vector<uint8> buffer_;
+  SbDecodeTargetProvider* provider_;
+  SbDecodeTarget target_;
+};
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+#endif  // defined(STARBOARD)
+
+#endif  // COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
diff --git a/src/cobalt/loader/image/jpeg_image_decoder.cc b/src/cobalt/loader/image/jpeg_image_decoder.cc
index a249c7e..29b99dc 100644
--- a/src/cobalt/loader/image/jpeg_image_decoder.cc
+++ b/src/cobalt/loader/image/jpeg_image_decoder.cc
@@ -84,6 +84,7 @@
 JPEGImageDecoder::JPEGImageDecoder(
     render_tree::ResourceProvider* resource_provider)
     : ImageDataDecoder(resource_provider) {
+  TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::JPEGImageDecoder()");
   memset(&info_, 0, sizeof(info_));
   memset(&source_manager_, 0, sizeof(source_manager_));
 
@@ -111,6 +112,8 @@
 
 size_t JPEGImageDecoder::DecodeChunkInternal(const uint8* data,
                                              size_t input_byte) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "JPEGImageDecoder::DecodeChunkInternal()");
   // |client_data| is available for use by application.
   jmp_buf jump_buffer;
   info_.client_data = &jump_buffer;
@@ -175,6 +178,7 @@
 }
 
 bool JPEGImageDecoder::ReadHeader() {
+  TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::ReadHeader()");
   if (jpeg_read_header(&info_, true) == JPEG_SUSPENDED) {
     // Since |jpeg_read_header| doesn't have enough data, go back to the state
     // before reading the header.
@@ -183,11 +187,13 @@
   }
 
   AllocateImageData(math::Size(static_cast<int>(info_.image_width),
-                               static_cast<int>(info_.image_height)));
+                               static_cast<int>(info_.image_height)),
+                    false);
   return true;
 }
 
 bool JPEGImageDecoder::StartDecompress() {
+  TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::StartDecompress()");
   // jpeg_has_multiple_scans() returns TRUE if the incoming image file has more
   // than one scan.
   info_.buffered_image = jpeg_has_multiple_scans(&info_);
@@ -214,6 +220,8 @@
 // TODO: support displaying the low resolution image while decoding
 // the progressive JPEG.
 bool JPEGImageDecoder::DecodeProgressiveJPEG() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "JPEGImageDecoder::DecodeProgressiveJPEG()");
   int status;
   do {
     // |jpeg_consume_input| decodes the input data as it arrives.
@@ -290,6 +298,8 @@
 }
 
 bool JPEGImageDecoder::ReadLines() {
+  TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::ReadLines()");
+
   // Creation of 2-D sample arrays which is for one row.
   // See the comments in jmemmgr.c.
   JSAMPARRAY buffer = (*info_.mem->alloc_sarray)(
diff --git a/src/cobalt/loader/image/jpeg_image_decoder.h b/src/cobalt/loader/image/jpeg_image_decoder.h
index 9d8c487..242fd0e 100644
--- a/src/cobalt/loader/image/jpeg_image_decoder.h
+++ b/src/cobalt/loader/image/jpeg_image_decoder.h
@@ -41,11 +41,6 @@
   // From ImageDataDecoder
   std::string GetTypeString() const OVERRIDE { return "JPEGImageDecoder"; }
 
-  // Returns true if the signature is valid for the particular image type.
-  static bool IsValidSignature(const uint8* header) {
-    return !memcmp(header, "\xFF\xD8\xFF", 3);
-  }
-
  private:
   // From ImageDataDecoder
   size_t DecodeChunkInternal(const uint8* data, size_t size) OVERRIDE;
diff --git a/src/cobalt/loader/image/png_image_decoder.cc b/src/cobalt/loader/image/png_image_decoder.cc
index e048d41..b09e637 100644
--- a/src/cobalt/loader/image/png_image_decoder.cc
+++ b/src/cobalt/loader/image/png_image_decoder.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/loader/image/png_image_decoder.h"
 
+#include "base/debug/trace_event.h"
 #include "base/logging.h"
 
 namespace cobalt {
@@ -72,6 +73,8 @@
       info_(NULL),
       has_alpha_(false),
       interlace_buffer_(0) {
+  TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::PNGImageDecoder()");
+
   png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, DecodingFailed,
                                 DecodingWarning);
   info_ = png_create_info_struct(png_);
@@ -80,6 +83,8 @@
 }
 
 size_t PNGImageDecoder::DecodeChunkInternal(const uint8* data, size_t size) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "PNGImageDecoder::DecodeChunkInternal()");
   // int setjmp(jmp_buf env) saves the current environment (ths program state),
   // at some point of program execution, into a platform-specific data
   // structure (jmp_buf) that can be used at some later point of program
@@ -107,6 +112,7 @@
 }
 
 PNGImageDecoder::~PNGImageDecoder() {
+  TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::~PNGImageDecoder()");
   // Both are created at the same time. So they should be both zero
   // or both non-zero. Use && here to be safer.
   if (png_ && info_) {
@@ -127,6 +133,7 @@
 // static
 void PNGImageDecoder::HeaderAvailable(png_structp png, png_infop info) {
   UNREFERENCED_PARAMETER(info);
+  TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::~PNGImageDecoder()");
   PNGImageDecoder* decoder =
       static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png));
   decoder->HeaderAvailableCallback();
@@ -146,12 +153,16 @@
 // static
 void PNGImageDecoder::DecodeDone(png_structp png, png_infop info) {
   UNREFERENCED_PARAMETER(info);
+  TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::DecodeDone()");
+
   PNGImageDecoder* decoder =
       static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png));
   decoder->DecodeDoneCallback();
 }
 
 void PNGImageDecoder::HeaderAvailableCallback() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "PNGImageDecoder::HeaderAvailableCallback()");
   DCHECK_EQ(state(), kWaitingForHeader);
 
   png_uint_32 width = png_get_image_width(png_, info_);
@@ -236,7 +247,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/png_image_decoder.h b/src/cobalt/loader/image/png_image_decoder.h
index 29a3887..d270087 100644
--- a/src/cobalt/loader/image/png_image_decoder.h
+++ b/src/cobalt/loader/image/png_image_decoder.h
@@ -36,11 +36,6 @@
   // From ImageDataDecoder
   std::string GetTypeString() const OVERRIDE { return "PNGImageDecoder"; }
 
-  // Returns true if the signature is valid for the particular image type.
-  static bool IsValidSignature(const uint8* header) {
-    return !memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8);
-  }
-
  private:
   // From ImageDataDecoder
   size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
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..cff8594 100644
--- a/src/cobalt/loader/image/webp_image_decoder.cc
+++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/loader/image/webp_image_decoder.h"
 
+#include "base/debug/trace_event.h"
 #include "base/logging.h"
 
 namespace cobalt {
@@ -25,6 +26,7 @@
 WEBPImageDecoder::WEBPImageDecoder(
     render_tree::ResourceProvider* resource_provider)
     : ImageDataDecoder(resource_provider), internal_decoder_(NULL) {
+  TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::WEBPImageDecoder()");
   // Initialize the configuration as empty.
   WebPInitDecoderConfig(&config_);
   // Skip the in-loop filtering.
@@ -37,10 +39,16 @@
   config_.options.no_enhancement = 1;
 }
 
-WEBPImageDecoder::~WEBPImageDecoder() { DeleteInternalDecoder(); }
+WEBPImageDecoder::~WEBPImageDecoder() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "WEBPImageDecoder::~WEBPImageDecoder()");
+  DeleteInternalDecoder();
+}
 
 size_t WEBPImageDecoder::DecodeChunkInternal(const uint8* data,
                                              size_t input_byte) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "WEBPImageDecoder::DecodeChunkInternal()");
   const uint8* next_input_byte = data;
   size_t bytes_in_buffer = input_byte;
 
@@ -81,6 +89,7 @@
 
 bool WEBPImageDecoder::ReadHeader(const uint8* data, size_t size,
                                   bool* has_alpha) {
+  TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::ReadHeader()");
   // Retrieve features from the bitstream. The *features structure is filled
   // with information gathered from the bitstream.
   // Returns VP8_STATUS_OK when the features are successfully retrieved. Returns
@@ -92,7 +101,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.
@@ -105,6 +114,8 @@
 }
 
 bool WEBPImageDecoder::CreateInternalDecoder(bool has_alpha) {
+  TRACE_EVENT0("cobalt::loader::image",
+               "WEBPImageDecoder::CreateInternalDecoder()");
   config_.output.colorspace = has_alpha ? MODE_rgbA : MODE_RGBA;
   config_.output.u.RGBA.stride = image_data()->GetDescriptor().pitch_in_bytes;
   config_.output.u.RGBA.size =
@@ -127,6 +138,8 @@
 }
 
 void WEBPImageDecoder::DeleteInternalDecoder() {
+  TRACE_EVENT0("cobalt::loader::image",
+               "WEBPImageDecoder::DeleteInternalDecoder()");
   if (internal_decoder_) {
     // Deletes the WebPIDecoder object and associated memory. Must always be
     // called if WebPIDecode succeeded.
diff --git a/src/cobalt/loader/image/webp_image_decoder.h b/src/cobalt/loader/image/webp_image_decoder.h
index da9ce59..187ca62 100644
--- a/src/cobalt/loader/image/webp_image_decoder.h
+++ b/src/cobalt/loader/image/webp_image_decoder.h
@@ -34,11 +34,6 @@
   // From ImageDataDecoder
   std::string GetTypeString() const OVERRIDE { return "WEBPImageDecoder"; }
 
-  // Returns true if the signature is valid for the particular image type.
-  static bool IsValidSignature(const uint8* header) {
-    return !memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6);
-  }
-
  private:
   // From ImageDataDecoder
   size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
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..f3460d8 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',
@@ -40,11 +42,15 @@
         'image/image_data_decoder.h',
         'image/image_decoder.cc',
         'image/image_decoder.h',
+        'image/image_decoder_starboard.cc',
+        'image/image_decoder_starboard.h',
         'image/jpeg_image_decoder.cc',
         'image/jpeg_image_decoder.h',
         '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 +97,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/mock_loader_factory.h b/src/cobalt/loader/mock_loader_factory.h
new file mode 100644
index 0000000..b5314d6
--- /dev/null
+++ b/src/cobalt/loader/mock_loader_factory.h
@@ -0,0 +1,82 @@
+/*
+ * 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_MOCK_LOADER_FACTORY_H_
+#define COBALT_LOADER_MOCK_LOADER_FACTORY_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/csp/content_security_policy.h"
+#include "cobalt/loader/font/typeface_decoder.h"
+#include "cobalt/loader/image/image_decoder.h"
+#include "cobalt/loader/loader.h"
+#include "cobalt/loader/loader_factory.h"
+#include "googleurl/src/gurl.h"
+
+namespace cobalt {
+namespace loader {
+
+class MockLoaderFactory {
+ public:
+  MOCK_METHOD5(CreateImageLoaderMock,
+               loader::Loader*(
+                   const GURL& url,
+                   const csp::SecurityCallback& url_security_callback,
+                   const image::ImageDecoder::SuccessCallback& success_callback,
+                   const image::ImageDecoder::FailureCallback& failure_callback,
+                   const image::ImageDecoder::ErrorCallback& error_callback));
+
+  MOCK_METHOD5(
+      CreateTypefaceLoaderMock,
+      loader::Loader*(
+          const GURL& url, const csp::SecurityCallback& url_security_callback,
+          const font::TypefaceDecoder::SuccessCallback& success_callback,
+          const font::TypefaceDecoder::FailureCallback& failure_callback,
+          const font::TypefaceDecoder::ErrorCallback& error_callback));
+
+  // This workaround is required since scoped_ptr has no copy constructor.
+  // see:
+  // https://groups.google.com/a/chromium.org/forum/#!msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ
+  scoped_ptr<Loader> CreateImageLoader(
+      const GURL& url, const csp::SecurityCallback& url_security_callback,
+      const image::ImageDecoder::SuccessCallback& success_callback,
+      const image::ImageDecoder::FailureCallback& failure_callback,
+      const image::ImageDecoder::ErrorCallback& error_callback) {
+    return scoped_ptr<Loader>(
+        CreateImageLoaderMock(url, url_security_callback, success_callback,
+                              failure_callback, error_callback));
+  }
+
+  scoped_ptr<Loader> CreateTypefaceLoader(
+      const GURL& url, const csp::SecurityCallback& url_security_callback,
+      const font::TypefaceDecoder::SuccessCallback& success_callback,
+      const font::TypefaceDecoder::FailureCallback& failure_callback,
+      const font::TypefaceDecoder::ErrorCallback& error_callback) {
+    return scoped_ptr<Loader>(
+        CreateTypefaceLoaderMock(url, url_security_callback, success_callback,
+                                 failure_callback, error_callback));
+  }
+
+  MOCK_METHOD0(Suspend, void());
+  MOCK_METHOD1(Resume, void(render_tree::ResourceProvider* resource_provider));
+};
+
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_MOCK_LOADER_FACTORY_H_
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc
index 4ba9d17..efb946e 100644
--- a/src/cobalt/loader/net_fetcher.cc
+++ b/src/cobalt/loader/net_fetcher.cc
@@ -21,12 +21,46 @@
 #include "base/stringprintf.h"
 #include "cobalt/network/network_module.h"
 #include "net/url_request/url_fetcher.h"
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#define HANDLE_CORE_DUMP
+#include "base/lazy_instance.h"
+#include "starboard/ps4/core_dump_handler.h"
+#endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#endif  // OS_STARBOARD
 
 namespace cobalt {
 namespace loader {
 
 namespace {
 
+#if defined(HANDLE_CORE_DUMP)
+
+class NetFetcherLog {
+ public:
+  NetFetcherLog() : total_fetched_bytes_(0) {
+    SbCoreDumpRegisterHandler(CoreDumpHandler, this);
+  }
+  ~NetFetcherLog() { SbCoreDumpUnregisterHandler(CoreDumpHandler, this); }
+
+  static void CoreDumpHandler(void* context) {
+    SbCoreDumpLogInteger(
+        "NetFetcher total fetched bytes",
+        static_cast<NetFetcherLog*>(context)->total_fetched_bytes_);
+  }
+
+  void IncrementFetchedBytes(int length) { total_fetched_bytes_ += length; }
+
+ private:
+  int total_fetched_bytes_;
+  DISALLOW_COPY_AND_ASSIGN(NetFetcherLog);
+};
+
+base::LazyInstance<NetFetcherLog> net_fetcher_log = LAZY_INSTANCE_INITIALIZER;
+
+#endif  // defined(HANDLE_CORE_DUMP)
+
 bool IsResponseCodeSuccess(int response_code) {
   // NetFetcher only considers success to be if the network request
   // was successful *and* we get a 2xx response back.
@@ -47,32 +81,33 @@
                        const Options& options)
     : Fetcher(handler),
       security_callback_(security_callback),
-      ALLOW_THIS_IN_INITIALIZER_LIST(start_callback_(
-          base::Bind(&NetFetcher::Start, base::Unretained(this)))) {
+      ALLOW_THIS_IN_INITIALIZER_LIST(csp_reject_callback_(
+          base::Bind(&NetFetcher::ProcessCSPReject, base::Unretained(this)))) {
   url_fetcher_.reset(
       net::URLFetcher::Create(url, options.request_method, this));
   url_fetcher_->SetRequestContext(network_module->url_request_context_getter());
   url_fetcher_->DiscardResponse();
 
-  // Delay the actual start until this function is complete. Otherwise we might
-  // call handler's callbacks at an unexpected time- e.g. receiving OnError()
-  // while a loader is still being constructed.
-  MessageLoop::current()->PostTask(FROM_HERE, start_callback_.callback());
-}
-
-void NetFetcher::Start() {
-  DCHECK(thread_checker_.CalledOnValidThread());
   const GURL& original_url = url_fetcher_->GetOriginalURL();
   if (security_callback_.is_null() ||
       security_callback_.Run(original_url, false /* did not redirect */)) {
     url_fetcher_->Start();
   } else {
-    std::string msg(base::StringPrintf("URL %s rejected by security policy.",
-                                       original_url.spec().c_str()));
-    return HandleError(msg).InvalidateThis();
+    // Delay the callback until this function is complete. Otherwise we might
+    // call handler's callbacks at an unexpected time- e.g. receiving OnError()
+    // while a loader is still being constructed.
+    MessageLoop::current()->PostTask(FROM_HERE,
+                                     csp_reject_callback_.callback());
   }
 }
 
+void NetFetcher::ProcessCSPReject() {
+  const GURL& original_url = url_fetcher_->GetOriginalURL();
+  std::string msg(base::StringPrintf("URL %s rejected by security policy.",
+                                     original_url.spec().c_str()));
+  return HandleError(msg).InvalidateThis();
+}
+
 void NetFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (source->GetURL() != source->GetOriginalURL()) {
@@ -118,13 +153,17 @@
 void NetFetcher::OnURLFetchDownloadData(const net::URLFetcher* source,
                                         scoped_ptr<std::string> download_data) {
   if (IsResponseCodeSuccess(source->GetResponseCode())) {
-    handler()->OnReceived(this, download_data->data(), download_data->length());
+#if defined(HANDLE_CORE_DUMP)
+    net_fetcher_log.Get().IncrementFetchedBytes(
+        static_cast<int>(download_data->length()));
+#endif
+    handler()->OnReceivedPassed(this, download_data.Pass());
   }
 }
 
 NetFetcher::~NetFetcher() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  start_callback_.Cancel();
+  csp_reject_callback_.Cancel();
 }
 
 NetFetcher::ReturnWrapper NetFetcher::HandleError(const std::string& message) {
diff --git a/src/cobalt/loader/net_fetcher.h b/src/cobalt/loader/net_fetcher.h
index ce816b5..1d6bc5d 100644
--- a/src/cobalt/loader/net_fetcher.h
+++ b/src/cobalt/loader/net_fetcher.h
@@ -59,7 +59,7 @@
   net::URLFetcher* url_fetcher() const { return url_fetcher_.get(); }
 
  private:
-  void Start();
+  void ProcessCSPReject();
 
   // Empty struct to ensure the caller of |HandleError()| knows that |this|
   // may have been destroyed and handles it appropriately.
@@ -81,9 +81,9 @@
   base::ThreadChecker thread_checker_;
   scoped_ptr<net::URLFetcher> url_fetcher_;
   csp::SecurityCallback security_callback_;
-  // Ensure we can cancel any in-flight Start() task if we are destroyed
-  // after being constructed, but before Start() runs.
-  base::CancelableClosure start_callback_;
+  // Ensure we can cancel any in-flight ProcessCSPReject() task if we are
+  // destroyed after being constructed, but before ProcessCSPReject() runs.
+  base::CancelableClosure csp_reject_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(NetFetcher);
 };
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 2f4ce23..1c3e4e6 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -47,6 +47,13 @@
 template <typename CacheType>
 class ResourceCache;
 
+enum CallbackType {
+  kOnLoadingSuccessCallbackType,
+  kOnLoadingFailureCallbackType,
+  kOnLoadingErrorCallbackType,
+  kCallbackTypeCount,
+};
+
 //////////////////////////////////////////////////////////////////////////
 // CachedResource - Declarations
 //////////////////////////////////////////////////////////////////////////
@@ -121,17 +128,11 @@
  private:
   friend class base::RefCountedThreadSafe<CachedResource>;
   friend class OnLoadedCallbackHandler;
+  friend class ResourceCache<CacheType>;
 
   typedef std::list<base::Closure> CallbackList;
   typedef std::list<base::Closure>::iterator CallbackListIterator;
 
-  enum CallbackType {
-    kOnLoadingSuccessCallbackType,
-    kOnLoadingFailureCallbackType,
-    kOnLoadingErrorCallbackType,
-    kCallbackTypeCount,
-  };
-
   ~CachedResource();
 
   // Callbacks for decoders.
@@ -274,10 +275,8 @@
   resource_ = resource;
 
   loader_.reset();
-  resource_cache_->NotifyResourceSuccessfullyLoaded(this);
-  // To avoid the last reference of this object get deleted in the callbacks.
-  scoped_refptr<CachedResource<CacheType> > holder(this);
-  RunCallbacks(kOnLoadingSuccessCallbackType);
+  resource_cache_->NotifyResourceLoadingComplete(this,
+                                                 kOnLoadingSuccessCallbackType);
 }
 
 template <typename CacheType>
@@ -287,9 +286,8 @@
   LOG(WARNING) << "Warning while loading '" << url_ << "': " << message;
 
   loader_.reset();
-  // To avoid the last reference of this object get deleted in the callbacks.
-  scoped_refptr<CachedResource<CacheType> > holder(this);
-  RunCallbacks(kOnLoadingFailureCallbackType);
+  resource_cache_->NotifyResourceLoadingComplete(this,
+                                                 kOnLoadingFailureCallbackType);
 }
 
 template <typename CacheType>
@@ -299,9 +297,8 @@
   LOG(ERROR) << "Error while loading '" << url_ << "': " << error;
 
   loader_.reset();
-  // To avoid the last reference of this object get deleted in the callbacks.
-  scoped_refptr<CachedResource<CacheType> > holder(this);
-  RunCallbacks(kOnLoadingErrorCallbackType);
+  resource_cache_->NotifyResourceLoadingComplete(this,
+                                                 kOnLoadingErrorCallbackType);
 }
 
 template <typename CacheType>
@@ -328,7 +325,7 @@
 void CachedResource<CacheType>::RunCallbacks(CallbackType type) {
   DCHECK(cached_resource_thread_checker_.CalledOnValidThread());
 
-  // To avoid the list gets altered in the callbacks.
+  // To avoid the list getting altered in the callbacks.
   CallbackList callback_list = callback_lists[type];
   CallbackListIterator callback_iter;
   for (callback_iter = callback_list.begin();
@@ -391,6 +388,15 @@
   typedef
       typename CachedResourceType::CreateLoaderFunction CreateLoaderFunction;
 
+  struct ResourceCallbackInfo {
+    ResourceCallbackInfo(CachedResourceType* cached_resource,
+                         CallbackType callback_type)
+        : cached_resource(cached_resource), callback_type(callback_type) {}
+
+    CachedResourceType* cached_resource;
+    CallbackType callback_type;
+  };
+
   ResourceCache(const std::string& name, uint32 cache_capacity,
                 const CreateLoaderFunction& create_loader_function);
 
@@ -421,12 +427,17 @@
   typedef base::hash_map<std::string, CachedResourceType*> CachedResourceMap;
   typedef typename CachedResourceMap::iterator CachedResourceMapIterator;
 
+  typedef base::hash_set<std::string> ResourceSet;
+  typedef base::linked_hash_map<std::string, ResourceCallbackInfo>
+      ResourceCallbackMap;
+
   typedef base::linked_hash_map<std::string, scoped_refptr<ResourceType> >
       ResourceMap;
   typedef typename ResourceMap::iterator ResourceMapIterator;
 
-  // Called by CachedResource objects after they are successfully loaded.
-  void NotifyResourceSuccessfullyLoaded(CachedResourceType* cached_resource);
+  // Called by CachedResource objects after they finish loading.
+  void NotifyResourceLoadingComplete(CachedResourceType* cached_resource,
+                                     CallbackType callback_type);
 
   // Called by the destructor of CachedResource to remove CachedResource from
   // |cached_resource_map_| and either immediately free the resource from memory
@@ -434,10 +445,24 @@
   // cache is over its memory limit.
   void NotifyResourceDestroyed(CachedResourceType* cached_resource);
 
+  // Reclaims memory from unreferenced cache objects until total cache memory
+  // is reduced to |bytes_to_reclaim_down_to|. In the case where the desired
+  // memory cannot be freed, pending callbacks are processed (potentially
+  // enabling additional resources to be reclaimed), and memory reclamation is
+  // attempted again.
+  void ReclaimMemoryAndMaybeProcessPendingCallbacks(
+      uint32 bytes_to_reclaim_down_to);
   // Releases unreferenced cache objects until our total cache memory usage is
   // less than or equal to |bytes_to_reclaim_down_to|, or until there are no
   // more unreferenced cache objects to release.
-  void ReclaimMemory(uint32 bytes_to_reclaim_down_to);
+  void ReclaimMemory(uint32 bytes_to_reclaim_down_to, bool log_warning_if_over);
+
+  // Calls ProcessPendingCallbacks() if
+  // |callback_blocking_loading_resource_set_| is empty.
+  void ProcessPendingCallbacksIfUnblocked();
+  // Processes all pending callbacks regardless of the state of
+  // |callback_blocking_loading_resource_set_|.
+  void ProcessPendingCallbacks();
 
   // The name of this resource cache object, useful while debugging.
   const std::string name_;
@@ -448,6 +473,28 @@
 
   csp::SecurityCallback security_callback_;
 
+  // The resource cache attempts to batch callbacks as much as possible to try
+  // to ensure that events triggered by the callbacks occur together. It
+  // accomplishes this by waiting for all active loads to complete before
+  // processing any of their callbacks. However, to ensure that callbacks are
+  // processed in a timely manner as well, active loads are placed into two
+  // buckets: callback blocking and non-callback blocking. While no callbacks
+  // are pending, all active loads are added as callback blocking. As soon as
+  // a callback is pending, any additional load requests are added as
+  // non-callback blocking. As soon as all of the callback blocking loads are
+  // finished, the pending callbacks are processed, the non-callback blocking
+  // loads become callback blocking loads, and the process repeats itself.
+
+  // Currently loading resources that block any pending callbacks from running.
+  ResourceSet callback_blocking_loading_resource_set_;
+  // Currently loading resources that do not block the pending callbacks from
+  // running. After pending callbacks run, these become blocking.
+  ResourceSet non_callback_blocking_loading_resource_set_;
+  // Resources that have completed loading and have callbacks pending.
+  ResourceCallbackMap pending_callback_map_;
+  // Whether or not ProcessPendingCallbacks() is running.
+  bool is_processing_pending_callbacks_;
+
   // |cached_resource_map_| stores the cached resources that are currently
   // referenced.
   CachedResourceMap cached_resource_map_;
@@ -476,6 +523,7 @@
     : name_(name),
       cache_capacity_(cache_capacity),
       create_loader_function_(create_loader_function),
+      is_processing_pending_callbacks_(false),
       size_in_bytes_(base::StringPrintf("%s.Size", name_.c_str()), 0,
                      "Total number of bytes currently used by the cache."),
       capacity_in_bytes_(base::StringPrintf("%s.Capacity", name_.c_str()),
@@ -512,8 +560,21 @@
     return cached_resource;
   }
 
-  // If the resource doesn't exist, create a cached resource and fetch the
-  // resource based on the url.
+  // If we reach this point, then the resource doesn't exist yet.
+
+  // Add the resource to a loading set. If no current resources have pending
+  // callbacks, then this resource will block callbacks until it is decoded.
+  // However, if there are resources with pending callbacks, then the decoding
+  // of this resource won't block the callbacks from occurring. This ensures
+  // that a steady stream of new resources won't prevent callbacks from ever
+  // occurring.
+  if (pending_callback_map_.empty()) {
+    callback_blocking_loading_resource_set_.insert(url.spec());
+  } else {
+    non_callback_blocking_loading_resource_set_.insert(url.spec());
+  }
+
+  // Create the cached resource and fetch its resource based on the url.
   scoped_refptr<CachedResourceType> cached_resource(new CachedResourceType(
       url, security_callback_, create_loader_function_, this));
   cached_resource_map_.insert(
@@ -526,55 +587,106 @@
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
   cache_capacity_ = capacity;
   capacity_in_bytes_ = capacity;
-  ReclaimMemory(cache_capacity_);
+  ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
 }
 
 template <typename CacheType>
 void ResourceCache<CacheType>::Purge() {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
-  ReclaimMemory(0);
+  ReclaimMemoryAndMaybeProcessPendingCallbacks(0);
 }
 
 template <typename CacheType>
-void ResourceCache<CacheType>::NotifyResourceSuccessfullyLoaded(
-    CachedResourceType* cached_resource) {
+void ResourceCache<CacheType>::NotifyResourceLoadingComplete(
+    CachedResourceType* cached_resource, CallbackType callback_type) {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+  const std::string& url = cached_resource->url().spec();
 
   if (cached_resource->TryGetResource()) {
     size_in_bytes_ +=
         CacheType::GetEstimatedSizeInBytes(cached_resource->TryGetResource());
-    if (size_in_bytes_ > cache_capacity_) {
-      ReclaimMemory(cache_capacity_);
-    }
   }
+
+  // Remove the resource from its loading set. It should exist in exactly one
+  // of the loading sets.
+  if (callback_blocking_loading_resource_set_.erase(url)) {
+    DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
+           non_callback_blocking_loading_resource_set_.end());
+  } else if (!non_callback_blocking_loading_resource_set_.erase(url)) {
+    DCHECK(false);
+  }
+
+  // Add a callback for the resource that just finished loading to the pending
+  // callbacks.
+  pending_callback_map_.insert(std::make_pair(
+      url, ResourceCallbackInfo(cached_resource, callback_type)));
+
+  ProcessPendingCallbacksIfUnblocked();
+  ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
 }
 
 template <typename CacheType>
 void ResourceCache<CacheType>::NotifyResourceDestroyed(
     CachedResourceType* cached_resource) {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+  const std::string& url = cached_resource->url().spec();
 
-  std::string url = cached_resource->url().spec();
   cached_resource_map_.erase(url);
 
   DCHECK(unreference_cached_resource_map_.find(url) ==
          unreference_cached_resource_map_.end());
+
   // Check to see if this was a loaded resource.
   if (cached_resource->TryGetResource()) {
     // Add it into the unreferenced cached resource map, so that it will be
     // retained while memory is available for it in the cache.
     unreference_cached_resource_map_.insert(
         std::make_pair(url, cached_resource->TryGetResource()));
-    // Try to reclaim some memory.
-    ReclaimMemory(cache_capacity_);
+  }
+
+  // Remove the resource from any loading or pending container that it is in.
+  // It should never exist in more than one of the containers.
+  if (callback_blocking_loading_resource_set_.erase(url)) {
+    DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
+           non_callback_blocking_loading_resource_set_.end());
+    DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
+  } else if (non_callback_blocking_loading_resource_set_.erase(url)) {
+    DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
+  } else {
+    pending_callback_map_.erase(url);
+  }
+
+  // Only process pending callbacks and attempt to reclaim memory if
+  // NotifyResourceDestroyed() wasn't called from within
+  // ProcessPendingCallbacks(). This prevents recursion and redundant
+  // processing.
+  if (!is_processing_pending_callbacks_) {
+    ProcessPendingCallbacksIfUnblocked();
+    ReclaimMemory(cache_capacity_, true /*log_warning_if_over*/);
   }
 }
 
 template <typename CacheType>
-void ResourceCache<CacheType>::ReclaimMemory(uint32 bytes_to_reclaim_down_to) {
+void ResourceCache<CacheType>::ReclaimMemoryAndMaybeProcessPendingCallbacks(
+    uint32 bytes_to_reclaim_down_to) {
+  ReclaimMemory(bytes_to_reclaim_down_to, false /*log_warning_if_over*/);
+  // If the current size of the cache is still greater than
+  // |bytes_to_reclaim_down_to| after reclaiming memory, then process any
+  // pending callbacks and try again. References to the cached resources are
+  // potentially being held until the callbacks run, so processing them may
+  // enable more memory to be reclaimed.
+  if (size_in_bytes_ > bytes_to_reclaim_down_to) {
+    ProcessPendingCallbacks();
+    ReclaimMemory(bytes_to_reclaim_down_to, true /*log_warning_if_over*/);
+  }
+}
+
+template <typename CacheType>
+void ResourceCache<CacheType>::ReclaimMemory(uint32 bytes_to_reclaim_down_to,
+                                             bool log_warning_if_over) {
   DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
 
-  while (size_in_bytes_.value() > bytes_to_reclaim_down_to &&
+  while (size_in_bytes_ > bytes_to_reclaim_down_to &&
          !unreference_cached_resource_map_.empty()) {
     // The first element is the earliest-inserted element.
     scoped_refptr<ResourceType> resource =
@@ -588,14 +700,47 @@
     size_in_bytes_ -= first_resource_size;
   }
 
-  // Make sure that |size_in_bytes_| is less than or equal to |cache_capacity_|,
-  // otherwise it means that |unreference_cached_resource_map_| is empty. We
-  // have to increase the size of |cache_capacity_| if the system memory is
-  // large enough or evict resources from the cache even though they are still
-  // in use.
-  DLOG_IF(WARNING, size_in_bytes_.value() > cache_capacity_)
-      << "cached size: " << size_in_bytes_
-      << ", cache capacity: " << cache_capacity_;
+  if (log_warning_if_over) {
+    // Log a warning if we're still over |bytes_to_reclaim_down_to| after
+    // attempting to reclaim memory. This can occur validly when the size of
+    // the referenced images exceeds the target size.
+    DLOG_IF(WARNING, size_in_bytes_ > bytes_to_reclaim_down_to)
+        << "cached size: " << size_in_bytes_
+        << ", target size: " << bytes_to_reclaim_down_to;
+  }
+}
+
+template <typename CacheType>
+void ResourceCache<CacheType>::ProcessPendingCallbacksIfUnblocked() {
+  if (callback_blocking_loading_resource_set_.empty()) {
+    ProcessPendingCallbacks();
+
+    // Now that we've processed the callbacks, if there are any non-blocking
+    // loading resources, then they're becoming blocking. Simply swap the two
+    // sets, rather than copying the contents over.
+    if (!non_callback_blocking_loading_resource_set_.empty()) {
+      callback_blocking_loading_resource_set_.swap(
+          non_callback_blocking_loading_resource_set_);
+    }
+  }
+}
+
+template <typename CacheType>
+void ResourceCache<CacheType>::ProcessPendingCallbacks() {
+  DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
+
+  is_processing_pending_callbacks_ = true;
+  while (!pending_callback_map_.empty()) {
+    ResourceCallbackInfo& callback_info = pending_callback_map_.front().second;
+
+    // To avoid the last reference of this object getting deleted in the
+    // callbacks.
+    scoped_refptr<CachedResourceType> holder(callback_info.cached_resource);
+    callback_info.cached_resource->RunCallbacks(callback_info.callback_type);
+
+    pending_callback_map_.erase(pending_callback_map_.begin());
+  }
+  is_processing_pending_callbacks_ = false;
 }
 
 }  // namespace loader
diff --git a/src/cobalt/loader/sync_loader.cc b/src/cobalt/loader/sync_loader.cc
index 1d0d116..7ceff82 100644
--- a/src/cobalt/loader/sync_loader.cc
+++ b/src/cobalt/loader/sync_loader.cc
@@ -18,6 +18,7 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
+#include "base/debug/trace_event.h"
 #include "base/synchronization/waitable_event.h"
 
 namespace cobalt {
@@ -117,6 +118,7 @@
     base::Callback<scoped_ptr<Fetcher>(Fetcher::Handler*)> fetcher_creator,
     base::Callback<scoped_ptr<Decoder>()> decoder_creator,
     base::Callback<void(const std::string&)> error_callback) {
+  TRACE_EVENT0("cobalt::loader", "LoadSynchronously()");
   DCHECK(message_loop);
   DCHECK(!error_callback.is_null());
 
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/math/cubic_bezier.cc b/src/cobalt/math/cubic_bezier.cc
index 17c8368..51b5df0 100644
--- a/src/cobalt/math/cubic_bezier.cc
+++ b/src/cobalt/math/cubic_bezier.cc
@@ -66,8 +66,10 @@
   }
 
   // We should have terminated the above loop because we got close to x, not
-  // because we exceeded MAX_STEPS. Do a DCHECK here to confirm.
-  DCHECK_GT(kBezierEpsilon, std::abs(eval_bezier(x1, x2, t) - x));
+  // because we exceeded MAX_STEPS. Warn if this is not the case.
+  if (std::abs(eval_bezier(x1, x2, t) - x) > kBezierEpsilon) {
+    DLOG(WARNING) << "Notable error detected in bezier evaluation.";
+  }
 
   return t;
 }
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 70c6929..ed7dc27 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -35,6 +35,7 @@
       'dependencies': [
         '<(DEPTH)/cobalt/network/network.gyp:network',
         '<(DEPTH)/media/media.gyp:media',
+        '<(DEPTH)/nb/nb.gyp:nb',
       ],
       'export_dependent_settings': [
         '<(DEPTH)/media/media.gyp:media',
@@ -46,9 +47,6 @@
             'shell_media_platform_<(sb_media_platform).cc',
             'shell_media_platform_<(sb_media_platform).h',
           ],
-          'dependencies': [
-            '<(DEPTH)/nb/nb.gyp:nb',
-          ],
         }],
         ['OS=="starboard" and sb_media_platform == "ps4"', {
           'sources': [
@@ -72,9 +70,6 @@
             'shell_media_platform_ps3.cc',
             'shell_media_platform_ps3.h',
           ],
-          'dependencies': [
-            '<(DEPTH)/nb/nb.gyp:nb',
-          ],
         }],
       ],
     },
diff --git a/src/cobalt/media/media_buffer_allocator.h b/src/cobalt/media/media_buffer_allocator.h
new file mode 100644
index 0000000..ef7f620
--- /dev/null
+++ b/src/cobalt/media/media_buffer_allocator.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_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
+#define COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
+
+#include "base/optional.h"
+#include "nb/memory_pool.h"
+
+namespace cobalt {
+namespace media {
+
+class MediaBufferAllocator {
+ public:
+  MediaBufferAllocator(void* pool, size_t main_pool_size,
+                       size_t small_allocation_pool_size,
+                       size_t small_allocation_threshold)
+      : pool_(reinterpret_cast<uint8*>(pool)),
+        main_pool_size_(main_pool_size),
+        small_allocation_pool_size_(small_allocation_pool_size),
+        small_allocation_threshold_(small_allocation_threshold),
+        main_pool_(pool_, main_pool_size_, true, /* thread_safe */
+                   true /* verify_full_capacity */) {
+    if (small_allocation_pool_size_ > 0u) {
+      DCHECK_GT(small_allocation_threshold_, 0u);
+      small_allocation_pool_.emplace(pool_ + main_pool_size_,
+                                     small_allocation_pool_size_,
+                                     true, /* thread_safe */
+                                     true /* verify_full_capacity */);
+    } else {
+      DCHECK_EQ(small_allocation_pool_size_, 0u);
+      DCHECK_EQ(small_allocation_threshold_, 0u);
+    }
+  }
+
+  void* Allocate(size_t size, size_t alignment) {
+    void* p = NULL;
+    if (size < small_allocation_threshold_ && small_allocation_pool_) {
+      p = small_allocation_pool_->Allocate(size, alignment);
+    }
+    if (!p) {
+      p = main_pool_.Allocate(size, alignment);
+    }
+    if (!p && small_allocation_pool_) {
+      p = small_allocation_pool_->Allocate(size, alignment);
+    }
+    return p;
+  }
+
+  void Free(void* p) {
+    if (p >= pool_ + main_pool_size_ && small_allocation_pool_) {
+      DCHECK_LT(p, pool_ + main_pool_size_ + small_allocation_pool_size_);
+      small_allocation_pool_->Free(p);
+      return;
+    }
+    DCHECK_GE(p, pool_);
+    DCHECK_LT(p, pool_ + main_pool_size_);
+    main_pool_.Free(p);
+  }
+
+ private:
+  uint8* pool_;
+  size_t main_pool_size_;
+  size_t small_allocation_pool_size_;
+  size_t small_allocation_threshold_;
+
+  nb::MemoryPool main_pool_;
+  base::optional<nb::MemoryPool> small_allocation_pool_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaBufferAllocator);
+};
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
diff --git a/src/cobalt/media/media_module_starboard.cc b/src/cobalt/media/media_module_starboard.cc
index 4c54544..f4cf154 100644
--- a/src/cobalt/media/media_module_starboard.cc
+++ b/src/cobalt/media/media_module_starboard.cc
@@ -16,21 +16,14 @@
 
 #include "cobalt/media/media_module.h"
 
-#include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/shell_media_platform_starboard.h"
 #include "cobalt/system_window/starboard/system_window.h"
-#include "media/audio/null_audio_streamer.h"
-#include "media/audio/shell_audio_sink.h"
 #include "media/base/filter_collection.h"
 #include "media/base/media_log.h"
 #include "media/base/message_loop_factory.h"
-#include "media/filters/shell_audio_decoder_impl.h"
-#include "media/filters/shell_raw_audio_decoder_stub.h"
-#include "media/filters/shell_raw_video_decoder_stub.h"
-#include "media/filters/shell_video_decoder_impl.h"
 #include "media/player/web_media_player_impl.h"
 #include "starboard/media.h"
 #include "starboard/window.h"
@@ -76,14 +69,6 @@
         message_loop_factory->GetMessageLoop(MessageLoopFactory::kPipeline);
     scoped_ptr<FilterCollection> filter_collection(new FilterCollection);
 
-    ::media::ShellAudioStreamer* streamer = NULL;
-    if (options_.use_null_audio_streamer) {
-      DLOG(INFO) << "Use Null audio";
-      streamer = ::media::NullAudioStreamer::GetInstance();
-    } else {
-      DLOG(INFO) << "Use Pulse audio";
-      streamer = ::media::ShellAudioStreamer::Instance();
-    }
     SbWindow window = kSbWindowInvalid;
     if (system_window_) {
       window = polymorphic_downcast<SystemWindowStarboard*>(system_window_)
@@ -91,8 +76,8 @@
     }
     return make_scoped_ptr<WebMediaPlayer>(new ::media::WebMediaPlayerImpl(
         window, client, this, media_platform_.GetVideoFrameProvider(),
-        filter_collection.Pass(), new ::media::ShellAudioSink(streamer),
-        message_loop_factory.Pass(), new ::media::MediaLog));
+        filter_collection.Pass(), NULL, message_loop_factory.Pass(),
+        new ::media::MediaLog));
   }
 
  private:
diff --git a/src/cobalt/media/shell_media_platform_starboard.cc b/src/cobalt/media/shell_media_platform_starboard.cc
index 096b670..9b9bf31 100644
--- a/src/cobalt/media/shell_media_platform_starboard.cc
+++ b/src/cobalt/media/shell_media_platform_starboard.cc
@@ -38,6 +38,8 @@
 const size_t kGPUMemoryBufferBudget = SB_MEDIA_GPU_BUFFER_BUDGET;
 const size_t kMainMemoryBufferBudget = SB_MEDIA_MAIN_BUFFER_BUDGET;
 
+const size_t kSmallAllocationThreshold = 768U;
+
 }  // namespace
 
 ShellMediaPlatformStarboard::ShellMediaPlatformStarboard(
@@ -58,7 +60,7 @@
     gpu_memory_pool_.reset(new nb::MemoryPool(
         gpu_memory_buffer_space_->GetMemory(),
         gpu_memory_buffer_space_->GetSizeInBytes(), true, /* thread_safe */
-        true /* verify_full_capacity */));
+        true /* verify_full_capacity */, kSmallAllocationThreshold));
   }
 
   DCHECK_LE(0, kMainMemoryBufferBudget > 0);
@@ -68,7 +70,8 @@
   main_memory_pool_.reset(new nb::MemoryPool(main_memory_buffer_space_.get(),
                                              kMainMemoryBufferBudget,
                                              true, /* thread_safe */
-                                             true /* verify_full_capacity */));
+                                             true, /* verify_full_capacity */
+                                             kSmallAllocationThreshold));
 
   ShellBufferFactory::Initialize();
   ShellAudioStreamer::Initialize();
diff --git a/src/cobalt/network/persistent_cookie_store.cc b/src/cobalt/network/persistent_cookie_store.cc
index a5dd276..ce0926c 100644
--- a/src/cobalt/network/persistent_cookie_store.cc
+++ b/src/cobalt/network/persistent_cookie_store.cc
@@ -55,9 +55,13 @@
         get_all.ColumnString(6),
         base::Time::FromInternalValue(get_all.ColumnInt64(7)), expiry,
         get_all.ColumnBool(10), get_all.ColumnBool(11));
-    cookie->SetLastAccessDate(
-        base::Time::FromInternalValue(get_all.ColumnInt64(9)));
-    actual_cookies.push_back(cookie);
+    if (cookie) {
+      cookie->SetLastAccessDate(
+          base::Time::FromInternalValue(get_all.ColumnInt64(9)));
+      actual_cookies.push_back(cookie);
+    } else {
+      DLOG(ERROR) << "Failed to create cookie.";
+    }
   }
 
   return actual_cookies;
diff --git a/src/cobalt/render_tree/animations/animate_node.cc b/src/cobalt/render_tree/animations/animate_node.cc
index 6363ec9..0663359 100644
--- a/src/cobalt/render_tree/animations/animate_node.cc
+++ b/src/cobalt/render_tree/animations/animate_node.cc
@@ -21,6 +21,7 @@
 #include "base/debug/trace_event.h"
 #include "cobalt/base/enable_if.h"
 #include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/math/transform_2d.h"
 #include "cobalt/render_tree/child_iterator.h"
 #include "cobalt/render_tree/node_visitor.h"
 
@@ -214,6 +215,158 @@
   animated_ = true;
 }
 
+// A helper class for computing the tightest bounding rectangle that encloses
+// all animated subtrees.  It is meant to be applied to an already-animated
+// render tree, using a TraverseList provided by
+// ApplyVisitor::animated_traverse_list().  The result of the visit can be
+// retrieved from bounds().  This calculation is useful for determining a
+// "dirty rectangle" to minimize drawing.
+class AnimateNode::BoundsVisitor : public NodeVisitor {
+ public:
+  BoundsVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset,
+                base::TimeDelta since);
+
+  void Visit(animations::AnimateNode* /* animate */) OVERRIDE {
+    // An invariant of AnimateNodes is that they should never contain descendant
+    // AnimateNodes.
+    NOTREACHED();
+  }
+  // Immediately switch to a templated visitor function.
+  void Visit(CompositionNode* composition) OVERRIDE { VisitNode(composition); }
+  void Visit(FilterNode* text) OVERRIDE { VisitNode(text); }
+  void Visit(ImageNode* image) OVERRIDE { VisitNode(image); }
+  void Visit(MatrixTransformNode* transform) OVERRIDE { VisitNode(transform); }
+  void Visit(PunchThroughVideoNode* punch_through) OVERRIDE {
+    VisitNode(punch_through);
+  }
+  void Visit(RectNode* rect) OVERRIDE { VisitNode(rect); }
+  void Visit(RectShadowNode* rect) OVERRIDE { VisitNode(rect); }
+  void Visit(TextNode* text) OVERRIDE { VisitNode(text); }
+
+  const math::RectF& bounds() const { return bounds_; }
+
+ private:
+  template <typename T>
+  typename base::enable_if<!ChildIterator<T>::has_children>::type VisitNode(
+      T* node);
+  template <typename T>
+  typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode(
+      T* node);
+  void ProcessAnimatedNodeBounds(const TraverseListEntry& entry,
+                                 render_tree::Node* node);
+  TraverseListEntry AdvanceIterator(Node* node);
+
+  void ApplyTransform(Node* node);
+  void ApplyTransform(CompositionNode* node);
+  void ApplyTransform(MatrixTransformNode* node);
+
+  // The time offset to be passed in to individual animations.
+  base::TimeDelta time_offset_;
+
+  // The time when we "start" checking for active animations.  In other words,
+  // if an animation had expired *before* |since_|, then it is not considered
+  // animated, and not considered for the bounding box.
+  base::TimeDelta since_;
+
+  // A list of nodes that we are allowed to traverse into (i.e. a traversal that
+  // guides us to animated nodes).  It assumes that a pre-order traversal will
+  // be taken.
+  const TraverseList& traverse_list_;
+
+  // An iterator pointing to the next valid render tree node to visit.
+  TraverseList::const_iterator iterator_;
+
+  // The resulting bounding box surrounding all active animations.
+  math::RectF bounds_;
+
+  // We need to maintain a "current" transform as we traverse the tree, so that
+  // we know the transformed bounding boxes of nodes when we reach them.
+  math::Matrix3F transform_;
+};
+
+AnimateNode::BoundsVisitor::BoundsVisitor(const TraverseList& traverse_list,
+                                          base::TimeDelta time_offset,
+                                          base::TimeDelta since)
+    : time_offset_(time_offset),
+      since_(since),
+      traverse_list_(traverse_list),
+      transform_(math::Matrix3F::Identity()) {
+  iterator_ = traverse_list_.begin();
+}
+
+template <typename T>
+typename base::enable_if<!ChildIterator<T>::has_children>::type
+AnimateNode::BoundsVisitor::VisitNode(T* node) {
+  TraverseListEntry current_entry = AdvanceIterator(node);
+
+  DCHECK(current_entry.animations);
+  ProcessAnimatedNodeBounds(current_entry, node);
+}
+
+template <typename T>
+typename base::enable_if<ChildIterator<T>::has_children>::type
+AnimateNode::BoundsVisitor::VisitNode(T* node) {
+  TraverseListEntry current_entry = AdvanceIterator(node);
+
+  math::Matrix3F old_transform = transform_;
+  ApplyTransform(node);
+
+  // Traverse the child nodes, but only the ones that are on the
+  // |traverse_list_|.  In particular, the next node we are allowed to visit
+  // is the one in the traverse list pointed to by |iterator_->node|.
+  ChildIterator<T> child_iterator(node);
+  while (Node* child = child_iterator.GetCurrent()) {
+    if (iterator_ == traverse_list_.end()) {
+      // If we've reached the end of |traverse_list_| then we are done
+      // iterating and it's time to return.
+      break;
+    }
+
+    if (child == iterator_->node) {
+      // If one of our children is next up on the path to animation, traverse
+      // into it.
+      child->Accept(this);
+    }
+
+    child_iterator.Next();
+  }
+  transform_ = old_transform;
+
+  if (current_entry.animations) {
+    ProcessAnimatedNodeBounds(current_entry, node);
+  }
+}
+
+void AnimateNode::BoundsVisitor::ProcessAnimatedNodeBounds(
+    const TraverseListEntry& entry, render_tree::Node* node) {
+  TRACE_EVENT0("cobalt::renderer",
+               "AnimateNode::BoundsVisitor::ProcessAnimatedNodeBounds()");
+  if (entry.animations->GetExpiry() >= since_) {
+    bounds_.Union(transform_.MapRect(node->GetBounds()));
+  }
+}
+
+AnimateNode::TraverseListEntry AnimateNode::BoundsVisitor::AdvanceIterator(
+    Node* node) {
+  // Check that the iterator that we are advancing past is indeed the one we
+  // expect it to be.
+  DCHECK_EQ(node, iterator_->node);
+  return *(iterator_++);
+}
+
+void AnimateNode::BoundsVisitor::ApplyTransform(Node* node) {
+  UNREFERENCED_PARAMETER(node);
+}
+
+void AnimateNode::BoundsVisitor::ApplyTransform(CompositionNode* node) {
+  transform_ = transform_ * math::TranslateMatrix(node->data().offset().x(),
+                                                  node->data().offset().y());
+}
+
+void AnimateNode::BoundsVisitor::ApplyTransform(MatrixTransformNode* node) {
+  transform_ = transform_ * node->data().transform;
+}
+
 // A helper render tree visitor class used to apply compiled sub render-tree
 // animations.  Only one of these visitors is needed to visit an entire render
 // tree.
@@ -242,6 +395,11 @@
   // final animated result can be pulled from this visitor.
   const scoped_refptr<Node>& animated() const { return animated_; }
 
+  // As we compute the animated nodes, we create a new traverse list that leads
+  // to the newly created animated nodes.  This can be used afterwards to
+  // calculate the bounding boxes around the active animated nodes.
+  TraverseList* animated_traverse_list() { return &animated_traverse_list_; }
+
  private:
   template <typename T>
   typename base::enable_if<!ChildIterator<T>::has_children>::type VisitNode(
@@ -250,8 +408,8 @@
   typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode(
       T* node);
   template <typename T>
-  void ApplyAnimations(const TraverseListEntry& entry,
-                       typename T::Builder* builder);
+  scoped_refptr<Node> ApplyAnimations(const TraverseListEntry& entry,
+                                      typename T::Builder* builder);
   TraverseListEntry AdvanceIterator(Node* node);
 
   // The time offset to be passed in to individual animations.
@@ -262,6 +420,12 @@
   // guides us to animated nodes).  It assumes that a pre-order traversal will
   // be taken.
   const TraverseList& traverse_list_;
+
+  // As we animate the nodes, we also keep track of a new traverse list that
+  // replaces the non-animated nodes for the animated nodes, so that we can
+  // go through and traverse the animated nodes after they have been animated.
+  TraverseList animated_traverse_list_;
+
   // An iterator pointing to the next valid render tree node to visit.
   TraverseList::const_iterator iterator_;
 };
@@ -269,6 +433,7 @@
 AnimateNode::ApplyVisitor::ApplyVisitor(const TraverseList& traverse_list,
                                         base::TimeDelta time_offset)
     : time_offset_(time_offset), traverse_list_(traverse_list) {
+  animated_traverse_list_.reserve(traverse_list.size());
   iterator_ = traverse_list_.begin();
 }
 
@@ -280,8 +445,9 @@
   // have animations.
   DCHECK(current_entry.animations);
   typename T::Builder builder(node->data());
-  ApplyAnimations<T>(current_entry, &builder);
-  animated_ = new T(builder);
+  animated_ = ApplyAnimations<T>(current_entry, &builder);
+  animated_traverse_list_.push_back(
+      TraverseListEntry(animated_, current_entry.animations));
 }
 
 template <typename T>
@@ -289,6 +455,10 @@
 AnimateNode::ApplyVisitor::VisitNode(T* node) {
   TraverseListEntry current_entry = AdvanceIterator(node);
 
+  size_t animated_traverse_list_index = animated_traverse_list_.size();
+  animated_traverse_list_.push_back(
+      TraverseListEntry(NULL, current_entry.animations));
+
   // Traverse the child nodes, but only the ones that are on the
   // |traverse_list_|.  In particular, the next node we are allowed to visit
   // is the one in the traverse list pointed to by |iterator_->node|.
@@ -326,8 +496,7 @@
       // be passed into the animations.
       builder.emplace(node->data());
     }
-    ApplyAnimations<T>(current_entry, &(*builder));
-    animated_ = new T(*builder);
+    animated_ = ApplyAnimations<T>(current_entry, &(*builder));
   } else {
     // If there were no animations targeting this node directly, then its
     // children must have been modified since otherwise it wouldn't be in
@@ -335,11 +504,13 @@
     DCHECK(children_modified);
     animated_ = new T(child_iterator.TakeReplacedChildrenBuilder());
   }
+
+  animated_traverse_list_[animated_traverse_list_index].node = animated_;
 }
 
 template <typename T>
-void AnimateNode::ApplyVisitor::ApplyAnimations(const TraverseListEntry& entry,
-                                                typename T::Builder* builder) {
+scoped_refptr<Node> AnimateNode::ApplyVisitor::ApplyAnimations(
+    const TraverseListEntry& entry, typename T::Builder* builder) {
   TRACE_EVENT0("cobalt::renderer",
                "AnimateNode::ApplyVisitor::ApplyAnimations()");
   // Cast to the specific type we expect these animations to have.
@@ -353,6 +524,8 @@
        iter != typed_node_animations->data().animations.end(); ++iter) {
     iter->Run(builder, time_offset_);
   }
+
+  return new T(*builder);
 }
 
 AnimateNode::TraverseListEntry AnimateNode::ApplyVisitor::AdvanceIterator(
@@ -374,15 +547,66 @@
   CommonInit(Builder::InternalMap(), source);
 }
 
-scoped_refptr<Node> AnimateNode::Apply(base::TimeDelta time_offset) {
+// Helper class to refcount wrap a TraverseList object so that it can be
+// passed around in a callback.
+class AnimateNode::RefCountedTraversalList
+    : public base::RefCounted<RefCountedTraversalList> {
+ public:
+  explicit RefCountedTraversalList(TraverseList* to_swap_in) {
+    traverse_list_.swap(*to_swap_in);
+  }
+  const TraverseList& traverse_list() const { return traverse_list_; }
+
+ private:
+  friend class base::RefCounted<RefCountedTraversalList>;
+  ~RefCountedTraversalList() {}
+
+  TraverseList traverse_list_;
+};
+
+// static
+math::RectF AnimateNode::GetAnimationBoundsSince(
+    const scoped_refptr<RefCountedTraversalList>& traverse_list,
+    base::TimeDelta time_offset, const scoped_refptr<Node>& animated,
+    base::TimeDelta since) {
+  TRACE_EVENT0("cobalt::renderer", "AnimateNode::GetAnimationBoundsSince()");
+
+  BoundsVisitor bounds_visitor(traverse_list->traverse_list(), time_offset,
+                               since);
+  animated->Accept(&bounds_visitor);
+  return bounds_visitor.bounds();
+}
+
+namespace {
+// Helper function to always return an empty bounding rectangle.
+math::RectF ReturnTrivialEmptyRectBound(base::TimeDelta since) {
+  UNREFERENCED_PARAMETER(since);
+  return math::RectF();
+}
+}  // namespace
+
+AnimateNode::AnimateResults AnimateNode::Apply(base::TimeDelta time_offset) {
   TRACE_EVENT0("cobalt::renderer", "AnimateNode::Apply()");
+  AnimateResults results;
   if (traverse_list_.empty()) {
-    return source_;
+    results.animated = source_;
+    // There are no animations, so there is no bounding rectangle, so setup the
+    // bounding box function to trivially return an empty rectangle.
+    results.get_animation_bounds_since =
+        base::Bind(&ReturnTrivialEmptyRectBound);
   } else {
     ApplyVisitor apply_visitor(traverse_list_, time_offset);
     source_->Accept(&apply_visitor);
-    return apply_visitor.animated();
+    results.animated = apply_visitor.animated();
+
+    // Setup a function for returning
+    results.get_animation_bounds_since = base::Bind(
+        &GetAnimationBoundsSince,
+        scoped_refptr<RefCountedTraversalList>(new RefCountedTraversalList(
+            apply_visitor.animated_traverse_list())),
+        time_offset, results.animated);
   }
+  return results;
 }
 
 void AnimateNode::CommonInit(const Builder::InternalMap& node_animation_map,
diff --git a/src/cobalt/render_tree/animations/animate_node.h b/src/cobalt/render_tree/animations/animate_node.h
index b5a11e1..d0b81c8 100644
--- a/src/cobalt/render_tree/animations/animate_node.h
+++ b/src/cobalt/render_tree/animations/animate_node.h
@@ -22,6 +22,7 @@
 
 #include "base/containers/small_map.h"
 #include "base/memory/ref_counted.h"
+#include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/animations/animation_list.h"
 #include "cobalt/render_tree/movable.h"
 #include "cobalt/render_tree/node.h"
@@ -139,10 +140,21 @@
     return base::GetTypeId<AnimateNode>();
   }
 
+  struct AnimateResults {
+    // The animated render tree, which is guaranteed to not contain any
+    // AnimateNodes.
+    scoped_refptr<Node> animated;
+
+    // Can be called in order to return a bounding rectangle around all
+    // nodes that are actively animated.  The parameter specifies a "since"
+    // time.  Any animations that had already expired *before* the "since" time
+    // will not be included in the returned bounding box.
+    // This information is likely to be used to enable optimizations where only
+    // regions of the screen that have changed are rendered.
+    base::Callback<math::RectF(base::TimeDelta)> get_animation_bounds_since;
+  };
   // Apply the animations to the sub render tree with the given |time_offset|.
-  // An animated sub-tree is returned, which is guaranteed to not contain any
-  // AnimateNodes.
-  scoped_refptr<Node> Apply(base::TimeDelta time_offset);
+  AnimateResults Apply(base::TimeDelta time_offset);
 
   // Returns the sub-tree for which the animations apply to.
   const scoped_refptr<Node> source() const { return source_; }
@@ -154,17 +166,6 @@
   const base::TimeDelta& expiry() const { return expiry_; }
 
  private:
-  // A helper render tree visitor class used to compile sub render-tree
-  // animations.
-  class TraverseListBuilder;
-  // A helper render tree visitor class used to apply compiled sub render-tree
-  // animations.  This class follows the traversal generated by
-  // TraverseListBuilder.
-  class ApplyVisitor;
-
-  void CommonInit(const Builder::InternalMap& node_animation_map,
-                  const scoped_refptr<Node>& source);
-
   // The compiled node animation list is a sequence of nodes that are either
   // animated themselves, or on the path to an animated node.  Only nodes in
   // this sequence need to be traversed.  TraverseListEntry is an entry in this
@@ -180,6 +181,27 @@
   };
   typedef std::vector<TraverseListEntry> TraverseList;
 
+  class RefCountedTraversalList;
+
+  // A helper render tree visitor class used to compile sub render-tree
+  // animations.
+  class TraverseListBuilder;
+  // A helper render tree visitor class used to apply compiled sub render-tree
+  // animations.  This class follows the traversal generated by
+  // TraverseListBuilder.
+  class ApplyVisitor;
+  // A helper class to traverse an animated tree, and compute a bounding
+  // rectangle for all active animations.
+  class BoundsVisitor;
+
+  void CommonInit(const Builder::InternalMap& node_animation_map,
+                  const scoped_refptr<Node>& source);
+
+  static math::RectF GetAnimationBoundsSince(
+      const scoped_refptr<RefCountedTraversalList>& traverse_list,
+      base::TimeDelta time_offset, const scoped_refptr<Node>& animated,
+      base::TimeDelta since);
+
   // The compiled traversal list through the sub-tree represented by |source_|
   // that guides us towards all nodes that need to be animated.
   TraverseList traverse_list_;
diff --git a/src/cobalt/render_tree/animations/animate_node_test.cc b/src/cobalt/render_tree/animations/animate_node_test.cc
index f46e63e..0321bd8 100644
--- a/src/cobalt/render_tree/animations/animate_node_test.cc
+++ b/src/cobalt/render_tree/animations/animate_node_test.cc
@@ -48,12 +48,19 @@
 template <typename T>
 scoped_refptr<AnimateNode> CreateSingleAnimation(
     const scoped_refptr<T>& target,
-    typename Animation<T>::Function anim_function) {
+    typename Animation<T>::Function anim_function, base::TimeDelta expiry) {
   AnimateNode::Builder animations_builder;
-  animations_builder.Add(target, anim_function);
+  animations_builder.Add(target, anim_function, expiry);
   return new AnimateNode(animations_builder, target);
 }
 
+template <typename T>
+scoped_refptr<AnimateNode> CreateSingleAnimation(
+    const scoped_refptr<T>& target,
+    typename Animation<T>::Function anim_function) {
+  return CreateSingleAnimation(target, anim_function, base::TimeDelta::Max());
+}
+
 class ImageFake : public Image {
  public:
   const math::Size& GetSize() const OVERRIDE { return size_; }
@@ -77,7 +84,7 @@
       CreateSingleAnimation(text_node, base::Bind(&AnimateText));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   TextNode* animated_text_node =
       dynamic_cast<TextNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_text_node);
@@ -97,7 +104,7 @@
       CreateSingleAnimation(image_node, base::Bind(&AnimateImage));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   ImageNode* animated_image_node =
       dynamic_cast<ImageNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_image_node);
@@ -118,7 +125,7 @@
       CreateSingleAnimation(rect_node, base::Bind(&AnimateRect));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   RectNode* animated_rect_node =
       dynamic_cast<RectNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_rect_node);
@@ -146,21 +153,21 @@
   ImageNode* animated_image_node;
 
   animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
   animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width());
 
   animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(2));
+      with_animations->Apply(base::TimeDelta::FromSeconds(2)).animated;
   animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
   EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width());
 
   animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(4));
+      with_animations->Apply(base::TimeDelta::FromSeconds(4)).animated;
   animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
   EXPECT_TRUE(animated_image_node);
   EXPECT_TRUE(animated_image_node);
@@ -198,7 +205,7 @@
       new AnimateNode(animations_builder, image_node));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(3));
+      with_animations->Apply(base::TimeDelta::FromSeconds(3)).animated;
 
   ImageNode* animated_image_node =
       dynamic_cast<ImageNode*>(animated_render_tree.get());
@@ -224,7 +231,7 @@
       CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
       dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
@@ -256,7 +263,7 @@
       new AnimateNode(animation_builder, composition_node);
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   CompositionNode* animated_composition_node =
       dynamic_cast<CompositionNode*>(animated_render_tree.get());
@@ -287,7 +294,7 @@
       CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
       dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
@@ -324,7 +331,7 @@
       new AnimateNode(transform_node_b);
 
   scoped_refptr<Node> animated_render_tree =
-      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+      with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
 
   MatrixTransformNode* animated_transform_node =
       dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
@@ -342,6 +349,159 @@
   EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect);
 }
 
+void BoundsAnimateRect(RectNode::Builder* rect_node,
+                       base::TimeDelta time_elapsed) {
+  UNREFERENCED_PARAMETER(time_elapsed);
+  rect_node->rect = RectF(3.0f, 5.0f, 15.0f, 20.0f);
+}
+TEST(AnimateNodeTest, AnimationBounds) {
+  scoped_refptr<RectNode> rect_node_static(new RectNode(
+      RectF(1.0f, 1.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  scoped_refptr<RectNode> rect_node_animated(new RectNode(
+      RectF(4.0f, 4.0f, 10.0f, 10.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  CompositionNode::Builder builder;
+  builder.AddChild(rect_node_static);
+  builder.AddChild(rect_node_animated);
+  scoped_refptr<CompositionNode> composition(
+      new CompositionNode(builder.Pass()));
+
+  AnimateNode::Builder animations_builder;
+  animations_builder.Add(rect_node_animated, base::Bind(&BoundsAnimateRect));
+  scoped_refptr<AnimateNode> with_animations =
+      new AnimateNode(animations_builder, composition);
+
+  AnimateNode::AnimateResults results =
+      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+
+  math::RectF animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta());
+
+  EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds);
+}
+
+TEST(AnimateNodeTest, AnimationBoundsExpiration) {
+  scoped_refptr<RectNode> rect_node_static(new RectNode(
+      RectF(1.0f, 1.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  scoped_refptr<RectNode> rect_node_animated(new RectNode(
+      RectF(4.0f, 4.0f, 10.0f, 10.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  CompositionNode::Builder builder;
+  builder.AddChild(rect_node_static);
+  builder.AddChild(rect_node_animated);
+  scoped_refptr<CompositionNode> composition(
+      new CompositionNode(builder.Pass()));
+
+  AnimateNode::Builder animations_builder;
+  animations_builder.Add(rect_node_animated, base::Bind(&BoundsAnimateRect),
+                         base::TimeDelta::FromSeconds(2));
+  scoped_refptr<AnimateNode> with_animations =
+      new AnimateNode(animations_builder, composition);
+
+  // Make sure that our animation bounds are updated when we apply animations
+  // before the expiration.
+  AnimateNode::AnimateResults results =
+      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+  math::RectF animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0));
+  EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds);
+
+  // Make sure that our animation bounds are updated when we apply animations
+  // after the expiration, but we pass in a "since" value from before the
+  // animations expire.
+  results = with_animations->Apply(base::TimeDelta::FromSeconds(4));
+  animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(1));
+  EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds);
+
+  // Make sure that our animation bounds are empty after our animations have
+  // expired.
+  results = with_animations->Apply(base::TimeDelta::FromSeconds(4));
+  animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(3));
+  EXPECT_EQ(0, animation_bounds.size().GetArea());
+}
+
+void BoundsAnimateRect2(RectNode::Builder* rect_node,
+                        base::TimeDelta time_elapsed) {
+  UNREFERENCED_PARAMETER(time_elapsed);
+  rect_node->rect = RectF(2.0f, 6.0f, 10.0f, 25.0f);
+}
+TEST(AnimateNodeTest, AnimationBoundsUnionsMultipleAnimations) {
+  scoped_refptr<RectNode> rect_node_1(new RectNode(
+      RectF(1.0f, 1.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  scoped_refptr<RectNode> rect_node_2(new RectNode(
+      RectF(4.0f, 4.0f, 10.0f, 10.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  CompositionNode::Builder builder;
+  builder.AddChild(rect_node_1);
+  builder.AddChild(rect_node_2);
+  scoped_refptr<CompositionNode> composition(
+      new CompositionNode(builder.Pass()));
+
+  AnimateNode::Builder animations_builder;
+  animations_builder.Add(rect_node_1, base::Bind(&BoundsAnimateRect));
+  animations_builder.Add(rect_node_2, base::Bind(&BoundsAnimateRect2));
+  scoped_refptr<AnimateNode> with_animations =
+      new AnimateNode(animations_builder, composition);
+
+  // Make sure that our animation bounds are the union from our two animated
+  // resulting boxes.
+  AnimateNode::AnimateResults results =
+      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+  math::RectF animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0));
+  EXPECT_EQ(RectF(2.0f, 5.0f, 16.0f, 26.0f), animation_bounds);
+}
+
+void AnimateTranslate(CompositionNode::Builder* composition_node,
+                      base::TimeDelta time_elapsed) {
+  UNREFERENCED_PARAMETER(time_elapsed);
+  composition_node->set_offset(math::Vector2dF(4.0f, 4.0f));
+}
+// This test makes sure that the animation bounds are calculated correctly when
+// multiple nodes on the path from the root to a leaf node are animated.  Our
+// results should use the *animated* path to the node to calculate the bounds,
+// versus the non-animated path.
+TEST(AnimateNodeTest, AnimationBoundsWorksForCompoundedTransformations) {
+  scoped_refptr<RectNode> rect_node(new RectNode(
+      RectF(8.0f, 8.0f),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f)))));
+
+  CompositionNode::Builder composition_node_builder_1;
+  composition_node_builder_1.AddChild(rect_node);
+  scoped_refptr<CompositionNode> composition_node_1(
+      new CompositionNode(composition_node_builder_1.Pass()));
+
+  CompositionNode::Builder composition_node_builder_2;
+  composition_node_builder_2.AddChild(composition_node_1);
+  scoped_refptr<CompositionNode> composition_node_2(
+      new CompositionNode(composition_node_builder_2.Pass()));
+
+  AnimateNode::Builder animations_builder;
+  animations_builder.Add(composition_node_1, base::Bind(&AnimateTranslate));
+  animations_builder.Add(composition_node_2, base::Bind(&AnimateTranslate));
+  scoped_refptr<AnimateNode> with_animations =
+      new AnimateNode(animations_builder, composition_node_2);
+
+  // Make sure that our animation bounds are the union from our two animated
+  // resulting boxes.
+  AnimateNode::AnimateResults results =
+      with_animations->Apply(base::TimeDelta::FromSeconds(1));
+  math::RectF animation_bounds =
+      results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0));
+  EXPECT_EQ(RectF(8.0f, 8.0f, 8.0f, 8.0f), animation_bounds);
+}
+
 }  // namespace animations
 }  // namespace render_tree
 }  // namespace cobalt
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h
index d98eeac..b81b95e 100644
--- a/src/cobalt/render_tree/brush.h
+++ b/src/cobalt/render_tree/brush.h
@@ -107,6 +107,12 @@
   // and destination points.
   const ColorStopList& color_stops() const { return color_stops_; }
 
+  // Returns true if, and only if the brush is horizontal.
+  bool IsHorizontal() const { return (source_.y() == dest_.y()); }
+
+  // Returns true if, and only if the brush is vertical.
+  bool IsVertical() const { return (source_.x() == dest_.x()); }
+
  private:
   math::PointF source_;
   math::PointF dest_;
diff --git a/src/cobalt/render_tree/color_rgba.h b/src/cobalt/render_tree/color_rgba.h
index ee5dbe9..f27f24c 100644
--- a/src/cobalt/render_tree/color_rgba.h
+++ b/src/cobalt/render_tree/color_rgba.h
@@ -22,10 +22,21 @@
 namespace cobalt {
 namespace render_tree {
 
+// Note: this does not handle infinities or nans.
+inline float clamp(float input, float min, float max) {
+#if defined(STARBOARD)
+  if (SB_UNLIKELY(input < min)) return min;
+  if (SB_UNLIKELY(input > max)) return max;
+#else
+  if (input < min) return min;
+  if (input > max) return max;
+#endif
+  return input;
+}
+
 // Used to specify a color in the RGB (plus alpha) space.
 // This color format is referenced by many render_tree objects in order to
-// specify a color.  It is expressed by a float for each component, and must
-// be in the range [0.0, 1.0].
+// specify a color.
 struct ColorRGBA {
  public:
   ColorRGBA() : r_(0), g_(0), b_(0), a_(0) {}
@@ -81,13 +92,76 @@
     a_ = value;
   }
 
-  float r() const { return r_; }
-  float g() const { return g_; }
-  float b() const { return b_; }
-  float a() const { return a_; }
+  // These functions clamp the color channel values between 0.0f and 1.0f.
+  float r() const { return clamp(r_, 0.0f, 1.0f); }
+  float g() const { return clamp(g_, 0.0f, 1.0f); }
+  float b() const { return clamp(b_, 0.0f, 1.0f); }
+  float a() const { return clamp(a_, 0.0f, 1.0f); }
+
+  uint8_t rgb8_r() const { return static_cast<uint8_t>(r() * 255); }
+
+  uint8_t rgb8_g() const { return static_cast<uint8_t>(g() * 255); }
+
+  uint8_t rgb8_b() const { return static_cast<uint8_t>(b() * 255); }
+
+  uint8_t rgb8_a() const { return static_cast<uint8_t>(a() * 255); }
+
+  ColorRGBA& operator=(const ColorRGBA& other) {
+    r_ = other.r_;
+    g_ = other.g_;
+    b_ = other.b_;
+    a_ = other.a_;
+    return *this;
+  }
+
+  ColorRGBA& operator-=(const ColorRGBA& other) {
+    r_ -= other.r_;
+    g_ -= other.g_;
+    b_ -= other.b_;
+    a_ -= other.a_;
+    return *this;
+  }
+
+  ColorRGBA& operator+=(const ColorRGBA& other) {
+    r_ += other.r_;
+    g_ += other.g_;
+    b_ += other.b_;
+    a_ += other.a_;
+    return *this;
+  }
+
+  ColorRGBA& operator+=(const float f) {
+    r_ += f;
+    b_ += f;
+    g_ += f;
+    a_ += f;
+    return *this;
+  }
+
+  ColorRGBA& operator-=(const float f) {
+    r_ -= f;
+    b_ -= f;
+    g_ -= f;
+    a_ -= f;
+    return *this;
+  }
+
+  ColorRGBA& operator*=(const float f) {
+    r_ *= f;
+    b_ *= f;
+    g_ *= f;
+    a_ *= f;
+    return *this;
+  }
+
+  ColorRGBA& operator/=(const float f) {
+    float one_over_f(1.0f / f);
+    *this *= one_over_f;
+    return *this;
+  }
 
  private:
-  void CheckRange(float value) {
+  void CheckRange(float value) const {
     DCHECK_LE(0.0f, value);
     DCHECK_GE(1.0f, value);
   }
@@ -100,6 +174,46 @@
          lhs.a() == rhs.a();
 }
 
+inline bool operator!=(const ColorRGBA& lhs, const ColorRGBA& rhs) {
+  return !(lhs == rhs);
+}
+
+inline ColorRGBA operator-(const ColorRGBA& lhs, const ColorRGBA& rhs) {
+  ColorRGBA color(lhs);
+  color -= rhs;
+  return color;
+}
+
+inline ColorRGBA operator+(const ColorRGBA& lhs, const ColorRGBA& rhs) {
+  ColorRGBA color(lhs);
+  color += rhs;
+  return color;
+}
+
+inline ColorRGBA operator+(const ColorRGBA& lhs, const float rhs) {
+  ColorRGBA color(lhs);
+  color += rhs;
+  return color;
+}
+
+inline ColorRGBA operator-(const ColorRGBA& lhs, const float rhs) {
+  ColorRGBA color(lhs);
+  color -= rhs;
+  return color;
+}
+
+inline ColorRGBA operator*(const ColorRGBA& lhs, const float rhs) {
+  ColorRGBA color(lhs);
+  color *= rhs;
+  return color;
+}
+
+inline ColorRGBA operator/(const ColorRGBA& lhs, const float rhs) {
+  ColorRGBA color(lhs);
+  color /= rhs;
+  return color;
+}
+
 // Used by tests.
 inline std::ostream& operator<<(std::ostream& stream, const ColorRGBA& color) {
   return stream << "rgba(" << color.r() << ", " << color.g() << ", "
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/render_tree/mesh.h b/src/cobalt/render_tree/mesh.h
new file mode 100644
index 0000000..b87793b
--- /dev/null
+++ b/src/cobalt/render_tree/mesh.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_RENDER_TREE_MESH_H_
+#define COBALT_RENDER_TREE_MESH_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "starboard/types.h"
+#include "third_party/glm/glm/vec2.hpp"
+#include "third_party/glm/glm/vec3.hpp"
+
+namespace cobalt {
+namespace render_tree {
+
+// Represents a mesh to which render_trees can be mapped for 3D projection
+// by being applied a filter.
+class Mesh {
+ public:
+  // Vertices interleave position (x, y, z) and texture (u, v) coordinates.
+  struct Vertex {
+    glm::vec3 position_coord;
+    glm::vec2 texture_coord;
+  };
+  COMPILE_ASSERT(sizeof(Vertex) == sizeof(float) * 5,
+                 vertex_struct_size_exceeds_5_floats);
+
+  // Defines a subset of the legal values for the draw mode parameter passed
+  // into glDrawArrays() and glDrawElements(). These correspond to GL_TRIANGLES,
+  // GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN. Users of this class can assert their
+  // values correspond exactly to the GL constants in order to convert via
+  // integer casting.
+  enum DrawMode {
+    kDrawModeTriangles = 4,
+    kDrawModeTriangleStrip = 5,
+    kDrawModeTriangleFan = 6
+  };
+
+  Mesh(const std::vector<Vertex>& vertices, const DrawMode mode)
+      : vertices_(vertices), draw_mode_(CheckDrawMode(mode)) {}
+
+  ~Mesh() {}
+
+  const std::vector<Vertex>& vertices() const { return vertices_; }
+
+  DrawMode draw_mode() const { return draw_mode_; }
+
+ private:
+  static const DrawMode CheckDrawMode(DrawMode mode) {
+    switch (mode) {
+      case kDrawModeTriangles:
+      case kDrawModeTriangleStrip:
+      case kDrawModeTriangleFan:
+        return mode;
+      default:
+        NOTREACHED() << "Unsupported render_tree::Mesh DrawMode detected, "
+                        "defaulting to kDrawModeTriangleStrip";
+        return kDrawModeTriangleStrip;
+    }
+  }
+
+  const std::vector<Vertex> vertices_;
+  const DrawMode draw_mode_;
+};
+
+}  // namespace render_tree
+}  // namespace cobalt
+
+#endif  // COBALT_RENDER_TREE_MESH_H_
diff --git a/src/cobalt/render_tree/mock_resource_provider.h b/src/cobalt/render_tree/mock_resource_provider.h
new file mode 100644
index 0000000..f51cba3
--- /dev/null
+++ b/src/cobalt/render_tree/mock_resource_provider.h
@@ -0,0 +1,150 @@
+/*
+ * 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_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_
+#define COBALT_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/render_tree/font.h"
+#include "cobalt/render_tree/font_provider.h"
+#include "cobalt/render_tree/glyph_buffer.h"
+#include "cobalt/render_tree/image.h"
+#include "cobalt/render_tree/resource_provider.h"
+#include "cobalt/render_tree/typeface.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace render_tree {
+
+class MockResourceProvider : public ResourceProvider {
+ public:
+  MOCK_METHOD0(Finish, void());
+  MOCK_METHOD1(PixelFormatSupported, bool(PixelFormat pixel_format));
+  MOCK_METHOD1(AlphaFormatSupported, bool(AlphaFormat alpha_format));
+  MOCK_METHOD3(AllocateImageDataMock,
+               ImageData*(const math::Size& size, PixelFormat pixel_format,
+                          AlphaFormat alpha_format));
+  MOCK_METHOD1(CreateImageMock, Image*(ImageData* pixel_data));
+  MOCK_METHOD2(AllocateRawImageMemoryMock,
+               RawImageMemory*(size_t size_in_bytes, size_t alignment));
+  MOCK_METHOD2(CreateMultiPlaneImageFromRawMemoryMock,
+               Image*(RawImageMemory* raw_image_memory,
+                      const MultiPlaneImageDataDescriptor& descriptor));
+  MOCK_CONST_METHOD1(HasLocalFontFamily, bool(const char* font_family_name));
+  MOCK_METHOD2(GetLocalTypefaceMock,
+               Typeface*(const char* font_family_name, FontStyle font_style));
+  MOCK_METHOD1(GetLocalTypefaceIfAvailableMock,
+               Typeface*(const std::string& font_face_name));
+  MOCK_METHOD3(GetCharacterFallbackTypefaceMock,
+               Typeface*(int32 utf32_character, FontStyle font_style,
+                         const std::string& language));
+  MOCK_METHOD2(CreateTypefaceFromRawDataMock,
+               Typeface*(RawTypefaceDataVector* raw_data,
+                         std::string* error_string));
+  MOCK_METHOD5(
+      CreateGlyphBufferMock,
+      render_tree::GlyphBuffer*(const char16* text_buffer, size_t text_length,
+                                const std::string& language, bool is_rtl,
+                                render_tree::FontProvider* font_provider));
+  MOCK_METHOD2(
+      CreateGlyphBufferMock,
+      render_tree::GlyphBuffer*(const std::string& utf8_string,
+                                const scoped_refptr<render_tree::Font>& font));
+  MOCK_METHOD6(GetTextWidth,
+               float(const char16* text_buffer, size_t text_length,
+                     const std::string& language, bool is_rtl,
+                     render_tree::FontProvider* font_provider,
+                     render_tree::FontVector* maybe_used_fonts));
+
+  scoped_ptr<ImageData> AllocateImageData(const math::Size& size,
+                                          PixelFormat pixel_format,
+                                          AlphaFormat alpha_format) {
+    return scoped_ptr<ImageData>(
+        AllocateImageDataMock(size, pixel_format, alpha_format));
+  }
+  scoped_refptr<Image> CreateImage(scoped_ptr<ImageData> pixel_data) {
+    return scoped_refptr<Image>(CreateImageMock(pixel_data.get()));
+  }
+
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  scoped_refptr<Image> CreateImageFromSbDecodeTarget(SbDecodeTarget target) {
+    UNREFERENCED_PARAMETER(target);
+    return NULL;
+  }
+
+  SbDecodeTargetProvider* GetSbDecodeTargetProvider() { return NULL; }
+
+  bool SupportsSbDecodeTarget() { return false; }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
+  scoped_ptr<RawImageMemory> AllocateRawImageMemory(size_t size_in_bytes,
+                                                    size_t alignment) {
+    return scoped_ptr<RawImageMemory>(
+        AllocateRawImageMemoryMock(size_in_bytes, alignment));
+  }
+  scoped_refptr<Image> CreateMultiPlaneImageFromRawMemory(
+      scoped_ptr<RawImageMemory> raw_image_memory,
+      const MultiPlaneImageDataDescriptor& descriptor) {
+    return scoped_refptr<Image>(CreateMultiPlaneImageFromRawMemoryMock(
+        raw_image_memory.get(), descriptor));
+  }
+  scoped_refptr<Typeface> GetLocalTypeface(const char* font_family_name,
+                                           FontStyle font_style) {
+    return scoped_refptr<Typeface>(
+        GetLocalTypefaceMock(font_family_name, font_style));
+  }
+  scoped_refptr<Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) {
+    return scoped_refptr<Typeface>(
+        GetLocalTypefaceIfAvailableMock(font_face_name));
+  }
+  scoped_refptr<Typeface> GetCharacterFallbackTypeface(
+      int32 utf32_character, FontStyle font_style,
+      const std::string& language) {
+    return scoped_refptr<Typeface>(GetCharacterFallbackTypefaceMock(
+        utf32_character, font_style, language));
+  }
+  scoped_refptr<Typeface> CreateTypefaceFromRawData(
+      scoped_ptr<RawTypefaceDataVector> raw_data, std::string* error_string) {
+    return scoped_refptr<Typeface>(
+        CreateTypefaceFromRawDataMock(raw_data.get(), error_string));
+  }
+  scoped_refptr<render_tree::GlyphBuffer> CreateGlyphBuffer(
+      const char16* text_buffer, size_t text_length,
+      const std::string& language, bool is_rtl,
+      render_tree::FontProvider* font_provider) {
+    return scoped_refptr<render_tree::GlyphBuffer>(CreateGlyphBufferMock(
+        text_buffer, text_length, language, is_rtl, font_provider));
+  }
+  scoped_refptr<render_tree::GlyphBuffer> CreateGlyphBuffer(
+      const std::string& utf8_string,
+      const scoped_refptr<render_tree::Font>& font) {
+    return scoped_refptr<render_tree::GlyphBuffer>(
+        CreateGlyphBufferMock(utf8_string, font.get()));
+  }
+};
+
+}  // namespace render_tree
+}  // namespace cobalt
+
+#endif  // COBALT_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_
diff --git a/src/cobalt/render_tree/render_tree.gyp b/src/cobalt/render_tree/render_tree.gyp
index 5d6f1a6..d8dfa5e 100644
--- a/src/cobalt/render_tree/render_tree.gyp
+++ b/src/cobalt/render_tree/render_tree.gyp
@@ -47,6 +47,7 @@
         'map_to_mesh_filter.h',
         'matrix_transform_node.cc',
         'matrix_transform_node.h',
+        'mesh.h',
         'node.h',
         'node_visitor.h',
         'opacity_filter.h',
diff --git a/src/cobalt/render_tree/resource_provider.h b/src/cobalt/render_tree/resource_provider.h
index 72c106a..4590121 100644
--- a/src/cobalt/render_tree/resource_provider.h
+++ b/src/cobalt/render_tree/resource_provider.h
@@ -27,6 +27,9 @@
 #include "cobalt/render_tree/glyph_buffer.h"
 #include "cobalt/render_tree/image.h"
 #include "cobalt/render_tree/typeface.h"
+#if defined(STARBOARD)
+#include "starboard/decode_target.h"
+#endif  // defined(STARBOARD)
 
 namespace cobalt {
 namespace render_tree {
@@ -72,6 +75,23 @@
   virtual scoped_refptr<Image> CreateImage(
       scoped_ptr<ImageData> pixel_data) = 0;
 
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  // This function will consume an SbDecodeTarget object produced by
+  // SbDecodeTargetCreate(), wrap it in a render_tree::Image that can be used
+  // in a render tree, and return it to the caller.
+  virtual scoped_refptr<Image> CreateImageFromSbDecodeTarget(
+      SbDecodeTarget target) = 0;
+
+  // Return the associated SbDecodeTargetProvider with the ResourceProvider,
+  // if it exists.  Returns NULL if SbDecodeTarget is not supported.
+  virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() = 0;
+
+  // Whether SbDecodeTargetIsSupported or not.
+  virtual bool SupportsSbDecodeTarget() = 0;
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   // Returns a raw chunk of memory that can later be passed into a function like
   // CreateMultiPlaneImageFromRawMemory() in order to create a texture.
   // If possible, the memory returned will be GPU memory that can be directly
@@ -107,6 +127,16 @@
   virtual scoped_refptr<Typeface> GetLocalTypeface(const char* font_family_name,
                                                    FontStyle font_style) = 0;
 
+  // Given a set of typeface information, this method returns the locally
+  // available typeface that best fits the specified parameters. In the case
+  // where no typeface is found that matches the font family name, NULL is
+  // returned.
+  //
+  // Font's typeface (aka face name) is combination of a style and a font
+  // family.  Font's style consists of weight, and a slant (but not size).
+  virtual scoped_refptr<Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) = 0;
+
   // Given a UTF-32 character, a set of typeface information, and a language,
   // this method returns the best-fit locally available fallback typeface that
   // provides a glyph for the specified character. In the case where no fallback
diff --git a/src/cobalt/render_tree/resource_provider_stub.h b/src/cobalt/render_tree/resource_provider_stub.h
index 1430f5f..d40787f 100644
--- a/src/cobalt/render_tree/resource_provider_stub.h
+++ b/src/cobalt/render_tree/resource_provider_stub.h
@@ -187,6 +187,21 @@
     return make_scoped_refptr(new ImageStub(skia_source_data.Pass()));
   }
 
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  scoped_refptr<Image> CreateImageFromSbDecodeTarget(
+      SbDecodeTarget decode_target) OVERRIDE {
+    NOTREACHED();
+    SbDecodeTargetDestroy(decode_target);
+    return NULL;
+  }
+
+  SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
+
+  bool SupportsSbDecodeTarget() OVERRIDE { return false; }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   scoped_ptr<RawImageMemory> AllocateRawImageMemory(size_t size_in_bytes,
                                                     size_t alignment) OVERRIDE {
     return scoped_ptr<RawImageMemory>(
@@ -213,6 +228,12 @@
     return make_scoped_refptr(new TypefaceStub(NULL));
   }
 
+  scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) OVERRIDE {
+    UNREFERENCED_PARAMETER(font_face_name);
+    return make_scoped_refptr(new TypefaceStub(NULL));
+  }
+
   scoped_refptr<Typeface> GetCharacterFallbackTypeface(
       int32 utf32_character, FontStyle font_style,
       const std::string& language) OVERRIDE {
diff --git a/src/cobalt/renderer/animations_test.cc b/src/cobalt/renderer/animations_test.cc
index 033a78f..b46c59e 100644
--- a/src/cobalt/renderer/animations_test.cc
+++ b/src/cobalt/renderer/animations_test.cc
@@ -1,4 +1,4 @@
-/*
+  /*
  * Copyright 2015 Google Inc. All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -171,7 +171,7 @@
     Pipeline pipeline(
         base::Bind(render_module_options.create_rasterizer_function,
                    graphics_context.get(), render_module_options),
-        dummy_output_surface, NULL);
+        dummy_output_surface, NULL, true);
 
     // Our test render tree will consist of only a single ImageNode.
     scoped_refptr<ImageNode> test_node = new ImageNode(
diff --git a/src/cobalt/renderer/backend/blitter/graphics_context.cc b/src/cobalt/renderer/backend/blitter/graphics_context.cc
index b6e4210..3e9613f 100644
--- a/src/cobalt/renderer/backend/blitter/graphics_context.cc
+++ b/src/cobalt/renderer/backend/blitter/graphics_context.cc
@@ -75,6 +75,12 @@
   return pixels.Pass();
 }
 
+void GraphicsContextBlitter::Finish() {
+  // Note: flushing the context doesn't actually guarantee that drawing has
+  // finished.
+  SbBlitterFlushContext(context_);
+}
+
 }  // namespace backend
 }  // namespace renderer
 }  // namespace cobalt
diff --git a/src/cobalt/renderer/backend/blitter/graphics_context.h b/src/cobalt/renderer/backend/blitter/graphics_context.h
index 5c15184..5fc3330 100644
--- a/src/cobalt/renderer/backend/blitter/graphics_context.h
+++ b/src/cobalt/renderer/backend/blitter/graphics_context.h
@@ -38,6 +38,7 @@
       const math::Size& dimensions) OVERRIDE;
   scoped_array<uint8_t> DownloadPixelDataAsRGBA(
       const scoped_refptr<RenderTarget>& render_target) OVERRIDE;
+  void Finish() OVERRIDE;
 
   SbBlitterContext GetSbBlitterContext() const { return context_; }
   SbBlitterDevice GetSbBlitterDevice() const { return device_; }
diff --git a/src/cobalt/renderer/backend/egl/display.cc b/src/cobalt/renderer/backend/egl/display.cc
index dc325c7..c294168 100644
--- a/src/cobalt/renderer/backend/egl/display.cc
+++ b/src/cobalt/renderer/backend/egl/display.cc
@@ -54,6 +54,16 @@
   surface_ = eglCreateWindowSurface(display_, config_, native_window_, NULL);
   CHECK_EQ(EGL_SUCCESS, eglGetError());
 
+  // Configure the surface to preserve contents on swap.
+  EGLBoolean surface_attrib_set =
+      eglSurfaceAttrib(display_, surface_,
+                       EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
+  // NOTE: Must check eglGetError() to clear any error flags and also check
+  // the return value of eglSurfaceAttrib since some implementations may not
+  // set the error condition.
+  content_preserved_on_swap_ =
+      eglGetError() == EGL_SUCCESS && surface_attrib_set == EGL_TRUE;
+
   // Query and cache information about the surface now that we have created it.
   EGLint egl_surface_width;
   EGLint egl_surface_height;
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc
index 3482472..2f39ddb 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -211,6 +211,7 @@
 
 GraphicsContextEGL::~GraphicsContextEGL() {
   MakeCurrent();
+  GL_CALL(glFinish());
   GL_CALL(glDeleteBuffers(1, &blit_vertex_buffer_));
   GL_CALL(glDeleteProgram(blit_program_));
   GL_CALL(glDeleteShader(blit_fragment_shader_));
@@ -350,6 +351,11 @@
   return pixels.Pass();
 }
 
+void GraphicsContextEGL::Finish() {
+  ScopedMakeCurrent scoped_current_context(this);
+  GL_CALL(glFinish());
+}
+
 void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width,
                               int height) {
   // Render a texture to the specified output rectangle on the render target.
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h
index 57d77aa..c69ed50 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.h
+++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -58,6 +58,8 @@
   scoped_array<uint8_t> DownloadPixelDataAsRGBA(
       const scoped_refptr<RenderTarget>& render_target) OVERRIDE;
 
+  void Finish() OVERRIDE;
+
   // Helper class to allow one to create a RAII object that will acquire the
   // current context upon construction and release it upon destruction.
   class ScopedMakeCurrent {
@@ -106,6 +108,13 @@
 
   void Blit(GLuint texture, int x, int y, int width, int height);
 
+  bool ReadPixelsNeedVerticalFlip() {
+    if (!read_pixels_needs_vertical_flip_) {
+      read_pixels_needs_vertical_flip_ = ComputeReadPixelsNeedVerticalFlip();
+    }
+    return *read_pixels_needs_vertical_flip_;
+  }
+
  private:
   // Performs a test to determine if the pixel data returned by glReadPixels
   // needs to be vertically flipped or not.  This test is expensive and so
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.cc b/src/cobalt/renderer/backend/egl/graphics_system.cc
index 16b5104..d8895bd 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_system.cc
@@ -64,23 +64,35 @@
 
   // Setup our configuration to support RGBA and compatibility with PBuffer
   // objects (for offscreen rendering).
-  EGLint const attribute_list[] = {EGL_RED_SIZE,
-                                   8,
-                                   EGL_GREEN_SIZE,
-                                   8,
-                                   EGL_BLUE_SIZE,
-                                   8,
-                                   EGL_ALPHA_SIZE,
-                                   8,
-                                   EGL_SURFACE_TYPE,
-                                   EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
-                                   EGL_BIND_TO_TEXTURE_RGBA,
-                                   EGL_TRUE,
-                                   EGL_NONE};
+  EGLint attribute_list[] = {EGL_SURFACE_TYPE,    // this must be first
+                             EGL_WINDOW_BIT | EGL_PBUFFER_BIT |
+                                EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
+                             EGL_RED_SIZE,
+                             8,
+                             EGL_GREEN_SIZE,
+                             8,
+                             EGL_BLUE_SIZE,
+                             8,
+                             EGL_ALPHA_SIZE,
+                             8,
+                             EGL_BIND_TO_TEXTURE_RGBA,
+                             EGL_TRUE,
+                             EGL_NONE};
+
+  // Try to allow preservation of the frame contents between swap calls --
+  // this will allow rendering of only parts of the frame that have changed.
+  DCHECK_EQ(EGL_SURFACE_TYPE, attribute_list[0]);
+  EGLint& surface_type_value = attribute_list[1];
 
   EGLint num_configs;
-  EGL_CALL(
-      eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs));
+  eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs);
+  if (eglGetError() != EGL_SUCCESS || num_configs == 0) {
+    // Swap buffer preservation may not be supported. Try to find a config
+    // without the feature.
+    surface_type_value &= ~EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
+    EGL_CALL(
+        eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs));
+  }
   DCHECK_EQ(1, num_configs);
 
 #if defined(GLES3_SUPPORTED)
diff --git a/src/cobalt/renderer/backend/egl/render_target.h b/src/cobalt/renderer/backend/egl/render_target.h
index d88c385..9256372 100644
--- a/src/cobalt/renderer/backend/egl/render_target.h
+++ b/src/cobalt/renderer/backend/egl/render_target.h
@@ -27,7 +27,11 @@
 
 class RenderTargetEGL : public RenderTarget {
  public:
-  RenderTargetEGL() : swap_count_(0), has_been_made_current_(false) {}
+  RenderTargetEGL()
+      : swap_count_(0)
+      , has_been_made_current_(false)
+      , content_preserved_on_swap_(false)
+  {}
 
   // An EGLSurface is needed for the EGL function eglMakeCurrent() which
   // associates a render target with a rendering context.
@@ -40,6 +44,10 @@
 
   virtual bool IsWindowRenderTarget() const { return false; }
 
+  // Returns whether the render target contents are preserved after the
+  // target has been displayed via eglSwapBuffers().
+  bool IsContentPreservedOnSwap() const { return content_preserved_on_swap_; }
+
   int64 swap_count() { return swap_count_; }
   void increment_swap_count() { ++swap_count_; }
 
@@ -51,6 +59,7 @@
 
   int64 swap_count_;
   bool has_been_made_current_;
+  bool content_preserved_on_swap_;
 };
 
 }  // namespace backend
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
index edc0e40..86fc108 100644
--- a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
+++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
@@ -32,7 +32,7 @@
 TextureDataPBO::TextureDataPBO(ResourceContext* resource_context,
                                const math::Size& size, GLenum format)
     : resource_context_(resource_context), size_(size), format_(format) {
-  data_size_ = GetPitchInBytes() * size_.height();
+  data_size_ = static_cast<int64>(GetPitchInBytes()) * size_.height();
 
   resource_context_->RunSynchronouslyWithinResourceContext(
       base::Bind(&TextureDataPBO::InitAndMapPBO, base::Unretained(this)));
@@ -57,7 +57,7 @@
       GL_PIXEL_UNPACK_BUFFER, 0, data_size_, GL_MAP_WRITE_BIT |
                                                  GL_MAP_INVALIDATE_BUFFER_BIT |
                                                  GL_MAP_UNSYNCHRONIZED_BIT));
-  DCHECK(mapped_data_);
+  CHECK(mapped_data_);
   DCHECK_EQ(GL_NO_ERROR, glGetError());
   GL_CALL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
 }
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.h b/src/cobalt/renderer/backend/egl/texture_data_pbo.h
index 56754ea..d99ca54 100644
--- a/src/cobalt/renderer/backend/egl/texture_data_pbo.h
+++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.h
@@ -63,7 +63,7 @@
   math::Size size_;
   GLenum format_;
   GLuint pixel_buffer_;
-  int data_size_;
+  int64 data_size_;
   GLubyte* mapped_data_;
 };
 
diff --git a/src/cobalt/renderer/backend/graphics_context.h b/src/cobalt/renderer/backend/graphics_context.h
index e5743f5..bd51d53 100644
--- a/src/cobalt/renderer/backend/graphics_context.h
+++ b/src/cobalt/renderer/backend/graphics_context.h
@@ -62,6 +62,9 @@
   virtual scoped_array<uint8_t> DownloadPixelDataAsRGBA(
       const scoped_refptr<RenderTarget>& render_target) = 0;
 
+  // Waits until all drawing is finished.
+  virtual void Finish() = 0;
+
  private:
   GraphicsSystem* system_;
 };
diff --git a/src/cobalt/renderer/backend/graphics_context_stub.h b/src/cobalt/renderer/backend/graphics_context_stub.h
index 5cb36bd..b2e0f1b 100644
--- a/src/cobalt/renderer/backend/graphics_context_stub.h
+++ b/src/cobalt/renderer/backend/graphics_context_stub.h
@@ -45,6 +45,8 @@
     return scoped_array<uint8_t>(
         new uint8_t[render_target->GetSize().GetArea() * 4]);
   }
+
+  void Finish() OVERRIDE {}
 };
 
 }  // namespace backend
diff --git a/src/cobalt/renderer/frame_rate_throttler.cc b/src/cobalt/renderer/frame_rate_throttler.cc
new file mode 100644
index 0000000..20d662e
--- /dev/null
+++ b/src/cobalt/renderer/frame_rate_throttler.cc
@@ -0,0 +1,47 @@
+/*
+ * 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/frame_rate_throttler.h"
+
+#include "base/threading/platform_thread.h"
+
+namespace cobalt {
+namespace renderer {
+
+void FrameRateThrottler::BeginInterval() {
+  if (COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0) {
+    begin_time_ = base::TimeTicks::HighResNow();
+  }
+}
+
+void FrameRateThrottler::EndInterval() {
+  // Throttle presentation of new frames if a minimum frame time is specified.
+  if (COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0) {
+    if (!begin_time_.is_null()) {
+      base::TimeDelta elapsed = base::TimeTicks::HighResNow() - begin_time_;
+      base::TimeDelta wait_time =
+          base::TimeDelta::FromMillisecondsD(
+              COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS) -
+          elapsed;
+      if (wait_time > base::TimeDelta::FromMicroseconds(0)) {
+        base::PlatformThread::Sleep(wait_time);
+      }
+    }
+  }
+}
+
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/frame_rate_throttler.h b/src/cobalt/renderer/frame_rate_throttler.h
new file mode 100644
index 0000000..6af2be7
--- /dev/null
+++ b/src/cobalt/renderer/frame_rate_throttler.h
@@ -0,0 +1,44 @@
+/*
+ * 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_FRAME_RATE_THROTTLER_H_
+#define COBALT_RENDERER_FRAME_RATE_THROTTLER_H_
+
+#include "base/time.h"
+
+namespace cobalt {
+namespace renderer {
+
+// The FrameRateThrottler is used to enforce a minimum frame time. The
+// rasterizer should call the provided hooks just before and after submitting
+// a new frame. This can be used to throttle the frame rate (to 30 Hz for
+// example) when the presentation interval cannot be specified.
+class FrameRateThrottler {
+ public:
+  // To be called just after submitting a new frame.
+  void BeginInterval();
+
+  // To be called just before submitting a new frame.
+  void EndInterval();
+
+ private:
+  base::TimeTicks begin_time_;
+};
+
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_FRAME_RATE_THROTTLER_H_
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl
index 6fcae22..0a0d63b 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl
@@ -11,7 +11,7 @@
     // Stage 0: YUV to RGB
     output_Stage0 = vec4(
         texture2D(uSampler0_Stage0, vMatrixCoord_Stage0).aaaa.r,
-        texture2D(uSampler1_Stage0, vMatrixCoord_Stage0).ba,
+        texture2D(uSampler1_Stage0, vMatrixCoord_Stage0).ra,
         1.0) * uYUVMatrix_Stage0;
   }
   gl_FragColor = output_Stage0;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
index b0e8820..7500fc3 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
+++ b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
@@ -64,6 +64,7 @@
       'vertex_skia_antialiased_color_only.glsl',
       'vertex_skia_antialiased_oval.glsl',
       'vertex_skia_color_only.glsl',
+      'vertex_mesh.glsl',
       'vertex_skia_texcoords_and_color.glsl',
       'vertex_skia_texcoords_and_color_with_texcoord_matrix.glsl',
       'vertex_skia_texcoords_derived_from_position.glsl',
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
new file mode 100644
index 0000000..b41b78f
--- /dev/null
+++ b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
@@ -0,0 +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.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/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index df77418..58d850b 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -65,15 +65,15 @@
 
 Pipeline::Pipeline(const CreateRasterizerFunction& create_rasterizer_function,
                    const scoped_refptr<backend::RenderTarget>& render_target,
-                   backend::GraphicsContext* graphics_context)
+                   backend::GraphicsContext* graphics_context,
+                   bool submit_even_if_render_tree_is_unchanged)
     : rasterizer_created_event_(true, false),
       render_target_(render_target),
       graphics_context_(graphics_context),
       rasterizer_thread_("Rasterizer"),
       submission_disposal_thread_("Rasterizer Submission Disposal"),
-      rasterize_current_tree_interval_timer_(
-          "Renderer.Rasterize.Interval",
-          kRasterizeCurrentTreeTimerTimeIntervalInMs),
+      submit_even_if_render_tree_is_unchanged_(
+          submit_even_if_render_tree_is_unchanged),
       rasterize_current_tree_timer_("Renderer.Rasterize.Duration",
                                     kRasterizeCurrentTreeTimerTimeIntervalInMs)
 #if defined(ENABLE_DEBUG_CONSOLE)
@@ -121,6 +121,12 @@
                             base::Unretained(&submission_queue_shutdown)));
   submission_queue_shutdown.Wait();
 
+  // This potential reference to a render tree whose animations may have ended
+  // must be destroyed before we shutdown the rasterizer thread since it may
+  // contain references to render tree nodes and resources.
+  last_rendered_expired_render_tree_ = NULL;
+  last_render_tree_ = NULL;
+
   // Submit a shutdown task to the rasterizer thread so that it can shutdown
   // anything that must be shutdown from that thread.
   rasterizer_thread_.message_loop()->PostTask(
@@ -221,17 +227,15 @@
   base::TimeTicks now = base::TimeTicks::Now();
   Submission submission = submission_queue_->GetCurrentSubmission(now);
 
-#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
   // If our render tree hasn't changed from the one that was previously rendered
   // and the animations on the previously rendered tree have expired, and it's
   // okay on this system to not flip the display buffer frequently, then we
   // can just not do anything here.
-  if (submission.render_tree == last_rendered_expired_render_tree_) {
+  if (!submit_even_if_render_tree_is_unchanged_ &&
+      submission.render_tree == last_rendered_expired_render_tree_) {
     return;
   }
-#endif  // #if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
 
-  rasterize_current_tree_interval_timer_.Start(now);
   rasterize_current_tree_timer_.Start(now);
 
   // Rasterize the last submitted render tree.
@@ -239,17 +243,18 @@
 
   rasterize_current_tree_timer_.Stop();
 
-#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
   // Check whether the animations in the render tree that was just rasterized
   // have expired or not, and if so, mark that down so that if we see it in
   // the future we don't spend the time re-rendering it.
-  render_tree::animations::AnimateNode* animate_node =
-      base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
-          submission.render_tree.get());
-  last_rendered_expired_render_tree_ =
-      animate_node->expiry() <= submission.time_offset ? submission.render_tree
-                                                       : NULL;
-#endif  // defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
+  if (!submit_even_if_render_tree_is_unchanged_) {
+    render_tree::animations::AnimateNode* animate_node =
+        base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
+            submission.render_tree.get());
+    last_rendered_expired_render_tree_ =
+        animate_node->expiry() <= submission.time_offset
+            ? submission.render_tree
+            : NULL;
+  }
 }
 
 void Pipeline::RasterizeSubmissionToRenderTarget(
@@ -257,19 +262,45 @@
     const scoped_refptr<backend::RenderTarget>& render_target) {
   TRACE_EVENT0("cobalt::renderer",
                "Pipeline::RasterizeSubmissionToRenderTarget()");
+
+  // Keep track of the last render tree that we rendered so that we can watch
+  // if it changes, in which case we should reset our tracked
+  // |previous_animated_area_|.
+  if (submission.render_tree != last_render_tree_) {
+    last_render_tree_ = submission.render_tree;
+    previous_animated_area_ = base::nullopt;
+    last_render_time_ = base::nullopt;
+  }
+
   // Animate the render tree using the submitted animations.
   render_tree::animations::AnimateNode* animate_node =
       base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
           submission.render_tree.get());
-  scoped_refptr<Node> animated_render_tree =
+  render_tree::animations::AnimateNode::AnimateResults results =
       animate_node->Apply(submission.time_offset);
 
+  // Calculate a bounding box around the active animations.  Union it with the
+  // bounding box around active animations from the previous frame, and we get
+  // a scissor rectangle marking the dirty regions of the screen.
+  math::RectF animated_bounds = results.get_animation_bounds_since.Run(
+      last_render_time_ ? *last_render_time_ : base::TimeDelta());
+  math::Rect rounded_bounds = math::RoundOut(animated_bounds);
+  base::optional<math::Rect> redraw_area;
+  if (previous_animated_area_) {
+    redraw_area = math::UnionRects(rounded_bounds, *previous_animated_area_);
+  }
+  previous_animated_area_ = rounded_bounds;
+
   // Rasterize the animated render tree.
-  rasterizer_->Submit(animated_render_tree, render_target);
+  rasterizer::Rasterizer::Options rasterizer_options;
+  rasterizer_options.dirty = redraw_area;
+  rasterizer_->Submit(results.animated, render_target, rasterizer_options);
 
   if (!submission.on_rasterized_callback.is_null()) {
     submission.on_rasterized_callback.Run();
   }
+
+  last_render_time_ = submission.time_offset;
 }
 
 void Pipeline::InitializeRasterizerThread(
@@ -344,11 +375,10 @@
   render_tree::animations::AnimateNode* animate_node =
       base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
           submission.render_tree.get());
-  scoped_refptr<Node> animated_render_tree =
+  render_tree::animations::AnimateNode::AnimateResults results =
       animate_node->Apply(submission.time_offset);
 
-  std::string tree_dump =
-      render_tree::DumpRenderTreeToString(animated_render_tree);
+  std::string tree_dump = render_tree::DumpRenderTreeToString(results.animated);
   if (message.empty() || message == "undefined") {
     // If no filename was specified, send output to the console.
     LOG(INFO) << tree_dump.c_str();
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index cec3874..d3a2e96 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -34,13 +34,6 @@
 #include "cobalt/renderer/submission.h"
 #include "cobalt/renderer/submission_queue.h"
 
-#if defined(OS_STARBOARD) && !SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER
-// 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
-// if it has not changed from the last frame.
-#define DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE
-#endif
-
 namespace cobalt {
 namespace renderer {
 
@@ -66,7 +59,8 @@
   // thread safe objects.
   Pipeline(const CreateRasterizerFunction& create_rasterizer_function,
            const scoped_refptr<backend::RenderTarget>& render_target,
-           backend::GraphicsContext* graphics_context);
+           backend::GraphicsContext* graphics_context,
+           bool submit_even_if_render_tree_is_unchanged);
   ~Pipeline();
 
   // Submit a new render tree to the renderer pipeline.  After calling this
@@ -173,17 +167,26 @@
   // the future.
   base::optional<SubmissionQueue> submission_queue_;
 
-#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
   // Keep track of the last render tree we rendered whose animations have
   // expired so that if we see that we are asked to rasterize that render tree
   // again, we know that we do not have to do anything.
   scoped_refptr<render_tree::Node> last_rendered_expired_render_tree_;
-#endif  // defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE)
 
-  // Timers for tracking how frequently |RasterizeCurrentTree| is called and
-  // the amount of time spent in |RasterizeCurrentTree| each call.
-  base::CValTimeIntervalTimer<base::CValPublic>
-      rasterize_current_tree_interval_timer_;
+  // If true, we will submit the current render tree to the rasterizer every
+  // frame, even if it hasn't changed.
+  const bool submit_even_if_render_tree_is_unchanged_;
+
+  // Keeps track of the last rendered animated render tree.
+  scoped_refptr<render_tree::Node> last_render_tree_;
+  // Keeps track of the area of the screen that animations previously existed
+  // within, so that we can know which regions of the screens would be dirty
+  // next frame.
+  base::optional<math::Rect> previous_animated_area_;
+  // The submission time used during the last render tree render.
+  base::optional<base::TimeDelta> last_render_time_;
+
+  // Timer tracking the amount of time spent in |RasterizeCurrentTree| each
+  // call.
   base::CValTimeIntervalTimer<base::CValPublic> rasterize_current_tree_timer_;
 
 #if defined(ENABLE_DEBUG_CONSOLE)
diff --git a/src/cobalt/renderer/pipeline_test.cc b/src/cobalt/renderer/pipeline_test.cc
index f66b4e5..c70d44e 100644
--- a/src/cobalt/renderer/pipeline_test.cc
+++ b/src/cobalt/renderer/pipeline_test.cc
@@ -52,7 +52,7 @@
 
   void Submit(const scoped_refptr<cobalt::render_tree::Node>&,
               const scoped_refptr<cobalt::renderer::backend::RenderTarget>&,
-              int options) OVERRIDE {
+              const Options& options) OVERRIDE {
     if (last_submission_time) {
       // Simulate a "wait for vsync".
       base::TimeDelta since_last_submit =
@@ -88,7 +88,8 @@
     submission_count_ = 0;
     start_time_ = base::TimeTicks::Now();
     pipeline_.reset(new Pipeline(
-        base::Bind(&CreateMockRasterizer, &submission_count_), NULL, NULL));
+        base::Bind(&CreateMockRasterizer, &submission_count_), NULL, NULL,
+                   true));
   }
 
   static void DummyAnimateFunction(
diff --git a/src/cobalt/renderer/rasterizer/benchmark.cc b/src/cobalt/renderer/rasterizer/benchmark.cc
index 097d0bf..c0d2567 100644
--- a/src/cobalt/renderer/rasterizer/benchmark.cc
+++ b/src/cobalt/renderer/rasterizer/benchmark.cc
@@ -116,11 +116,14 @@
   for (int i = 0; i < kRenderIterationCount; ++i) {
     AnimateNode* animate_node =
         base::polymorphic_downcast<AnimateNode*>(scene.get());
-    scoped_refptr<Node> animated = animate_node->Apply(
-        base::TimeDelta::FromSecondsD(i * kFixedTimeStepInSecondsPerFrame));
+    scoped_refptr<Node> animated =
+        animate_node->Apply(base::TimeDelta::FromSecondsD(
+                                i * kFixedTimeStepInSecondsPerFrame))
+            .animated;
 
     // Submit the render tree to be rendered.
     rasterizer->Submit(animated, test_surface);
+    graphics_context->Finish();
 
     if (i == 0) {
       // Enable tracing again after one iteration has passed and any lazy
@@ -183,6 +186,10 @@
       CreateDefaultRasterizer(graphics_context.get());
 
   ResourceProvider* resource_provider = rasterizer->GetResourceProvider();
+  if (!resource_provider->AlphaFormatSupported(alpha_format)) {
+    // Only run the test if the alpha format is supported.
+    return;
+  }
 
   const int kIterationCount = 20;
   const Size kImageSize(400, 400);
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..5e65c9c
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc
@@ -0,0 +1,251 @@
+/*
+ * 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."),
+      cache_frame_usage_(
+          "Memory.CachedSoftwareRasterizer.FrameCacheUsage", 0,
+          "Total memory occupied by cache software-rasterizer surfaces that "
+          "were referenced this frame.") {}
+
+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 was referenced mark it as being unreferenced for the next
+    // frame.
+    if (current->second.referenced) {
+#if defined(ENABLE_DEBUG_CONSOLE)
+      current->second.created = false;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+      current->second.referenced = false;
+    }
+  }
+
+  // Reset our current frame cache usage to 0 since this is the start of a new
+  // frame.
+  cache_frame_usage_ = 0;
+}
+
+void CachedSoftwareRasterizer::PurgeUntilSpaceAvailable(int space_needed) {
+  while (space_needed + cache_memory_usage_.value() > cache_capacity_) {
+    CacheMap::iterator next_item = surface_map_.begin();
+
+    // We shouldn't call this function if it means we would have to purge
+    // elements that were referenced this frame.  This is to avoid thrashing
+    // the cache, we would prefer to cache as much as we can in a frame, and
+    // then just not cache whatever we can't without cycling what's in the cache
+    // already.
+    DCHECK(!next_item->second.referenced);
+    cache_memory_usage_ -= next_item->second.GetEstimatedMemoryUsage();
+    SbBlitterDestroySurface(next_item->second.surface);
+    surface_map_.erase(next_item);
+  }
+}
+
+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
+      std::pair<render_tree::Node*, Surface> to_insert =
+          std::make_pair(node, found->second);
+
+      // Move this surface's position in the queue to the front, since it was
+      // referenced.
+      to_insert.second.referenced = true;
+      surface_map_.erase(found);
+      surface_map_.insert(to_insert);
+
+      cache_frame_usage_ += to_insert.second.GetEstimatedMemoryUsage();
+
+      return to_insert.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_frame_usage_.value() <=
+      cache_capacity_) {
+    software_surface.cached = true;
+    cache_frame_usage_ += software_surface.GetEstimatedMemoryUsage();
+
+    if (found != surface_map_.end()) {
+      // This surface may have already been in the cache if it was in there
+      // with a different scale.  In that case, replace the old one.
+      cache_memory_usage_ -= found->second.GetEstimatedMemoryUsage();
+      surface_map_.erase(found);
+    }
+
+    PurgeUntilSpaceAvailable(software_surface.GetEstimatedMemoryUsage());
+
+    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..10d76e9
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h
@@ -0,0 +1,156 @@
+/*
+ * 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/containers/linked_hash_map.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:
+  typedef base::linked_hash_map<render_tree::Node*, Surface> CacheMap;
+
+  // Release surfaces until we have |space_needed| free bytes in the cache.
+  // This function will never release surfaces that were referenced this frame.
+  // It is an error to call this function if it is impossible to purge
+  // unreferenced surfaces until the desired amount of free space is available.
+  void PurgeUntilSpaceAvailable(int space_needed);
+
+  // The cache, mapping input render_tree::Node references to cached surfaces.
+  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_;
+
+  // Cache memory used this frame only.
+  base::CVal<base::cval::SizeInBytes> cache_frame_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/cobalt_blitter_conversions.cc b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc
index 7206ba2..29a960a 100644
--- a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc
@@ -23,11 +23,9 @@
 namespace rasterizer {
 namespace blitter {
 
-namespace {
 int RoundToInt(float value) {
   return static_cast<int>(std::floor(value + 0.5f));
 }
-}  // namespace
 
 math::Rect RectFToRect(const math::RectF& rectf) {
   // We convert from floating point to integer in such a way that two boxes
diff --git a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h
index 29afcec..d13b77d 100644
--- a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h
+++ b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h
@@ -28,6 +28,7 @@
 namespace rasterizer {
 namespace blitter {
 
+int RoundToInt(float value);
 math::Rect RectFToRect(const math::RectF& rectf);
 
 inline SbBlitterRect RectToBlitterRect(const math::Rect& rect) {
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
index 2868926..cfca7ab 100644
--- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
@@ -16,11 +16,18 @@
 
 #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/frame_rate_throttler.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,43 +46,82 @@
  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,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options);
+              const Options& options);
 
   render_tree::ResourceProvider* GetResourceProvider();
 
  private:
+#if defined(ENABLE_DEBUG_CONSOLE)
+  void OnToggleHighlightSoftwareDraws(const std::string& message);
+#endif
+  void SetupLastFrameSurface(int width, int height);
+
   base::ThreadChecker thread_checker_;
 
   backend::GraphicsContextBlitter* context_;
 
-  skia::SoftwareRasterizer software_rasterizer_;
   scoped_ptr<render_tree::ResourceProvider> resource_provider_;
 
   int64 submit_count_;
 
+  // We maintain a "final results" surface that mirrors the display buffer.
+  // This way, we can rerender only the dirty parts of the screen to this
+  // |current_frame_| buffer and then blit that to the display.
+  SbBlitterSurface current_frame_;
+
   ScratchSurfaceCache scratch_surface_cache_;
   base::optional<SurfaceCacheDelegate> surface_cache_delegate_;
   base::optional<common::SurfaceCache> surface_cache_;
+
+  CachedSoftwareRasterizer software_surface_cache_;
+
+  FrameRateThrottler frame_rate_throttler_;
+
+#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),
+      current_frame_(kSbBlitterInvalidSurface),
       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(),
@@ -86,11 +132,20 @@
   }
 }
 
-HardwareRasterizer::Impl::~Impl() {}
+HardwareRasterizer::Impl::~Impl() { SbBlitterDestroySurface(current_frame_); }
+
+#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) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   TRACE_EVENT0("cobalt::renderer", "Rasterizer::Submit()");
 
   int width = render_target->GetSize().width();
@@ -102,8 +157,12 @@
       base::polymorphic_downcast<backend::RenderTargetBlitter*>(
           render_target.get());
 
-  CHECK(SbBlitterSetRenderTarget(context,
-                                 render_target_blitter->GetSbRenderTarget()));
+  if (!SbBlitterIsSurfaceValid(current_frame_)) {
+    SetupLastFrameSurface(width, height);
+  }
+
+  CHECK(SbBlitterSetRenderTarget(
+      context, SbBlitterGetRenderTargetFromSurface(current_frame_)));
 
   // Update our surface cache to do per-frame calculations such as deciding
   // which render tree nodes are candidates for caching in this upcoming
@@ -112,12 +171,16 @@
     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
   // if the output buffer is not completely rendered to, data from a previous
   // process cannot leak in.
-  if (options & Rasterizer::kSubmitOptions_Clear || submit_count_ < 3) {
+  bool cleared = false;
+  if (options.flags & Rasterizer::kSubmitFlags_Clear || submit_count_ < 3) {
+    cleared = true;
     CHECK(SbBlitterSetBlending(context, false));
     CHECK(SbBlitterSetColor(context, SbBlitterColorFromRGBA(0, 0, 0, 0)));
     CHECK(SbBlitterFillRect(context, SbBlitterMakeRect(0, 0, width, height)));
@@ -127,20 +190,41 @@
     TRACE_EVENT0("cobalt::renderer", "VisitRenderTree");
 
     // Visit the render tree with our Blitter API visitor.
+    BoundsStack start_bounds(context_->GetSbBlitterContext(),
+                             math::Rect(render_target_blitter->GetSize()));
+    if (options.dirty && !cleared) {
+      // If a dirty rectangle was specified, limit our redrawing to within it.
+      start_bounds.Push(*options.dirty);
+    }
+
+    RenderState initial_render_state(
+        SbBlitterGetRenderTargetFromSurface(current_frame_), Transform(),
+        start_bounds);
+#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);
   }
 
   // Finally flip the surface to make visible the rendered results.
+  CHECK(SbBlitterSetRenderTarget(context,
+                                 render_target_blitter->GetSbRenderTarget()));
+  CHECK(SbBlitterSetBlending(context, false));
+  CHECK(SbBlitterSetModulateBlitsWithColor(context, false));
+  CHECK(SbBlitterBlitRectToRect(context, current_frame_,
+                                SbBlitterMakeRect(0, 0, width, height),
+                                SbBlitterMakeRect(0, 0, width, height)));
   CHECK(SbBlitterFlushContext(context));
+  frame_rate_throttler_.EndInterval();
   render_target_blitter->Flip();
+  frame_rate_throttler_.BeginInterval();
 
   ++submit_count_;
 }
@@ -149,17 +233,26 @@
   return resource_provider_.get();
 }
 
+void HardwareRasterizer::Impl::SetupLastFrameSurface(int width, int height) {
+  current_frame_ =
+      SbBlitterCreateRenderTargetSurface(context_->GetSbBlitterDevice(), width,
+                                         height, kSbBlitterSurfaceFormatRGBA8);
+}
+
 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() {}
 
 void HardwareRasterizer::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const scoped_refptr<backend::RenderTarget>& render_target, int options) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   return impl_->Submit(render_tree, render_target, options);
 }
 
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
index abf4d4e..5ab8fd5 100644
--- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
@@ -39,14 +39,15 @@
  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
   // into the constructor.
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options) OVERRIDE;
+              const Options& options) OVERRIDE;
 
   render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
 
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.cc b/src/cobalt/renderer/rasterizer/blitter/image.cc
index 3e8ece5..af46382 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,9 +64,22 @@
   surface_ = SbBlitterCreateSurfaceFromPixelData(image_data->device(),
                                                  image_data->TakePixelData());
   CHECK(SbBlitterIsSurfaceValid(surface_));
+
+  is_opaque_ = image_data->GetDescriptor().alpha_format ==
+               render_tree::kAlphaFormatOpaque;
 }
 
-void SinglePlaneImage::EnsureInitialized() {}
+SinglePlaneImage::SinglePlaneImage(SbBlitterSurface surface, bool is_opaque)
+    : surface_(surface), is_opaque_(is_opaque) {
+  CHECK(SbBlitterIsSurfaceValid(surface_));
+  SbBlitterSurfaceInfo info;
+  if (!SbBlitterGetSurfaceInfo(surface_, &info)) {
+    NOTREACHED();
+  }
+  size_ = math::Size(info.width, info.height);
+}
+
+bool SinglePlaneImage::EnsureInitialized() { return false; }
 
 const SkBitmap& SinglePlaneImage::GetBitmap() const {
   // This function will only ever get called if the Skia software renderer needs
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.h b/src/cobalt/renderer/rasterizer/blitter/image.h
index 7275225..65229ab 100644
--- a/src/cobalt/renderer/rasterizer/blitter/image.h
+++ b/src/cobalt/renderer/rasterizer/blitter/image.h
@@ -71,25 +71,30 @@
 class SinglePlaneImage : public skia::SinglePlaneImage {
  public:
   explicit SinglePlaneImage(scoped_ptr<ImageData> image_data);
+  SinglePlaneImage(SbBlitterSurface surface, bool is_opaque);
 
   const math::Size& GetSize() const OVERRIDE { return size_; }
 
   SbBlitterSurface surface() const { return surface_; }
 
   // Overrides from skia::SinglePlaneImage.
-  void EnsureInitialized() OVERRIDE;
+  bool EnsureInitialized() OVERRIDE;
 
   // When GetBitmap() is called on a blitter::SinglePlaneImage for the first
   // time, we do a one-time download of the pixel data from the Blitter API
   // 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/linear_gradient.cc b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
new file mode 100644
index 0000000..1dc7e96
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
@@ -0,0 +1,311 @@
+/*
+ * 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/linear_gradient.h"
+
+#include <algorithm>
+
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h"
+#include "cobalt/renderer/rasterizer/blitter/render_state.h"
+#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h"
+#include "cobalt/renderer/rasterizer/skia/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"
+#include "third_party/skia/include/core/SkShader.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+
+#if SB_HAS(BLITTER)
+
+namespace {
+
+using cobalt::render_tree::ColorRGBA;
+using cobalt::render_tree::ColorStopList;
+using cobalt::render_tree::LinearGradientBrush;
+using cobalt::renderer::rasterizer::blitter::RenderState;
+using cobalt::renderer::rasterizer::blitter::SkiaToBlitterPixelFormat;
+using cobalt::renderer::rasterizer::blitter::RectFToRect;
+using cobalt::renderer::rasterizer::skia::SkiaColorStops;
+
+int GetPixelOffsetInBytes(const SkImageInfo& image_info,
+                          const SbBlitterPixelData& pixel_data) {
+  if (image_info.width() == 1)
+    return SbBlitterGetPixelDataPitchInBytes(pixel_data);
+
+  SbBlitterPixelDataFormat pixel_format =
+      SkiaToBlitterPixelFormat(image_info.colorType());
+  return SbBlitterBytesPerPixelForFormat(pixel_format);
+}
+
+void ColorStopToPixelData(SbBlitterPixelDataFormat pixel_format,
+                          const ColorRGBA& color_stop, uint8_t* pixel_data_out,
+                          uint8_t* pixel_data_out_end) {
+  DCHECK(pixel_data_out_end >=
+         (pixel_data_out + SbBlitterBytesPerPixelForFormat(pixel_format)));
+  uint8_t final_b(color_stop.rgb8_b());
+  uint8_t final_g(color_stop.rgb8_g());
+  uint8_t final_r(color_stop.rgb8_r());
+  uint8_t final_a(color_stop.rgb8_a());
+
+  switch (pixel_format) {
+    case kSbBlitterPixelDataFormatARGB8:
+      pixel_data_out[0] = final_a;
+      pixel_data_out[1] = final_r;
+      pixel_data_out[2] = final_g;
+      pixel_data_out[3] = final_b;
+      break;
+    case kSbBlitterPixelDataFormatBGRA8:
+      pixel_data_out[0] = final_b;
+      pixel_data_out[1] = final_g;
+      pixel_data_out[2] = final_r;
+      pixel_data_out[3] = final_a;
+      break;
+    case kSbBlitterPixelDataFormatRGBA8:
+      pixel_data_out[0] = final_r;
+      pixel_data_out[1] = final_g;
+      pixel_data_out[2] = final_b;
+      pixel_data_out[3] = final_a;
+      break;
+    default:
+      NOTREACHED() << "Unknown or unsupported pixel format." << pixel_format;
+      break;
+  }
+}
+
+// This function uses Skia to render a gradient into the pixel data pointed
+// at pixel_data_begin.  This function is intended as a fallback in case when
+// RenderOptimizedLinearGradient is unable to render the gradient.
+void RenderComplexLinearGradient(const LinearGradientBrush& brush,
+                                 const ColorStopList& color_stops, int width,
+                                 int height, const SkImageInfo& image_info,
+                                 uint8_t* pixel_data_begin,
+                                 int pitch_in_bytes) {
+  SkBitmap bitmap;
+  bitmap.installPixels(image_info, pixel_data_begin, pitch_in_bytes);
+  SkCanvas canvas(bitmap);
+  canvas.clear(SkColorSetARGB(0, 0, 0, 0));
+
+  SkPoint points[2];
+  points[0].fX = 0;
+  points[0].fY = 0;
+  points[1].fX = 0;
+  points[1].fY = 0;
+
+  // The -1 offset is easiest to think about in a 2 pixel case:
+  // If width = 2, then the ending coordinate should be 1.
+  if (brush.IsHorizontal()) {
+    if (brush.dest().x() < brush.source().x()) {
+      points[0].fX = width - 1;  // Right to Left.
+    } else {
+      points[1].fX = width - 1;  // Left to Right.
+    }
+  } else {
+    if (brush.dest().y() < brush.source().y()) {
+      points[0].fY = height - 1;  // High to Low.
+    } else {
+      points[1].fY = height - 1;  // Low to High.
+    }
+  }
+
+  SkiaColorStops skia_color_stops(color_stops);
+  SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(
+      points, skia_color_stops.colors.data(), skia_color_stops.positions.data(),
+      skia_color_stops.size(), SkShader::kClamp_TileMode,
+      SkGradientShader::kInterpolateColorsInPremul_Flag, NULL));
+  SkPaint paint;
+  paint.setShader(shader);
+  canvas.drawPaint(paint);
+}
+
+// This function tries to render a "simple" gradient into the pixel data pointed
+// to in the range [pixel_data_begin, pixel_data_end).  Simple gradients here
+// are defined as has following properties:
+//   1.  Vertical or horizontal gradient.
+//   2.  Two color stops only -- one at position 0, and one at position 1.
+//
+// Having these conditions greatly simplifies the logic, and allows us to avoid
+// the overhead of dealing with Skia.
+//
+// Returns: true if it could successfully render, false if optimal conditions
+// are not met.  If false is returned, nothing is written to the pixel data
+// buffer.
+bool RenderSimpleGradient(const LinearGradientBrush& brush,
+                          const ColorStopList& color_stops, int width,
+                          int height, SbBlitterPixelDataFormat pixel_format,
+                          uint8_t* pixel_data_begin, uint8_t* pixel_data_end,
+                          int pixel_offset_in_bytes) {
+  if (color_stops.size() != 2) {
+    return false;
+  }
+
+  const float small_value(1E-6);
+  if ((color_stops[0].position < -small_value) ||
+      (color_stops[0].position > small_value)) {
+    return false;
+  }
+  if ((color_stops[1].position < 1 - small_value) ||
+      (color_stops[1].position > 1 + small_value)) {
+    return false;
+  }
+
+  const int number_pixels = width * height;
+
+  if ((number_pixels < 2) || (width < 0) || (height < 0)) {
+    return false;
+  }
+
+  ColorRGBA start_color = color_stops[0].color;
+  ColorRGBA end_color = color_stops[1].color;
+
+  // Swap start and end colors if the gradient direction is right to left or
+  // down to up.
+  if (brush.IsHorizontal()) {
+    if (brush.dest().x() < brush.source().x()) {
+      std::swap(start_color, end_color);
+    }
+  } else {
+    if (brush.dest().y() < brush.source().y()) {
+      std::swap(start_color, end_color);
+    }
+  }
+
+  ColorRGBA color_step((end_color - start_color) / (number_pixels - 1));
+  // We add the color_stop * 0.5f to match pixel positioning in software Skia.
+  ColorRGBA current_color(start_color + color_step * 0.5f);
+  ColorStopToPixelData(pixel_format, current_color, pixel_data_begin,
+                       pixel_data_end);
+
+  uint8_t* second_pixel = pixel_data_begin + pixel_offset_in_bytes;
+  uint8_t* last_pixel =
+      (pixel_data_begin + (number_pixels - 1) * pixel_offset_in_bytes);
+  for (uint8_t* current_pixel = second_pixel; current_pixel != last_pixel;
+       current_pixel += pixel_offset_in_bytes) {
+    current_color += color_step;
+    ColorStopToPixelData(pixel_format, current_color, current_pixel,
+                         pixel_data_end);
+  }
+
+  ColorStopToPixelData(pixel_format, end_color, last_pixel, pixel_data_end);
+
+  return true;
+}
+
+void RenderOptimizedLinearGradient(SbBlitterDevice device,
+                                   SbBlitterContext context,
+                                   const RenderState& render_state,
+                                   cobalt::math::RectF rect,
+                                   const LinearGradientBrush& brush) {
+  if ((rect.width() == 0) && (rect.height() == 0)) return;
+
+  DCHECK(brush.IsVertical() || brush.IsHorizontal());
+
+  // The main strategy here is to create a 1D image, and then calculate
+  // the gradient values in software.  If the gradient is simple, this can be
+  // one with optimized function (RenderSimpleGradient), which avoids calling
+  // Skia (and thus is faster).  Otherwise, we call RenderComplexLinearGradient,
+  // which uses Skia.
+
+  // One a gradient is created, a SbBlitterSurface is created and a rectangle
+  // is blitted using the blitter API.
+  int width = brush.IsHorizontal() ? rect.width() : 1;
+  int height = brush.IsVertical() ? rect.height() : 1;
+
+  SkImageInfo image_info = SkImageInfo::MakeN32Premul(width, height);
+
+  SbBlitterPixelDataFormat pixel_format =
+      SkiaToBlitterPixelFormat(image_info.colorType());
+
+  if (!SbBlitterIsPixelFormatSupportedByPixelData(device, pixel_format)) {
+    NOTREACHED() << "Pixel Format is not supported by Pixel Data";
+    return;
+  }
+
+  SbBlitterPixelData pixel_data =
+      SbBlitterCreatePixelData(device, width, height, pixel_format);
+
+  int bytes_per_pixel = GetPixelOffsetInBytes(image_info, pixel_data);
+  int pitch_in_bytes = SbBlitterGetPixelDataPitchInBytes(pixel_data);
+
+  uint8_t* pixel_data_begin =
+      static_cast<uint8_t*>(SbBlitterGetPixelDataPointer(pixel_data));
+  uint8_t* pixel_data_end = pixel_data_begin + pitch_in_bytes * height;
+
+  if (pixel_data) {
+    const ColorStopList& color_stops(brush.color_stops());
+
+    int pixel_offset_in_bytes = (width == 1) ? pitch_in_bytes : bytes_per_pixel;
+    bool render_successful = RenderSimpleGradient(
+        brush, color_stops, width, height, pixel_format, pixel_data_begin,
+        pixel_data_end, pixel_offset_in_bytes);
+    if (!render_successful) {
+      RenderComplexLinearGradient(brush, color_stops, width, height, image_info,
+                                  pixel_data_begin, pitch_in_bytes);
+    }
+
+    SbBlitterSurface surface =
+        SbBlitterCreateSurfaceFromPixelData(device, pixel_data);
+
+    if (surface) {
+      SbBlitterSetBlending(context, true);
+      SbBlitterSetModulateBlitsWithColor(context, false);
+      cobalt::math::Rect transformed_rect =
+          RectFToRect(render_state.transform.TransformRect(rect));
+      SbBlitterRect source_rect = SbBlitterMakeRect(0, 0, width, height);
+      SbBlitterRect dest_rect = SbBlitterMakeRect(
+          transformed_rect.x(), transformed_rect.y(), transformed_rect.width(),
+          transformed_rect.height());
+      SbBlitterBlitRectToRect(context, surface, source_rect, dest_rect);
+
+      SbBlitterDestroySurface(surface);
+    }
+  }
+}
+}  // namespace
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace blitter {
+
+using render_tree::LinearGradientBrush;
+
+bool RenderLinearGradient(SbBlitterDevice device, SbBlitterContext context,
+                          const RenderState& render_state,
+                          const render_tree::RectNode& rect_node) {
+  DCHECK(rect_node.data().background_brush);
+  const LinearGradientBrush* const linear_gradient_brush =
+      base::polymorphic_downcast<LinearGradientBrush*>(
+          rect_node.data().background_brush.get());
+  if (!linear_gradient_brush) return false;
+
+  // Currently, only vertical and horizontal gradients are accelerated.
+  if ((linear_gradient_brush->IsVertical() ||
+       linear_gradient_brush->IsHorizontal()) == false)
+    return false;
+
+  RenderOptimizedLinearGradient(device, context, render_state,
+                                rect_node.data().rect, *linear_gradient_brush);
+  return true;
+}
+
+}  // namespace blitter
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // #if SB_HAS(BLITTER)
diff --git a/src/cobalt/renderer/rasterizer/blitter/linear_gradient.h b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.h
new file mode 100644
index 0000000..8340722
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.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 COBALT_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_
+#define COBALT_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_
+
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/rect_node.h"
+#include "cobalt/renderer/rasterizer/blitter/render_state.h"
+#include "starboard/blitter.h"
+
+#ifndef SB_HAS_BLITTER
+#define SB_HAS_BLITTER
+#endif
+#if SB_HAS(BLITTER)
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace blitter {
+
+bool RenderLinearGradient(SbBlitterDevice device, SbBlitterContext context,
+                          const RenderState& render_state,
+                          const render_tree::RectNode& rect_node);
+
+}  // namespace blitter
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // #if SB_HAS(BLITTER)
+
+#endif  // COBALT_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_
diff --git a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
index 8c75fd1..b5e0bec 100644
--- a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
@@ -38,9 +38,11 @@
       'type': 'static_library',
 
       'sources': [
+        'cached_software_rasterizer.cc',
         'cobalt_blitter_conversions.cc',
         'hardware_rasterizer.cc',
         'image.cc',
+        'linear_gradient.cc',
         'render_state.cc',
         'render_tree_blitter_conversions.cc',
         'render_tree_node_visitor.cc',
@@ -80,4 +82,4 @@
       ],
     },
   ],
-}
\ No newline at end of file
+}
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_state.h b/src/cobalt/renderer/rasterizer/blitter/render_state.h
index 5b6f683..b0a828a 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_state.h
+++ b/src/cobalt/renderer/rasterizer/blitter/render_state.h
@@ -25,6 +25,7 @@
 #include "cobalt/math/rect.h"
 #include "cobalt/math/rect_f.h"
 #include "cobalt/math/vector2d_f.h"
+#include "cobalt/render_tree/brush.h"
 #include "starboard/blitter.h"
 
 #if SB_HAS(BLITTER)
@@ -118,11 +119,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..47dcf43 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
@@ -17,6 +17,8 @@
 #include "cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h"
 
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/math/rect.h"
 #include "cobalt/math/rect_f.h"
@@ -25,15 +27,24 @@
 #include "cobalt/math/vector2d_f.h"
 #include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h"
 #include "cobalt/renderer/rasterizer/blitter/image.h"
+#include "cobalt/renderer/rasterizer/blitter/linear_gradient.h"
 #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)
 
+// This define exists so that developers can quickly toggle it temporarily and
+// obtain trace results for the render tree visit process here.  In general
+// though it slows down tracing too much to leave it enabled.
+#define ENABLE_RENDER_TREE_VISITOR_TRACING 0
+
+#if ENABLE_RENDER_TREE_VISITOR_TRACING
+#define TRACE_EVENT0_IF_ENABLED(x) TRACE_EVENT0("cobalt::renderer", x)
+#else
+#define TRACE_EVENT0_IF_ENABLED(x)
+#endif
+
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
@@ -47,23 +58,25 @@
 using render_tree::Border;
 using render_tree::Brush;
 using render_tree::ColorRGBA;
+using render_tree::ColorStop;
+using render_tree::ColorStopList;
+using render_tree::LinearGradientBrush;
 using render_tree::SolidColorBrush;
 using render_tree::ViewportFilter;
 
 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,8 +90,7 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::CompositionNode* composition_node) {
-  common::SurfaceCache::Block cache_block(surface_cache_, composition_node);
-  if (cache_block.Cached()) return;
+  TRACE_EVENT0_IF_ENABLED("Visit(CompositionNode)");
 
   const render_tree::CompositionNode::Children& children =
       composition_node->data().children();
@@ -99,7 +111,30 @@
   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) {
+  TRACE_EVENT0_IF_ENABLED("Visit(FilterNode)");
+
   if (filter_node->data().blur_filter) {
     // The Starboard Blitter API does not support blur filters, so we fallback
     // to software for this.
@@ -135,6 +170,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 =
@@ -164,6 +209,8 @@
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) {
+  TRACE_EVENT0_IF_ENABLED("Visit(ImageNode)");
+
   // All Blitter API images derive from skia::Image (so that they can be
   // compatible with the Skia software renderer), so we start here by casting
   // to skia::Image.
@@ -202,8 +249,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 +270,14 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::MatrixTransformNode* matrix_transform_node) {
-  common::SurfaceCache::Block cache_block(surface_cache_,
-                                          matrix_transform_node);
-  if (cache_block.Cached()) return;
+  TRACE_EVENT0_IF_ENABLED("Visit(MatrixTransformNode)");
 
   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;
   }
@@ -240,20 +296,16 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::PunchThroughVideoNode* punch_through_video_node) {
+  TRACE_EVENT0_IF_ENABLED("Visit(PunchThroughVideoNode)");
+
   SbBlitterRect blitter_rect =
       RectFToBlitterRect(render_state_.transform.TransformRect(
           punch_through_video_node->data().rect));
 
-  if (punch_through_video_node->data().set_bounds_cb.is_null()) {
-    return;
-  }
-  bool render_punch_through =
-      punch_through_video_node->data().set_bounds_cb.Run(
-          math::Rect(blitter_rect.x, blitter_rect.y, blitter_rect.width,
-                     blitter_rect.height));
-  if (!render_punch_through) {
-    return;
-  }
+  punch_through_video_node->data().set_bounds_cb.Run(
+      math::Rect(blitter_rect.x, blitter_rect.y, blitter_rect.width,
+                 blitter_rect.height));
+
   SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 0, 0, 0));
   SbBlitterSetBlending(context_, false);
   SbBlitterFillRect(context_, blitter_rect);
@@ -277,8 +329,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:
@@ -313,6 +371,8 @@
 }  // namespace
 
 void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) {
+  TRACE_EVENT0_IF_ENABLED("Visit(RectNode)");
+
   if (rect_node->data().rounded_corners) {
     // We can't render rounded corners through the Blitter API.
     RenderWithSoftwareRenderer(rect_node);
@@ -327,31 +387,43 @@
       return;
     }
   }
-  if (rect_node->data().background_brush) {
-    if (rect_node->data().background_brush->GetTypeId() !=
-        base::GetTypeId<SolidColorBrush>()) {
-      // We can only render solid color rectangles, if we have a more
-      // complicated brush (like gradients), fallback to software.
-      RenderWithSoftwareRenderer(rect_node);
-      return;
-    }
-  }
 
   const RectF& transformed_rect =
       render_state_.transform.TransformRect(rect_node->data().rect);
 
-  // Render the solid color fill, if a brush exists.
   if (rect_node->data().background_brush) {
-    SbBlitterSetBlending(context_, true);
+    base::TypeId background_brush_typeid(
+        rect_node->data().background_brush->GetTypeId());
 
-    SolidColorBrush* solid_color_brush =
-        base::polymorphic_downcast<SolidColorBrush*>(
-            rect_node->data().background_brush.get());
-    ColorRGBA color = solid_color_brush->color();
+    if (background_brush_typeid == base::GetTypeId<SolidColorBrush>()) {
+      // Render the solid color fill, if a brush exists.
+      SolidColorBrush* solid_color_brush =
+          base::polymorphic_downcast<SolidColorBrush*>(
+              rect_node->data().background_brush.get());
+      ColorRGBA color = solid_color_brush->color();
 
-    SbBlitterSetColor(context_, RenderTreeToBlitterColor(color));
+      if (render_state_.opacity < 1.0f) {
+        color.set_a(color.a() * render_state_.opacity);
+      }
 
-    SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect));
+      SbBlitterSetBlending(context_, color.a() < 1.0f);
+      SbBlitterSetColor(context_, RenderTreeToBlitterColor(color));
+
+      SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect));
+    } else if (background_brush_typeid ==
+               base::GetTypeId<LinearGradientBrush>()) {
+      DCHECK(rect_node != NULL);
+      bool rendered_gradient =
+          RenderLinearGradient(device_, context_, render_state_, *rect_node);
+      if (!rendered_gradient) {
+        RenderWithSoftwareRenderer(rect_node);
+        return;
+      }
+    } else {
+      // If we have a more complicated brush fallback to software.
+      RenderWithSoftwareRenderer(rect_node);
+      return;
+    }
   }
 
   // Render the border, if it exists.
@@ -370,103 +442,84 @@
       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);
     }
   }
 }
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::RectShadowNode* rect_shadow_node) {
+  TRACE_EVENT0_IF_ENABLED("Visit(RectShadowNode)");
+
   RenderWithSoftwareRenderer(rect_shadow_node);
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) {
+  TRACE_EVENT0_IF_ENABLED("Visit(TextNode)");
+
   RenderWithSoftwareRenderer(text_node);
 }
 
 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.
+  TRACE_EVENT0("cobalt::renderer", "RenderWithSoftwareRenderer()");
+  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)
+  {
+    TRACE_EVENT0("cobalt::renderer", "SbBlitterBlitRectToRect()");
+    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>
 RenderTreeNodeVisitor::RenderToOffscreenSurface(render_tree::Node* node) {
+  TRACE_EVENT0_IF_ENABLED("RenderToOffscreenSurface()");
+
   common::OffscreenRenderCoordinateMapping coord_mapping =
       common::GetOffscreenRenderCoordinateMapping(
           node->GetBounds(), render_state_.transform.ToMatrix(),
@@ -496,8 +549,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..938a5cf 100644
--- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
@@ -32,13 +32,18 @@
 #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"
+#include "third_party/skia/include/core/SkImageInfo.h"
+
+#ifndef SB_HAS_BLITTER
+#define SB_HAS_BLITTER
+#endif
 
 #if SB_HAS(BLITTER)
 
@@ -62,10 +67,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 +104,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 +119,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 9c6a1e2..b23aa15 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(
@@ -67,6 +68,31 @@
   return make_scoped_refptr(new SinglePlaneImage(blitter_source_data.Pass()));
 }
 
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+scoped_refptr<render_tree::Image>
+ResourceProvider::CreateImageFromSbDecodeTarget(SbDecodeTarget decode_target) {
+  SbDecodeTargetFormat format = SbDecodeTargetGetFormat(decode_target);
+  if (format == kSbDecodeTargetFormat1PlaneRGBA) {
+    SbBlitterSurface surface =
+        SbDecodeTargetGetPlane(decode_target, kSbDecodeTargetPlaneRGBA);
+    DCHECK(SbBlitterIsSurfaceValid(surface));
+    bool is_opaque = SbDecodeTargetIsOpaque(decode_target);
+
+    // Now that we have the surface it contained, we are free to delete
+    // |decode_target|.
+    SbDecodeTargetDestroy(decode_target);
+    return make_scoped_refptr(new SinglePlaneImage(surface, is_opaque));
+  }
+
+  NOTREACHED()
+      << "Only format kSbDecodeTargetFormat1PlaneRGBA is currently supported.";
+  SbDecodeTargetDestroy(decode_target);
+  return NULL;
+}
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+
 scoped_ptr<render_tree::RawImageMemory>
 ResourceProvider::AllocateRawImageMemory(size_t size_in_bytes,
                                          size_t alignment) {
@@ -92,6 +118,13 @@
                                                    font_style);
 }
 
+scoped_refptr<render_tree::Typeface>
+ResourceProvider::GetLocalTypefaceByFaceNameIfAvailable(
+    const std::string& font_face_name) {
+  return skia_resource_provider_->GetLocalTypefaceByFaceNameIfAvailable(
+      font_face_name);
+}
+
 scoped_refptr<Typeface> ResourceProvider::GetCharacterFallbackTypeface(
     int32 utf32_character, FontStyle font_style, const std::string& language) {
   return skia_resource_provider_->GetCharacterFallbackTypeface(
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
index 078a683..c0ae7ab 100644
--- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
@@ -25,6 +25,7 @@
 #include "cobalt/render_tree/image.h"
 #include "cobalt/render_tree/resource_provider.h"
 #include "starboard/blitter.h"
+#include "starboard/decode_target.h"
 
 #if SB_HAS(BLITTER)
 
@@ -42,6 +43,15 @@
 
   void Finish() OVERRIDE {}
 
+#if SB_VERSION(3)
+  scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
+      SbDecodeTarget decode_target) OVERRIDE;
+
+  SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
+
+  bool SupportsSbDecodeTarget() OVERRIDE { return true; }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+
   bool PixelFormatSupported(render_tree::PixelFormat pixel_format) OVERRIDE;
   bool AlphaFormatSupported(render_tree::AlphaFormat alpha_format) OVERRIDE;
 
@@ -64,6 +74,9 @@
   scoped_refptr<render_tree::Typeface> GetLocalTypeface(
       const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE;
 
+  scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) OVERRIDE;
+
   scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface(
       int32 utf32_character, render_tree::FontStyle font_style,
       const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc
index cad2346..f96d931 100644
--- a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc
@@ -39,7 +39,8 @@
 
 void SoftwareRasterizer::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const scoped_refptr<backend::RenderTarget>& render_target, int options) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   int width = render_target->GetSize().width();
   int height = render_target->GetSize().height();
 
@@ -94,7 +95,9 @@
 
   SbBlitterDestroySurface(surface);
 
+  frame_rate_throttler_.EndInterval();
   render_target_blitter->Flip();
+  frame_rate_throttler_.BeginInterval();
 }
 
 render_tree::ResourceProvider* SoftwareRasterizer::GetResourceProvider() {
diff --git a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h
index 4743332..04aa2ac 100644
--- a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h
@@ -26,6 +26,7 @@
 #include "cobalt/renderer/backend/blitter/graphics_context.h"
 #include "cobalt/renderer/backend/graphics_context.h"
 #include "cobalt/renderer/backend/render_target.h"
+#include "cobalt/renderer/frame_rate_throttler.h"
 #include "cobalt/renderer/rasterizer/rasterizer.h"
 #include "cobalt/renderer/rasterizer/skia/software_rasterizer.h"
 
@@ -44,13 +45,14 @@
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options) OVERRIDE;
+              const Options& options) OVERRIDE;
 
   render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
 
  private:
   backend::GraphicsContextBlitter* context_;
   skia::SoftwareRasterizer skia_rasterizer_;
+  FrameRateThrottler frame_rate_throttler_;
 };
 
 }  // namespace blitter
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/egl/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc
index e778d0f..8798b04 100644
--- a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc
@@ -40,7 +40,8 @@
 
 void SoftwareRasterizer::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const scoped_refptr<backend::RenderTarget>& render_target, int options) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   int width = render_target->GetSize().width();
   int height = render_target->GetSize().height();
 
@@ -91,7 +92,9 @@
       context_, render_target_egl);
 
   context_->Blit(output_texture->gl_handle(), 0, 0, width, height);
+  frame_rate_throttler_.EndInterval();
   context_->SwapBuffers(render_target_egl);
+  frame_rate_throttler_.BeginInterval();
 }
 
 render_tree::ResourceProvider* SoftwareRasterizer::GetResourceProvider() {
diff --git a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h
index 662353c..479ce7e 100644
--- a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h
@@ -21,6 +21,7 @@
 #include "cobalt/render_tree/resource_provider.h"
 #include "cobalt/renderer/backend/graphics_context.h"
 #include "cobalt/renderer/backend/render_target.h"
+#include "cobalt/renderer/frame_rate_throttler.h"
 #include "cobalt/renderer/rasterizer/rasterizer.h"
 #include "cobalt/renderer/rasterizer/skia/software_rasterizer.h"
 
@@ -44,13 +45,14 @@
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options) OVERRIDE;
+              const Options& options) OVERRIDE;
 
   render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
 
  private:
   backend::GraphicsContextEGL* context_;
   skia::SoftwareRasterizer skia_rasterizer_;
+  FrameRateThrottler frame_rate_throttler_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index 6f821ed..68e4c7a 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,53 @@
   TestTree(new ImageNode(image));
 }
 
+TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatOpaque) {
+  scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat(
+      GetResourceProvider(), output_surface_size(),
+      render_tree::kAlphaFormatOpaque);
+
+  TestTree(new ImageNode(image));
+}
+
+TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners) {
+  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,
+       SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor) {
+  scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat(
+      GetResourceProvider(), output_surface_size(),
+      render_tree::kAlphaFormatOpaque);
+
+  CompositionNode::Builder builder;
+  builder.AddChild(new RectNode(
+      RectF(output_surface_size()),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(0.0, 1.0, 0.0, 1)))));
+  builder.AddChild(new FilterNode(
+      ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)),
+      new ImageNode(image)));
+  TestTree(new CompositionNode(builder.Pass()));
+}
+
+TEST_F(PixelTest, RectWithRoundedCornersOnSolidColor) {
+  CompositionNode::Builder builder;
+  builder.AddChild(new RectNode(
+      RectF(output_surface_size()),
+      scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(0.0, 1.0, 0.0, 1)))));
+  builder.AddChild(new FilterNode(
+      ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)),
+      new RectNode(RectF(output_surface_size()),
+                   scoped_ptr<Brush>(
+                       new SolidColorBrush(ColorRGBA(0.0, 0.0, 1.0, 1))))));
+  TestTree(new CompositionNode(builder.Pass()));
+}
+
 TEST_F(PixelTest, SingleRGBAImageLargerThanRenderTarget) {
   // Tests that rasterizing images that are larger than the render target
   // work as expected (e.g. they are cropped).
@@ -2749,8 +2802,8 @@
       RoundedCorners(50, 50)));
 }
 
-// If SetBoundsCB() returns false, the PunchThroughVideoNode should have no
-// effect.
+// PunchThroughVideoNode should trigger the painting of a solid rectangle with
+// RGBA(0, 0, 0, 0) regardless of whether SetBoundsCB returns true or false.
 TEST_F(PixelTest, PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse) {
   CompositionNode::Builder builder;
   builder.AddChild(new RectNode(RectF(25, 25, 150, 150),
@@ -2763,8 +2816,6 @@
   TestTree(new CompositionNode(builder.Pass()));
 }
 
-// If SetBoundsCB() returns true, the PunchThroughVideoNode should trigger the
-// painting of a solid rectangle with RGBA(0, 0, 0, 0).
 TEST_F(PixelTest, PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsTrue) {
   CompositionNode::Builder builder;
   builder.AddChild(new RectNode(RectF(25, 25, 150, 150),
diff --git a/src/cobalt/renderer/rasterizer/rasterizer.h b/src/cobalt/renderer/rasterizer/rasterizer.h
index b15d4bb..fea7fe2 100644
--- a/src/cobalt/renderer/rasterizer/rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/rasterizer.h
@@ -18,6 +18,8 @@
 #define COBALT_RENDERER_RASTERIZER_RASTERIZER_H_
 
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "cobalt/math/rect.h"
 #include "cobalt/render_tree/font.h"
 #include "cobalt/render_tree/image.h"
 #include "cobalt/render_tree/node.h"
@@ -45,7 +47,21 @@
  public:
   // When set, will clear the render target before rasterizing the render tree
   // to it.
-  static const int kSubmitOptions_Clear = (1 << 0);
+  static const int kSubmitFlags_Clear = (1 << 0);
+
+  struct Options {
+    Options() : flags(0) {}
+
+    // A bitwise combination of any of the |kSubmitFlags_*| constants defined
+    // above.
+    int flags;
+
+    // If specified, indicates which region of |render_target| is
+    // dirty and needs to be updated.  If animations are playing for example,
+    // then |dirty| can be setup to bound the animations.  A rasterizer is free
+    // to ignore this value if they wish.
+    base::optional<math::Rect> dirty;
+  };
 
   virtual ~Rasterizer() {}
 
@@ -54,11 +70,11 @@
   // above.
   virtual void Submit(const scoped_refptr<render_tree::Node>& render_tree,
                       const scoped_refptr<backend::RenderTarget>& render_target,
-                      int options) = 0;
+                      const Options& options) = 0;
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target) {
-    Submit(render_tree, render_target, 0);
+    Submit(render_tree, render_target, Options());
   }
 
   // Returns a thread-safe object from which one can produce renderer resources
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/conversions.h b/src/cobalt/renderer/rasterizer/skia/conversions.h
new file mode 100644
index 0000000..c5640e4
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/skia/conversions.h
@@ -0,0 +1,65 @@
+/*
+ * 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_SKIA_CONVERSIONS_H_
+#define COBALT_RENDERER_RASTERIZER_SKIA_CONVERSIONS_H_
+
+#include <vector>
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace skia {
+
+inline SkColor ToSkColor(const render_tree::ColorRGBA& color) {
+  return SkColorSetARGB(color.rgb8_a(), color.rgb8_r(), color.rgb8_g(),
+                        color.rgb8_b());
+}
+
+// Helper struct to convert render_tree::ColorStopList to a format that Skia
+// methods will easily accept.
+struct SkiaColorStops {
+  explicit SkiaColorStops(const render_tree::ColorStopList& color_stops)
+      : colors(color_stops.size()),
+        positions(color_stops.size()),
+        has_alpha(false) {
+    for (size_t i = 0; i < color_stops.size(); ++i) {
+      if (color_stops[i].color.a() < 1.0f) {
+        has_alpha = true;
+      }
+
+      colors[i] = ToSkColor(color_stops[i].color);
+      positions[i] = color_stops[i].position;
+    }
+  }
+
+  std::size_t size() const {
+    DCHECK(colors.size() == positions.size());
+    return colors.size();
+  }
+
+  std::vector<SkColor> colors;
+  std::vector<SkScalar> positions;
+  bool has_alpha;
+};
+
+}  // namespace skia
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_SKIA_CONVERSIONS_H_
diff --git a/src/cobalt/renderer/rasterizer/skia/font.cc b/src/cobalt/renderer/rasterizer/skia/font.cc
index 06bbb20..7f357b9 100644
--- a/src/cobalt/renderer/rasterizer/skia/font.cc
+++ b/src/cobalt/renderer/rasterizer/skia/font.cc
@@ -117,7 +117,8 @@
   paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 
   SkRect skia_bounds;
-  float width = paint.measureText(&glyph, 2, &skia_bounds);
+  float width =
+      paint.measureText(&glyph, sizeof(render_tree::GlyphIndex), &skia_bounds);
 
   // Both cache and return the glyph's bounds.
   if (glyph < kPrimaryPageSize) {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
index f8096b0..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()");
@@ -210,12 +213,14 @@
   return backend_image_->GetBitmap();
 }
 
-void HardwareFrontendImage::EnsureInitialized() {
+bool HardwareFrontendImage::EnsureInitialized() {
   DCHECK_EQ(rasterizer_message_loop_, MessageLoop::current());
   if (!initialize_backend_image_.is_null()) {
     initialize_backend_image_.Run();
     initialize_backend_image_.Reset();
+    return true;
   }
+  return false;
 }
 
 void HardwareFrontendImage::InitializeBackendImageFromImageData(
@@ -257,16 +262,18 @@
 
 HardwareMultiPlaneImage::~HardwareMultiPlaneImage() {}
 
-void HardwareMultiPlaneImage::EnsureInitialized() {
+bool HardwareMultiPlaneImage::EnsureInitialized() {
   // A multi-plane image is not considered backend-initialized until all its
   // single-plane images are backend-initialized, thus we ensure that all
   // the component images are backend-initialized.
+  bool initialized = false;
   for (int i = 0; i < render_tree::MultiPlaneImageDataDescriptor::kMaxPlanes;
        ++i) {
     if (planes_[i]) {
-      planes_[i]->EnsureInitialized();
+      initialized |= planes_[i]->EnsureInitialized();
     }
   }
+  return initialized;
 }
 
 }  // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.h b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
index 15c318e..bb44b1a 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
@@ -100,7 +100,9 @@
   // restraint should always be satisfied naturally.
   const SkBitmap& GetBitmap() const OVERRIDE;
 
-  void EnsureInitialized() OVERRIDE;
+  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_;
@@ -164,7 +170,7 @@
     return planes_[plane_index]->GetBitmap();
   }
 
-  void EnsureInitialized() OVERRIDE;
+  bool EnsureInitialized() OVERRIDE;
 
  private:
   ~HardwareMultiPlaneImage() OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 20ddfd5..2ea8e6e 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -20,6 +20,8 @@
 
 #include "base/debug/trace_event.h"
 #include "cobalt/renderer/backend/egl/graphics_context.h"
+#include "cobalt/renderer/backend/egl/graphics_system.h"
+#include "cobalt/renderer/frame_rate_throttler.h"
 #include "cobalt/renderer/rasterizer/common/surface_cache.h"
 #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h"
 #include "cobalt/renderer/rasterizer/skia/hardware_resource_provider.h"
@@ -47,7 +49,7 @@
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options);
+              const Options& options);
 
   render_tree::ResourceProvider* GetResourceProvider();
 
@@ -70,6 +72,8 @@
   scoped_ptr<RenderTreeNodeVisitor::ScratchSurface> CreateScratchSurface(
       const math::Size& size);
 
+  void ResetSkiaState();
+
   base::ThreadChecker thread_checker_;
 
   backend::GraphicsContextEGL* graphics_context_;
@@ -83,6 +87,8 @@
 
   base::optional<SurfaceCacheDelegate> surface_cache_delegate_;
   base::optional<common::SurfaceCache> surface_cache_;
+
+  FrameRateThrottler frame_rate_throttler_;
 };
 
 namespace {
@@ -189,7 +195,8 @@
 
 void HardwareRasterizer::Impl::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const scoped_refptr<backend::RenderTarget>& render_target, int options) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   backend::RenderTargetEGL* render_target_egl =
@@ -224,9 +231,17 @@
 
   // Get a SkCanvas that outputs to our hardware render target.
   SkCanvas* canvas = sk_output_surface_->getCanvas();
+  canvas->save();
 
-  if (options & Rasterizer::kSubmitOptions_Clear) {
+  if (options.flags & Rasterizer::kSubmitFlags_Clear) {
     canvas->clear(SkColorSetARGB(0, 0, 0, 0));
+  } else if (options.dirty) {
+    // Only a portion of the display is dirty. Reuse the previous frame
+    // if possible.
+    if (render_target_egl->IsContentPreservedOnSwap() &&
+        render_target_egl->swap_count() >= 3) {
+      canvas->clipRect(CobaltRectFToSkiaRect(*options.dirty));
+    }
   }
 
   {
@@ -238,6 +253,8 @@
                        base::Unretained(this));
     RenderTreeNodeVisitor visitor(
         canvas, &create_scratch_surface_function,
+        base::Bind(&HardwareRasterizer::Impl::ResetSkiaState,
+                   base::Unretained(this)),
         surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL,
         surface_cache_ ? &surface_cache_.value() : NULL);
     render_tree->Accept(&visitor);
@@ -248,7 +265,10 @@
     canvas->flush();
   }
 
+  frame_rate_throttler_.EndInterval();
   graphics_context_->SwapBuffers(render_target_egl);
+  frame_rate_throttler_.BeginInterval();
+  canvas->restore();
 }
 
 render_tree::ResourceProvider* HardwareRasterizer::Impl::GetResourceProvider() {
@@ -304,6 +324,8 @@
   }
 }
 
+void HardwareRasterizer::Impl::ResetSkiaState() { gr_context_->resetContext(); }
+
 HardwareRasterizer::HardwareRasterizer(
     backend::GraphicsContext* graphics_context, int skia_cache_size_in_bytes,
     int scratch_surface_cache_size_in_bytes, int surface_cache_size_in_bytes)
@@ -315,7 +337,8 @@
 
 void HardwareRasterizer::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const scoped_refptr<backend::RenderTarget>& render_target, int options) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    const Options& options) {
   TRACE_EVENT0("cobalt::renderer", "Rasterizer::Submit()");
   TRACE_EVENT0("cobalt::renderer", "HardwareRasterizer::Submit()");
   impl_->Submit(render_tree, render_target, options);
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
index 8807fc3..af2b78e 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
@@ -59,7 +59,7 @@
   // into the constructor.
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options) OVERRIDE;
+              const Options& options) OVERRIDE;
 
   render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
 
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index d79a244..195b6fb 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -25,6 +25,7 @@
 #include "cobalt/renderer/rasterizer/skia/gl_format_conversions.h"
 #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h"
 #include "cobalt/renderer/rasterizer/skia/hardware_image.h"
+#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h"
 #include "cobalt/renderer/rasterizer/skia/typeface.h"
 #include "third_party/ots/include/opentype-sanitiser.h"
 #include "third_party/ots/include/ots-memory-stream.h"
@@ -67,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(
@@ -95,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,
@@ -166,6 +169,24 @@
 }
 
 scoped_refptr<render_tree::Typeface>
+HardwareResourceProvider::GetLocalTypefaceByFaceNameIfAvailable(
+    const std::string& font_face_name) {
+  TRACE_EVENT0("cobalt::renderer",
+               "HardwareResourceProvider::GetLocalTypefaceIfAvailable()");
+
+  SkFontMgr_Cobalt* font_manager =
+      base::polymorphic_downcast<SkFontMgr_Cobalt*>(font_manager_.get());
+
+  SkTypeface* typeface = font_manager->matchFaceName(font_face_name);
+  if (typeface != NULL) {
+    SkAutoTUnref<SkTypeface> typeface_unref_helper(typeface);
+    return scoped_refptr<render_tree::Typeface>(new SkiaTypeface(typeface));
+  }
+
+  return NULL;
+}
+
+scoped_refptr<render_tree::Typeface>
 HardwareResourceProvider::GetCharacterFallbackTypeface(
     int32 character, render_tree::FontStyle font_style,
     const std::string& language) {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
index 54a439d..1d18280 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
@@ -51,6 +51,27 @@
   scoped_refptr<render_tree::Image> CreateImage(
       scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE;
 
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+
+  scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
+      SbDecodeTarget decode_target) OVERRIDE {
+    NOTREACHED()
+        << "CreateImageFromSbDecodeTarget is not supported on EGL yet.";
+    SbDecodeTargetDestroy(decode_target);
+    return NULL;
+  }
+
+  // Return the associated SbDecodeTargetProvider with the ResourceProvider,
+  // if it exists.  Returns NULL if SbDecodeTarget is not supported.
+  SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
+
+  // Whether SbDecodeTargetIsSupported or not.
+  bool SupportsSbDecodeTarget() OVERRIDE { return false; }
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory(
       size_t size_in_bytes, size_t alignment) OVERRIDE;
 
@@ -63,6 +84,9 @@
   scoped_refptr<render_tree::Typeface> GetLocalTypeface(
       const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE;
 
+  scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) OVERRIDE;
+
   scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface(
       int32 character, render_tree::FontStyle font_style,
       const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/skia/image.h b/src/cobalt/renderer/rasterizer/skia/image.h
index cd2e907..da4a5dc 100644
--- a/src/cobalt/renderer/rasterizer/skia/image.h
+++ b/src/cobalt/renderer/rasterizer/skia/image.h
@@ -41,7 +41,8 @@
   // to be referenced in a render tree before then, and thus this function will
   // fast-track the initialization of the backend object if it has not yet
   // executed.
-  virtual void EnsureInitialized() = 0;
+  // Returns true if an initialization actually took place.
+  virtual bool EnsureInitialized() = 0;
 
   // Mechanism to allow dynamic type checking on Image objects.
   virtual base::TypeId GetTypeId() const = 0;
@@ -78,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 ab9fc7d..fd5dfd9 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -72,13 +72,15 @@
 RenderTreeNodeVisitor::RenderTreeNodeVisitor(
     SkCanvas* render_target,
     const CreateScratchSurfaceFunction* create_scratch_surface_function,
+    const base::Closure& reset_skia_context_function,
     SurfaceCacheDelegate* surface_cache_delegate,
     common::SurfaceCache* surface_cache, Type visitor_type)
     : draw_state_(render_target),
       create_scratch_surface_function_(create_scratch_surface_function),
       surface_cache_delegate_(surface_cache_delegate),
       surface_cache_(surface_cache),
-      visitor_type_(visitor_type) {
+      visitor_type_(visitor_type),
+      reset_skia_context_function_(reset_skia_context_function) {
   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
@@ -187,7 +189,7 @@
 }
 
 void ApplyViewportMask(
-    SkCanvas* canvas,
+    RenderTreeNodeVisitorDrawState* draw_state,
     const base::optional<render_tree::ViewportFilter>& filter) {
   if (!filter) {
     return;
@@ -195,11 +197,12 @@
 
   if (!filter->has_rounded_corners()) {
     SkRect filter_viewport(CobaltRectFToSkiaRect(filter->viewport()));
-    canvas->clipRect(filter_viewport);
+    draw_state->render_target->clipRect(filter_viewport);
   } else {
-    canvas->clipPath(
+    draw_state->render_target->clipPath(
         RoundedRectToSkiaPath(filter->viewport(), filter->rounded_corners()),
         SkRegion::kIntersect_Op, true /* doAntiAlias */);
+    draw_state->clip_is_rect = false;
   }
 }
 
@@ -264,9 +267,9 @@
 
   // Render our source sub-tree into the offscreen surface.
   {
-    RenderTreeNodeVisitor sub_visitor(canvas, create_scratch_surface_function_,
-                                      surface_cache_delegate_, surface_cache_,
-                                      kType_SubVisitor);
+    RenderTreeNodeVisitor sub_visitor(
+        canvas, create_scratch_surface_function_, reset_skia_context_function_,
+        surface_cache_delegate_, surface_cache_, kType_SubVisitor);
     filter_node.source->Accept(&sub_visitor);
   }
 
@@ -285,7 +288,7 @@
   // the offscreen surface, so reset the scale for now.
   draw_state_.render_target->save();
   ApplyBlurFilterToPaint(&paint, filter_node.blur_filter);
-  ApplyViewportMask(draw_state_.render_target, filter_node.viewport_filter);
+  ApplyViewportMask(&draw_state_, filter_node.viewport_filter);
 
   draw_state_.render_target->setMatrix(CobaltMatrixToSkia(
       math::TranslateMatrix(coord_mapping.output_pre_translate) * total_matrix *
@@ -364,7 +367,22 @@
 
 void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) {
   if (filter_node->data().map_to_mesh_filter) {
-    // TODO: Implement support for MapToMeshFilter.
+    // TODO: Implement support for MapToMeshFilter instead of punching out
+    //       the area that it occupies.
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+    paint.setARGB(0, 0, 0, 0);
+
+    math::RectF bounds = filter_node->GetBounds();
+    SkRect sk_rect = SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(),
+                                      bounds.height());
+
+    draw_state_.render_target->drawRect(sk_rect, paint);
+
+#if ENABLE_FLUSH_AFTER_EVERY_NODE
+    draw_state_.render_target->flush();
+#endif
+
     return;
   }
 
@@ -435,13 +453,17 @@
     RenderTreeNodeVisitorDrawState original_draw_state(draw_state_);
 
     draw_state_.render_target->save();
-    ApplyViewportMask(draw_state_.render_target,
-                      filter_node->data().viewport_filter);
+    // Remember the value of |clip_is_rect| because ApplyViewportMask may
+    // modify it.
+    bool clip_was_rect = draw_state_.clip_is_rect;
+    ApplyViewportMask(&draw_state_, filter_node->data().viewport_filter);
 
     if (filter_node->data().opacity_filter) {
       draw_state_.opacity *= filter_node->data().opacity_filter->opacity();
     }
     filter_node->data().source->Accept(this);
+
+    draw_state_.clip_is_rect = clip_was_rect;
     draw_state_.render_target->restore();
     draw_state_ = original_draw_state;
   } else {
@@ -488,12 +510,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 && draw_state.clip_is_rect) {
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   }
 
   return paint;
@@ -503,7 +527,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
@@ -591,7 +616,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);
@@ -622,7 +648,14 @@
   // This should be a quick operation, and it needs to happen eventually, so
   // if it is not done now, it will be done in the next frame, or the one after
   // that.
-  image->EnsureInitialized();
+  if (image->EnsureInitialized()) {
+    // EnsureInitialized() may make a number of GL calls that results in GL
+    // state being modified behind Skia's back, therefore we have Skia reset its
+    // state in this case.
+    if (!reset_skia_context_function_.is_null()) {
+      reset_skia_context_function_.Run();
+    }
+  }
 
   // We issue different skia rasterization commands to render the image
   // depending on whether it's single or multi planed.
@@ -704,18 +737,11 @@
   SkRect sk_rect_transformed;
   total_matrix.mapRect(&sk_rect_transformed, sk_rect);
 
-  if (punch_through_video_node->data().set_bounds_cb.is_null()) {
-    return;
-  }
-  bool render_punch_through =
-      punch_through_video_node->data().set_bounds_cb.Run(
-          math::Rect(static_cast<int>(sk_rect_transformed.x()),
-                     static_cast<int>(sk_rect_transformed.y()),
-                     static_cast<int>(sk_rect_transformed.width()),
-                     static_cast<int>(sk_rect_transformed.height())));
-  if (!render_punch_through) {
-    return;
-  }
+  punch_through_video_node->data().set_bounds_cb.Run(
+      math::Rect(static_cast<int>(sk_rect_transformed.x()),
+                 static_cast<int>(sk_rect_transformed.y()),
+                 static_cast<int>(sk_rect_transformed.width()),
+                 static_cast<int>(sk_rect_transformed.height())));
 
   SkPaint paint;
   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h
index 3efa3c6..65dac0e 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h
@@ -73,6 +73,7 @@
   RenderTreeNodeVisitor(
       SkCanvas* render_target,
       const CreateScratchSurfaceFunction* create_scratch_surface_function,
+      const base::Closure& reset_skia_context_function,
       SurfaceCacheDelegate* surface_cache_delegate,
       common::SurfaceCache* surface_cache, Type visitor_type = kType_Normal);
 
@@ -104,6 +105,8 @@
   base::optional<SurfaceCacheDelegate::ScopedContext>
       surface_cache_scoped_context_;
 
+  base::Closure reset_skia_context_function_;
+
   DISALLOW_COPY_AND_ASSIGN(RenderTreeNodeVisitor);
 };
 
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h
index 01a5d62..d3cb0fe 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h
@@ -26,10 +26,14 @@
 
 struct RenderTreeNodeVisitorDrawState {
   explicit RenderTreeNodeVisitorDrawState(SkCanvas* render_target)
-      : render_target(render_target), opacity(1.0f) {}
+      : render_target(render_target), opacity(1.0f), clip_is_rect(true) {}
 
   SkCanvas* render_target;
   float opacity;
+
+  // True if the current clip is a rectangle or not.  If it is not, we need
+  // to enable blending when rendering clipped rectangles.
+  bool clip_is_rect;
 };
 
 }  // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi b/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi
index 49aee75..dba75b6 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi
+++ b/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi
@@ -151,6 +151,9 @@
     '<(DEPTH)/third_party/skia/src/fonts/SkGScalerContext.h',
     '<(DEPTH)/third_party/skia/src/fonts/SkTestScalerContext.cpp',
     '<(DEPTH)/third_party/skia/src/fonts/SkTestScalerContext.h',
+
+    # Conflicts with cobalt implementation.
+    '<(DEPTH)/third_party/skia/src/gpu/gl/GrGLCreateNativeInterface_none.cpp',
   ],
 
   # Exclude Skia OpenGL backend source files.
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
index 013d0dc..4111f1b 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
@@ -250,6 +250,7 @@
     const char* name = attributes[i];
     const char* value = attributes[i + 1];
     size_t name_len = strlen(name);
+
     if (name_len == 4 && strncmp(name, "name", name_len) == 0) {
       SkAutoAsciiToLC to_lowercase(value);
       family->names.push_back().set(to_lowercase.lc());
@@ -268,25 +269,79 @@
 }
 
 void FontElementHandler(FontFileInfo* file, const char** attributes) {
-  // A <font> should have weight (integer) and style (normal, italic)attributes.
+  DCHECK(file != NULL);
+
+  // A <font> should have following attributes:
+  // weight (integer), style (normal, italic), font_name (string), and
+  // postscript_name (string).
   // The element should contain a filename.
+
+  enum SeenAttributeFlags {
+    kSeenNone = 0,
+    kSeenFontFullName = 1,
+    kSeenFontPostscriptName = 1 << 1,
+    kSeenWeight = 1 << 2,
+    kSeenStyle = 1 << 3
+  };
+
+  uint32_t seen_attributes_flag = kSeenNone;
   for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL;
        i += 2) {
     const char* name = attributes[i];
     const char* value = attributes[i + 1];
-    size_t name_len = strlen(name);
-    if (name_len == 6 && strncmp("weight", name, name_len) == 0) {
-      if (!ParseNonNegativeInteger(value, &file->weight)) {
-        SkDebugf("---- Font weight %s (INVALID)", value);
-        file->weight = 0;
-      }
-    } else if (name_len == 5 && strncmp("style", name, name_len) == 0) {
-      size_t value_len = strlen(value);
-      if (value_len == 6 && strncmp("italic", value, value_len) == 0) {
-        file->style = FontFileInfo::kItalic_FontStyle;
-      }
+
+    switch (strlen(name)) {
+      case 9:
+        if (strncmp("font_name", name, 9) == 0) {
+          file->full_font_name = value;
+          seen_attributes_flag |= kSeenFontFullName;
+          continue;
+        }
+        break;
+      case 15:
+        if (strncmp("postscript_name", name, 15) == 0) {
+          file->postscript_name = value;
+          seen_attributes_flag |= kSeenFontPostscriptName;
+          continue;
+        }
+        break;
+      case 6:
+        if (strncmp("weight", name, 6) == 0) {
+          if (!ParseNonNegativeInteger(value, &file->weight)) {
+            DLOG(WARNING) << "Invalid font weight [" << value << "]";
+            file->weight = 0;
+          } else {
+            seen_attributes_flag |= kSeenWeight;
+          }
+          continue;
+        }
+        break;
+      case 5:
+        if (strncmp("style", name, 5) == 0) {
+          if (strncmp("italic", value, 6) == 0) {
+            file->style = FontFileInfo::kItalic_FontStyle;
+            seen_attributes_flag |= kSeenStyle;
+            continue;
+          } else if (strncmp("normal", value, 6) == 0) {
+            file->style = FontFileInfo::kNormal_FontStyle;
+            seen_attributes_flag |= kSeenStyle;
+            continue;
+          } else {
+            NOTREACHED() << "Unsupported style [" << value << "]";
+          }
+        }
+        break;
+      default:
+        break;
     }
+
+    NOTREACHED() << "Unsupported attribute [" << name << "]";
   }
+
+  DCHECK_EQ(seen_attributes_flag, kSeenFontFullName | kSeenFontPostscriptName |
+                                      kSeenWeight | kSeenStyle);
+  DCHECK(!file->full_font_name.isEmpty());
+  DCHECK(!file->postscript_name.isEmpty());
 }
 
 FontFamily* FindFamily(FamilyData* family_data, const char* family_name) {
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
index 21b6b4c..6c9aa9d 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
@@ -16,11 +16,16 @@
 
 #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h"
 
+#if defined(STARBOARD)
+#include "starboard/file.h"
+#else
 #include <sys/stat.h>
+#endif
 #include <cmath>
 
 #include "base/at_exit.h"
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
 #include "base/lazy_instance.h"
 #include "base/memory/singleton.h"
 #include "cobalt/base/c_val.h"
@@ -32,6 +37,7 @@
 #include "SkString.h"
 #include "SkTArray.h"
 #include "SkTSearch.h"
+#include "third_party/skia/src/ports/SkFontHost_FreeType_common.h"
 
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -43,6 +49,8 @@
 
 // NOTE: It is the responsibility of the caller to call Unref() on the SkData.
 SkData* NewDataFromFile(const SkString& file_path) {
+  TRACE_EVENT1("cobalt::renderer", "SkFontMgr_cobalt::NewDataFromFile()",
+               "file_path", TRACE_STR_COPY(file_path.c_str()));
   LOG(INFO) << "Loading font file: " << file_path.c_str();
 
   SkAutoTUnref<SkStream> file_stream(SkStream::NewFromFile(file_path.c_str()));
@@ -154,6 +162,7 @@
   }
 
   virtual SkStream* onOpenStream(int* ttc_index) const SK_OVERRIDE {
+    TRACE_EVENT0("cobalt::renderer", "SkTypeface_CobaltSystem::onOpenStream()");
     *ttc_index = index_;
 
     // Scope the initial mutex lock.
@@ -170,6 +179,7 @@
       }
     }
 
+    TRACE_EVENT0("cobalt::renderer", "Load data from file");
     // This is where the bulk of the time will be spent, so load the font data
     // outside of a mutex lock.
     SkAutoTUnref<SkData> data(NewDataFromFile(path_name_));
@@ -232,6 +242,8 @@
       language_(family.language),
       page_ranges_(family.page_ranges),
       is_character_map_generated_(false) {
+  TRACE_EVENT0("cobalt::renderer",
+               "SkFontStyleSet_Cobalt::SkFontStyleSet_Cobalt()");
   DCHECK(manager_owned_mutex_);
 
   if (family.names.count() == 0) {
@@ -246,8 +258,13 @@
     SkString path_name(SkOSPath::Join(base_path, font_file.file_name.c_str()));
 
     // Sanity check that something exists at this location.
+#if defined(STARBOARD)
+    bool is_font_file_found = SbFileExists(path_name.c_str());
+#else
     struct stat status;
-    if (0 != stat(path_name.c_str(), &status)) {
+    bool is_font_file_found = (stat(path_name.c_str(), &status) == 0);
+#endif  // defined(STARBOARD)
+    if (!is_font_file_found) {
       LOG(ERROR) << "Failed to find font file: " << path_name.c_str();
       continue;
     }
@@ -257,8 +274,14 @@
                           ? SkFontStyle::kItalic_Slant
                           : SkFontStyle::kUpright_Slant);
 
-    styles_.push_back().reset(SkNEW_ARGS(SkFontStyleSetEntry_Cobalt,
-                                         (path_name, font_file.index, style)));
+    std::string full_font_name(font_file.full_font_name.c_str(),
+                               font_file.full_font_name.size());
+    std::string postscript_name(font_file.postscript_name.c_str(),
+                                font_file.postscript_name.size());
+
+    styles_.push_back().reset(SkNEW_ARGS(
+        SkFontStyleSetEntry_Cobalt,
+        (path_name, font_file.index, style, full_font_name, postscript_name)));
   }
 }
 
@@ -290,34 +313,57 @@
 
 SkTypeface* SkFontStyleSet_Cobalt::MatchStyleWithoutLocking(
     const SkFontStyle& pattern) {
-  while (styles_.count() > 0) {
-    int style_index = GetClosestStyleIndex(pattern);
-    SkFontStyleSetEntry_Cobalt* style = styles_[style_index];
-    if (style->typeface == NULL) {
-      CreateSystemTypeface(style);
-
-      // Check to see if the typeface is still NULL. If this is the case, then
-      // the style can't be created. Remove it from the array.
-      if (style->typeface == NULL) {
-        styles_[style_index].swap(&styles_.back());
-        styles_.pop_back();
-        continue;
-      }
-    }
-
-    return SkRef(style->typeface.get());
+  SkTypeface* typeface = NULL;
+  while (typeface == NULL && styles_.count() > 0) {
+    typeface = TryRetrieveTypefaceAndRemoveStyleOnFailure(
+        GetClosestStyleIndex(pattern));
   }
+  return typeface;
+}
 
+SkTypeface* SkFontStyleSet_Cobalt::MatchFullFontName(const std::string& name) {
+  for (int i = 0; i < styles_.count(); ++i) {
+    if (styles_[i]->full_font_name == name) {
+      return TryRetrieveTypefaceAndRemoveStyleOnFailure(i);
+    }
+  }
   return NULL;
 }
 
+SkTypeface* SkFontStyleSet_Cobalt::MatchFontPostScriptName(
+    const std::string& name) {
+  for (int i = 0; i < styles_.count(); ++i) {
+    if (styles_[i]->font_postscript_name == name) {
+      return TryRetrieveTypefaceAndRemoveStyleOnFailure(i);
+    }
+  }
+  return NULL;
+}
+
+SkTypeface* SkFontStyleSet_Cobalt::TryRetrieveTypefaceAndRemoveStyleOnFailure(
+    int style_index) {
+  DCHECK(style_index >= 0 && style_index < styles_.count());
+  SkFontStyleSetEntry_Cobalt* style = styles_[style_index];
+  // If the typeface doesn't already exist, then attempt to create it.
+  if (style->typeface == NULL) {
+    CreateSystemTypeface(style);
+    // If the creation attempt failed and the typeface is still NULL, then
+    // remove the entry from the set's styles.
+    if (style->typeface == NULL) {
+      styles_[style_index].swap(&styles_.back());
+      styles_.pop_back();
+      return NULL;
+    }
+  }
+  return SkRef(style->typeface.get());
+}
+
 bool SkFontStyleSet_Cobalt::ContainsTypeface(const SkTypeface* typeface) {
   for (int i = 0; i < styles_.count(); ++i) {
     if (styles_[i]->typeface == typeface) {
       return true;
     }
   }
-
   return false;
 }
 
@@ -355,6 +401,9 @@
   // The character map is lazily generated. Generate it now if it isn't already
   // generated.
   if (!is_character_map_generated_) {
+    TRACE_EVENT0("cobalt::renderer",
+                 "SkFontStyleSet_Cobalt::ContainsCharacter() and "
+                 "!is_character_map_generated_");
     // Attempt to load the closest font style from the set. If it fails to load,
     // it will be removed from the set and, as long as font styles remain in the
     // set, the logic will be attempted again.
@@ -406,6 +455,7 @@
   if (is_character_map_generated_) {
     return;
   }
+  TRACE_EVENT0("cobalt::renderer", "GenerateCharacterMapFromData()");
 
   FT_Library freetype_lib;
   if (FT_Init_FreeType(&freetype_lib) != 0) {
@@ -460,6 +510,8 @@
 
 void SkFontStyleSet_Cobalt::CreateSystemTypeface(
     SkFontStyleSetEntry_Cobalt* style_entry) {
+  TRACE_EVENT0("cobalt::renderer",
+               "SkFontStyleSet_Cobalt::CreateSystemTypeface()");
   SkAutoTUnref<SkData> font_data(NewDataFromFile(style_entry->font_file_path));
   if (font_data != NULL) {
     CreateSystemTypefaceFromData(style_entry, font_data);
@@ -468,6 +520,8 @@
 
 void SkFontStyleSet_Cobalt::CreateSystemTypefaceFromData(
     SkFontStyleSetEntry_Cobalt* style_entry, SkData* data) {
+  TRACE_EVENT0("cobalt::renderer",
+               "SkFontStyleSet_Cobalt::CreateSystemTypefaceFromData()");
   DCHECK(!style_entry->typeface);
 
   // Since the font data is available, generate the character map if this is a
@@ -566,13 +620,17 @@
     const char* directory, const SkTArray<SkString, true>& default_fonts)
     : default_family_(NULL),
       last_font_cache_purge_time_(base::TimeTicks::Now()) {
+  TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::SkFontMgr_Cobalt()");
   // Ensure that both the CValManager and SkFontMgrCVals are initialized. The
   // CValManager is created first as it must outlast the SkFontMgrCVals.
   base::CValManager::GetInstance();
   SkFontMgrCVals::GetInstance();
 
   SkTDArray<FontFamily*> font_families;
-  SkFontConfigParser::GetFontFamilies(directory, &font_families);
+  {
+    TRACE_EVENT0("cobalt::renderer", "SkFontConfigParser::GetFontFamilies()");
+    SkFontConfigParser::GetFontFamilies(directory, &font_families);
+  }
   BuildNameToFamilyMap(directory, &font_families);
   font_families.deleteAll();
 
@@ -594,6 +652,41 @@
   }
 }
 
+SkTypeface* SkFontMgr_Cobalt::matchFaceName(const std::string& font_face_name) {
+  if (font_face_name.empty()) {
+    return NULL;
+  }
+
+  // Lock the style sets mutex prior to accessing them.
+  SkAutoMutexAcquire scoped_mutex(style_sets_mutex_);
+
+  // Prioritize looking up the postscript name first since some of our client
+  // applications prefer this method to specify face names.
+  for (int i = 0; i <= 1; ++i) {
+    NameToStyleSetMap& name_to_style_set_map =
+        i == 0 ? font_postscript_name_to_style_set_map_
+               : full_font_name_to_style_set_map_;
+
+    NameToStyleSetMap::iterator style_set_iterator =
+        name_to_style_set_map.find(font_face_name);
+    if (style_set_iterator != name_to_style_set_map.end()) {
+      SkFontStyleSet_Cobalt* style_set = style_set_iterator->second;
+      SkTypeface* typeface =
+          i == 0 ? style_set->MatchFontPostScriptName(font_face_name)
+                 : style_set->MatchFullFontName(font_face_name);
+      if (typeface != NULL) {
+        return typeface;
+      } else {
+        // If no typeface was successfully created then remove the entry from
+        // the map. It won't provide a successful result in subsequent calls
+        // either.
+        name_to_style_set_map.erase(style_set_iterator);
+      }
+    }
+  }
+  return NULL;
+}
+
 int SkFontMgr_Cobalt::onCountFamilies() const { return family_names_.count(); }
 
 void SkFontMgr_Cobalt::onGetFamilyName(int index, SkString* family_name) const {
@@ -610,7 +703,7 @@
     return NULL;
   }
 
-  NameToFamilyMap::const_iterator family_iterator =
+  NameToStyleSetMap::const_iterator family_iterator =
       name_to_family_map_.find(family_names_[index].c_str());
   if (family_iterator != name_to_family_map_.end()) {
     return SkRef(family_iterator->second);
@@ -627,7 +720,7 @@
 
   SkAutoAsciiToLC tolc(family_name);
 
-  NameToFamilyMap::const_iterator family_iterator =
+  NameToStyleSetMap::const_iterator family_iterator =
       name_to_family_map_.find(tolc.lc());
   if (family_iterator != name_to_family_map_.end()) {
     return SkRef(family_iterator->second);
@@ -718,6 +811,7 @@
 
 SkTypeface* SkFontMgr_Cobalt::onCreateFromStream(SkStream* stream,
                                                  int ttc_index) const {
+  TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::onCreateFromStream()");
   bool is_fixed_pitch;
   SkTypeface::Style style;
   SkString name;
@@ -731,6 +825,7 @@
 
 SkTypeface* SkFontMgr_Cobalt::onCreateFromFile(const char path[],
                                                int ttc_index) const {
+  TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::onCreateFromFile()");
   SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
   return stream.get() ? createFromStream(stream, ttc_index) : NULL;
 }
@@ -749,6 +844,7 @@
 
 void SkFontMgr_Cobalt::BuildNameToFamilyMap(const char* base_path,
                                             SkTDArray<FontFamily*>* families) {
+  TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::BuildNameToFamilyMap()");
   for (int i = 0; i < families->count(); i++) {
     FontFamily& family = *(*families)[i];
     bool named_font = family.names.count() > 0;
@@ -768,6 +864,39 @@
       continue;
     }
 
+    for (SkAutoTUnref<SkFontStyleSet_Cobalt::SkFontStyleSetEntry_Cobalt>*
+             font_style_set_entry = new_set->styles_.begin();
+         font_style_set_entry != new_set->styles_.end();
+         ++font_style_set_entry) {
+      // On the first pass through, process the full font name.
+      // On the second pass through, process the font postscript name.
+      for (int i = 0; i <= 1; ++i) {
+        const std::string font_face_name_type_description =
+            i == 0 ? "Full Font" : "Postscript";
+        const std::string& font_face_name =
+            i == 0 ? (*font_style_set_entry)->full_font_name
+                   : (*font_style_set_entry)->font_postscript_name;
+        NameToStyleSetMap& font_face_name_style_set_map =
+            i == 0 ? full_font_name_to_style_set_map_
+                   : font_postscript_name_to_style_set_map_;
+
+        DCHECK(!font_face_name.empty());
+        if (font_face_name_style_set_map.find(font_face_name) ==
+            font_face_name_style_set_map.end()) {
+          DLOG(INFO) << "Adding " << font_face_name_type_description
+                     << " name [" << font_face_name << "].";
+          font_face_name_style_set_map[font_face_name] = new_set.get();
+        } else {
+          // Purposely, not overwriting the entry gives priority to the
+          // earlier entry.  This is consistent with how fonts.xml gives
+          // priority to fonts that are specified earlier in the file.
+          NOTREACHED() << font_face_name_type_description << " name ["
+                       << font_face_name
+                       << "] already registered in BuildNameToFamilyMap.";
+        }
+      }
+    }
+
     font_style_sets_.push_back().reset(SkRef(new_set.get()));
 
     if (named_font) {
@@ -851,6 +980,29 @@
   return NULL;
 }
 
+void SkFontMgr_Cobalt::HandlePeriodicProcessing() {
+  base::TimeTicks current_time = base::TimeTicks::Now();
+
+  ProcessSystemTypefacesWithOpenStreams(current_time);
+
+  // If the required delay has elapsed since the last font cache purge, then
+  // it's time to force another. This is accomplished by setting the limit to
+  // 1 byte smaller than it's current size, which initiates a partial purge (it
+  // always purges at least 25% of its contents). After this is done, the cache
+  // is set back to its previous size.
+  if ((current_time - last_font_cache_purge_time_).InMilliseconds() >=
+      kPeriodicFontCachePurgeDelayMs) {
+    last_font_cache_purge_time_ = current_time;
+
+    size_t font_cache_used = SkGraphics::GetFontCacheUsed();
+    if (font_cache_used > 0) {
+      size_t font_cache_limit = SkGraphics::GetFontCacheLimit();
+      SkGraphics::SetFontCacheLimit(font_cache_used - 1);
+      SkGraphics::SetFontCacheLimit(font_cache_limit);
+    }
+  }
+}
+
 // NOTE: It is the responsibility of the caller to lock
 // |system_typeface_stream_mutex_|
 void SkFontMgr_Cobalt::AddSystemTypefaceWithActiveOpenStream(
@@ -887,26 +1039,6 @@
   }
 }
 
-void SkFontMgr_Cobalt::HandlePeriodicProcessing() {
-  base::TimeTicks current_time = base::TimeTicks::Now();
-
-  ProcessSystemTypefacesWithOpenStreams(current_time);
-
-  // If the required delay has elapsed since the last font cache purge, then
-  // it's time to force another. This is accomplished by setting the limit to
-  // 1 byte smaller than it's current size, which initiates a partial purge (it
-  // always purges at least 25% of its contents). After this is done, the cache
-  // is set back to its previous size.
-  if ((current_time - last_font_cache_purge_time_).InMilliseconds() >=
-      kPeriodicFontCachePurgeDelayMs) {
-    last_font_cache_purge_time_ = current_time;
-
-    size_t font_cache_limit = SkGraphics::GetFontCacheLimit();
-    SkGraphics::SetFontCacheLimit(SkGraphics::GetFontCacheUsed() - 1);
-    SkGraphics::SetFontCacheLimit(font_cache_limit);
-  }
-}
-
 void SkFontMgr_Cobalt::ProcessSystemTypefacesWithOpenStreams(
     const base::TimeTicks& current_time) const {
   SkAutoMutexAcquire scoped_mutex(system_typeface_stream_mutex_);
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h
index e09b727..cc74881 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h
@@ -26,7 +26,6 @@
 #include "cobalt/base/poller.h"
 #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h"
 #include "SkFontHost.h"
-#include "SkFontHost_FreeType_common.h"
 #include "SkFontDescriptor.h"
 #include "SkFontMgr.h"
 #include "SkThread.h"
@@ -47,16 +46,31 @@
 class SkFontStyleSet_Cobalt : public SkFontStyleSet {
  public:
   struct SkFontStyleSetEntry_Cobalt : public SkRefCnt {
+    // NOTE: |SkFontStyleSetEntry_Cobalt| objects are not guaranteed to last for
+    // the lifetime of |SkFontMgr_Cobalt| and can be removed by their owning
+    // |SkFontStyleSet_Cobalt| if their typeface fails to load properly. As a
+    // result, it is not safe to store their pointers outside of
+    // |SkFontStyleSet_Cobalt|.
     SkFontStyleSetEntry_Cobalt(const SkString& file_path, const int index,
-                               const SkFontStyle& style)
+                               const SkFontStyle& style,
+                               const std::string& full_name,
+                               const std::string& postscript_name)
         : font_file_path(file_path),
           ttc_index(index),
           font_style(style),
+          full_font_name(full_name.data(), full_name.size()),
+          font_postscript_name(postscript_name.data(), postscript_name.size()),
           typeface(NULL) {}
 
     const SkString font_file_path;
     const int ttc_index;
     const SkFontStyle font_style;
+
+    // these two members are declared as std::string since we have a hasher
+    // for std::string, but not SkString at this point.
+    const std::string full_font_name;
+    const std::string font_postscript_name;
+
     SkAutoTUnref<SkTypeface> typeface;
   };
 
@@ -83,6 +97,9 @@
   // calling any of the non-const private functions.
 
   SkTypeface* MatchStyleWithoutLocking(const SkFontStyle& pattern);
+  SkTypeface* MatchFullFontName(const std::string& name);
+  SkTypeface* MatchFontPostScriptName(const std::string& name);
+  SkTypeface* TryRetrieveTypefaceAndRemoveStyleOnFailure(int style_index);
   bool ContainsTypeface(const SkTypeface* typeface);
 
   bool ContainsCharacter(const SkFontStyle& style, SkUnichar character);
@@ -129,6 +146,9 @@
   SkFontMgr_Cobalt(const char* directory,
                    const SkTArray<SkString, true>& default_fonts);
 
+  // NOTE: This returns NULL if a match is not found.
+  SkTypeface* matchFaceName(const std::string& font_face_name);
+
  protected:
   // From SkFontMgr
   virtual int onCountFamilies() const SK_OVERRIDE;
@@ -136,14 +156,20 @@
   virtual void onGetFamilyName(int index,
                                SkString* family_name) const SK_OVERRIDE;
 
+  // NOTE: This returns NULL if there is no accessible style set at the index.
   virtual SkFontStyleSet_Cobalt* onCreateStyleSet(int index) const SK_OVERRIDE;
 
+  // NOTE: This returns NULL if there is no family match.
   virtual SkFontStyleSet_Cobalt* onMatchFamily(const char family_name[]) const
       SK_OVERRIDE;
 
+  // NOTE: This always returns a non-NULL value. If the family name cannot be
+  // found, then the best match among the default family is returned.
   virtual SkTypeface* onMatchFamilyStyle(
       const char family_name[], const SkFontStyle& style) const SK_OVERRIDE;
 
+// NOTE: This always returns a non-NULL value. If no match can be found, then
+// the best match among the default family is returned.
 #ifdef SK_FM_NEW_MATCH_FAMILY_STYLE_CHARACTER
   virtual SkTypeface* onMatchFamilyStyleCharacter(
       const char family_name[], const SkFontStyle& style, const char* bcp47[],
@@ -154,19 +180,25 @@
       SkUnichar character) const SK_OVERRIDE;
 #endif
 
+  // NOTE: This returns NULL if a match is not found.
   virtual SkTypeface* onMatchFaceStyle(const SkTypeface* family_member,
                                        const SkFontStyle& font_style) const
       SK_OVERRIDE;
 
+  // NOTE: This returns NULL if the typeface cannot be created.
   virtual SkTypeface* onCreateFromData(SkData* data,
                                        int ttc_index) const SK_OVERRIDE;
 
+  // NOTE: This returns NULL if the typeface cannot be created.
   virtual SkTypeface* onCreateFromStream(SkStream* stream,
                                          int ttc_index) const SK_OVERRIDE;
 
+  // NOTE: This returns NULL if the typeface cannot be created.
   virtual SkTypeface* onCreateFromFile(const char path[],
                                        int ttc_index) const SK_OVERRIDE;
 
+  // NOTE: This always returns a non-NULL value. If no match can be found, then
+  // the best match among the default family is returned.
   virtual SkTypeface* onLegacyCreateTypeface(
       const char family_name[], unsigned style_bits) const SK_OVERRIDE;
 
@@ -180,9 +212,7 @@
     base::TimeTicks time;
   };
 
-  //  Map names to the back end so that all names for a given family refer to
-  //  the same (non-replicated) set of typefaces.
-  typedef base::hash_map<std::string, SkFontStyleSet_Cobalt*> NameToFamilyMap;
+  typedef base::hash_map<std::string, SkFontStyleSet_Cobalt*> NameToStyleSetMap;
 
   void BuildNameToFamilyMap(const char* base_path,
                             SkTDArray<FontFamily*>* families);
@@ -222,7 +252,11 @@
   SkTArray<SkAutoTUnref<SkFontStyleSet_Cobalt>, true> font_style_sets_;
 
   SkTArray<SkString> family_names_;
-  NameToFamilyMap name_to_family_map_;
+  //  Map names to the back end so that all names for a given family refer to
+  //  the same (non-replicated) set of typefaces.
+  NameToStyleSetMap name_to_family_map_;
+  NameToStyleSetMap full_font_name_to_style_set_map_;
+  NameToStyleSetMap font_postscript_name_to_style_set_map_;
 
   SkTArray<SkFontStyleSet_Cobalt*> fallback_families_;
 
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
index 5e12828..80287a4 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
@@ -25,7 +25,6 @@
 #include "SkString.h"
 #include "SkTDArray.h"
 
-
 // The font_character_map namespace contains all of the constants, typedefs, and
 // utility functions used with determining whether or not a font supports a
 // character.
@@ -110,6 +109,9 @@
   int index;
   int weight;
   FontStyle style;
+
+  SkString full_font_name;
+  SkString postscript_name;
 };
 
 // A font family provides one or more names for a collection of fonts, each of
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc
index 8d843b7..531621f 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc
@@ -54,7 +54,7 @@
 
 void sk_free(void* p) {
   if (p) {
-    SbMemoryFree(p);
+    SbMemoryDeallocate(p);
   }
 }
 
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 37bd1ad..d01f212 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/software_image.h
@@ -55,7 +55,9 @@
 
   const SkBitmap& GetBitmap() const OVERRIDE { return bitmap_; }
 
-  void EnsureInitialized() OVERRIDE {}
+  bool EnsureInitialized() OVERRIDE { return false; }
+
+  bool IsOpaque() const OVERRIDE { return is_opaque_; }
 
  private:
   void Initialize(uint8_t* source_data,
@@ -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 {
@@ -95,7 +98,11 @@
     return planes_[plane_index]->GetBitmap();
   }
 
-  void EnsureInitialized() OVERRIDE {}
+  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_;
diff --git a/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc
index d1cafc6..884db9e 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc
@@ -120,7 +120,7 @@
     RenderTreeNodeVisitor::CreateScratchSurfaceFunction
         create_scratch_surface_function = base::Bind(&CreateScratchSurface);
     RenderTreeNodeVisitor visitor(
-        render_target, &create_scratch_surface_function,
+        render_target, &create_scratch_surface_function, base::Closure(),
         surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL,
         surface_cache_ ? &surface_cache_.value() : NULL);
 
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
index 04ca7c1..5a9afb7 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
@@ -21,6 +21,7 @@
 #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h"
 #include "cobalt/renderer/rasterizer/skia/font.h"
 #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h"
+#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h"
 #include "cobalt/renderer/rasterizer/skia/software_image.h"
 #include "cobalt/renderer/rasterizer/skia/typeface.h"
 #include "third_party/ots/include/opentype-sanitiser.h"
@@ -46,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(
@@ -108,7 +110,8 @@
 
 scoped_refptr<render_tree::Typeface> SoftwareResourceProvider::GetLocalTypeface(
     const char* font_family_name, render_tree::FontStyle font_style) {
-  TRACE_EVENT0("cobalt::renderer", "SoftwareResourceProvider::GetLocalFont()");
+  TRACE_EVENT0("cobalt::renderer",
+               "SoftwareResourceProvider::GetLocalTypeface()");
 
   SkAutoTUnref<SkTypeface> typeface(font_manager_->matchFamilyStyle(
       font_family_name, CobaltFontStyleToSkFontStyle(font_style)));
@@ -116,6 +119,24 @@
 }
 
 scoped_refptr<render_tree::Typeface>
+SoftwareResourceProvider::GetLocalTypefaceByFaceNameIfAvailable(
+    const std::string& font_face_name) {
+  TRACE_EVENT0("cobalt::renderer",
+               "SoftwareResourceProvider::GetLocalTypefaceIfAvailable()");
+
+  SkFontMgr_Cobalt* font_manager =
+      base::polymorphic_downcast<SkFontMgr_Cobalt*>(font_manager_.get());
+
+  SkTypeface* typeface = font_manager->matchFaceName(font_face_name);
+  if (typeface != NULL) {
+    SkAutoTUnref<SkTypeface> typeface_unref_helper(typeface);
+    return scoped_refptr<render_tree::Typeface>(new SkiaTypeface(typeface));
+  }
+
+  return NULL;
+}
+
+scoped_refptr<render_tree::Typeface>
 SoftwareResourceProvider::GetCharacterFallbackTypeface(
     int32 character, render_tree::FontStyle font_style,
     const std::string& language) {
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
index 7be9105..9528e22 100644
--- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
+++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
@@ -46,6 +46,21 @@
   scoped_refptr<render_tree::Image> CreateImage(
       scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE;
 
+#if defined(STARBOARD)
+#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+  scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget(
+      SbDecodeTarget decode_target) OVERRIDE {
+    NOTREACHED();
+    SbDecodeTargetDestroy(decode_target);
+    return NULL;
+  }
+
+  SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; }
+
+  bool SupportsSbDecodeTarget() OVERRIDE { return false; }
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // defined(STARBOARD)
+
   scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory(
       size_t size_in_bytes, size_t alignment) OVERRIDE;
 
@@ -58,6 +73,9 @@
   scoped_refptr<render_tree::Typeface> GetLocalTypeface(
       const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE;
 
+  scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable(
+      const std::string& font_face_name) OVERRIDE;
+
   scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface(
       int32 character, render_tree::FontStyle font_style,
       const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/stub/rasterizer.h b/src/cobalt/renderer/rasterizer/stub/rasterizer.h
index 2c4f5bf..ff69528 100644
--- a/src/cobalt/renderer/rasterizer/stub/rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/stub/rasterizer.h
@@ -31,7 +31,7 @@
 
   void Submit(const scoped_refptr<render_tree::Node>& render_tree,
               const scoped_refptr<backend::RenderTarget>& render_target,
-              int options) OVERRIDE {}
+              const Options& options) OVERRIDE {}
 
   render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
 
diff --git a/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png b/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png
index d26f087..0f82d7a 100644
--- a/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png b/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png
new file mode 100644
index 0000000..e72e8d1
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png
new file mode 100644
index 0000000..b8638eb
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png
new file mode 100644
index 0000000..2076087
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png
new file mode 100644
index 0000000..6251d5f
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/render_tree_pixel_tester.cc b/src/cobalt/renderer/render_tree_pixel_tester.cc
index 6e6fa75..717ab06 100644
--- a/src/cobalt/renderer/render_tree_pixel_tester.cc
+++ b/src/cobalt/renderer/render_tree_pixel_tester.cc
@@ -311,8 +311,9 @@
 scoped_array<uint8_t> RenderTreePixelTester::RasterizeRenderTree(
     const scoped_refptr<cobalt::render_tree::Node>& tree) const {
   // Rasterize the test render tree to the rasterizer's offscreen render target.
-  rasterizer_->Submit(tree, test_surface_,
-                      rasterizer::Rasterizer::kSubmitOptions_Clear);
+  rasterizer::Rasterizer::Options rasterizer_options;
+  rasterizer_options.flags = rasterizer::Rasterizer::kSubmitFlags_Clear;
+  rasterizer_->Submit(tree, test_surface_, rasterizer_options);
 
   // Load the texture's pixel data into a CPU memory buffer and return it.
   return graphics_context_->DownloadPixelDataAsRGBA(test_surface_);
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp
index 5a1253e..d7619d5 100644
--- a/src/cobalt/renderer/renderer.gyp
+++ b/src/cobalt/renderer/renderer.gyp
@@ -22,6 +22,8 @@
       'target_name': 'renderer',
       'type': 'static_library',
       'sources': [
+        'frame_rate_throttler.cc',
+        'frame_rate_throttler.h',
         'pipeline.cc',
         'pipeline.h',
         'renderer_module.cc',
@@ -33,6 +35,9 @@
         'submission_queue.h',
       ],
 
+      'defines': [
+        'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)',
+      ],
       'includes': [
         'copy_font_data.gypi',
       ],
diff --git a/src/cobalt/renderer/renderer_module.cc b/src/cobalt/renderer/renderer_module.cc
index 49cded6..23ee70e 100644
--- a/src/cobalt/renderer/renderer_module.cc
+++ b/src/cobalt/renderer/renderer_module.cc
@@ -79,7 +79,8 @@
     pipeline_ = make_scoped_ptr(new renderer::Pipeline(
         base::Bind(options_.create_rasterizer_function, graphics_context_.get(),
                    options_),
-        display_->GetRenderTarget(), graphics_context_.get()));
+        display_->GetRenderTarget(), graphics_context_.get(),
+        options_.submit_even_if_render_tree_is_unchanged));
   }
 }
 
diff --git a/src/cobalt/renderer/renderer_module.h b/src/cobalt/renderer/renderer_module.h
index a6fe61d..96226ca 100644
--- a/src/cobalt/renderer/renderer_module.h
+++ b/src/cobalt/renderer/renderer_module.h
@@ -55,11 +55,23 @@
     // 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.
     int surface_cache_size_in_bytes;
 
+    // If this flag is set to true, the pipeline will not re-submit a render
+    // tree if it has not changed from the previous submission.  This can save
+    // CPU time so long as there's no problem with the fact that the display
+    // buffer will not be frequently swapped.
+    bool submit_even_if_render_tree_is_unchanged;
+
    private:
     // Implemented per-platform, and allows each platform to customize
     // the renderer options.
diff --git a/src/cobalt/renderer/renderer_module_default_options_starboard.cc b/src/cobalt/renderer/renderer_module_default_options_starboard.cc
index db5086b..c19cc0b 100644
--- a/src/cobalt/renderer/renderer_module_default_options_starboard.cc
+++ b/src/cobalt/renderer/renderer_module_default_options_starboard.cc
@@ -16,6 +16,7 @@
 
 #include "cobalt/renderer/renderer_module.h"
 
+#include "base/debug/trace_event.h"
 #include "cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h"
 #include "cobalt/renderer/rasterizer/blitter/software_rasterizer.h"
 #include "cobalt/renderer/rasterizer/egl/software_rasterizer.h"
@@ -30,6 +31,7 @@
 scoped_ptr<rasterizer::Rasterizer> CreateRasterizer(
     backend::GraphicsContext* graphics_context,
     const RendererModule::Options& options) {
+  TRACE_EVENT0("cobalt::renderer", "CreateRasterizer");
 #if COBALT_FORCE_STUB_RASTERIZER
   return scoped_ptr<rasterizer::Rasterizer>(new rasterizer::stub::Rasterizer());
 #else
@@ -54,7 +56,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 +73,14 @@
   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
+  // if it has not changed from the last frame.
+  submit_even_if_render_tree_is_unchanged =
+      SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER;
 
   create_rasterizer_function = base::Bind(&CreateRasterizer);
 }
diff --git a/src/cobalt/renderer/renderer_module_default_options_win.cc b/src/cobalt/renderer/renderer_module_default_options_win.cc
index 6b08fc9..b96c5f0 100644
--- a/src/cobalt/renderer/renderer_module_default_options_win.cc
+++ b/src/cobalt/renderer/renderer_module_default_options_win.cc
@@ -47,6 +47,8 @@
   scratch_surface_cache_size_in_bytes =
       COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES;
 
+  submit_even_if_render_tree_is_unchanged = true;
+
   create_rasterizer_function = base::Bind(&CreateRasterizer);
 }
 
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/conversion_helpers.h b/src/cobalt/script/mozjs/conversion_helpers.h
index f5eca1e..6166bfa 100644
--- a/src/cobalt/script/mozjs/conversion_helpers.h
+++ b/src/cobalt/script/mozjs/conversion_helpers.h
@@ -71,7 +71,6 @@
   size_t length = in_string.length();
   jschar* inflated_buffer =
       js::InflateUTF8String(context, in_string.c_str(), &length);
-  DCHECK(inflated_buffer);
 
   if (!inflated_buffer) {
     LOG(ERROR) << "Failed to inflate UTF8 string.";
diff --git a/src/cobalt/script/mozjs/mozjs.gyp b/src/cobalt/script/mozjs/mozjs.gyp
index 6e856f5..7e833e4 100644
--- a/src/cobalt/script/mozjs/mozjs.gyp
+++ b/src/cobalt/script/mozjs/mozjs.gyp
@@ -26,6 +26,7 @@
         'mozjs_global_environment.cc',
         'mozjs_property_enumerator.cc',
         'mozjs_source_code.cc',
+        'mozjs_trace_logging.cc',
         'opaque_root_tracker.cc',
         'proxy_handler.cc',
         'referenced_object_map.cc',
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h b/src/cobalt/script/mozjs/mozjs_callback_function.h
index a2714de..ef6932a 100644
--- a/src/cobalt/script/mozjs/mozjs_callback_function.h
+++ b/src/cobalt/script/mozjs/mozjs_callback_function.h
@@ -27,7 +27,9 @@
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/mozjs/conversion_helpers.h"
 #include "cobalt/script/mozjs/convert_callback_return_value.h"
+#include "cobalt/script/mozjs/util/exception_helpers.h"
 #include "cobalt/script/mozjs/weak_heap_object.h"
+#include "nb/memory_scope.h"
 #include "third_party/mozjs/js/src/jsapi.h"
 #include "third_party/mozjs/js/src/jscntxt.h"
 
@@ -57,13 +59,15 @@
 
   CallbackResult<R> Run()
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -78,7 +82,8 @@
       JSBool call_result = JS::Call(context_, this_value, function, 0, NULL,
           return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -110,13 +115,15 @@
   CallbackResult<R> Run(
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -136,7 +143,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -169,13 +177,15 @@
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1,
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -196,7 +206,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -230,13 +241,15 @@
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2,
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -258,7 +271,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -293,13 +307,15 @@
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3,
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -322,7 +338,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -359,13 +376,15 @@
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4,
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -389,7 +408,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -427,13 +447,15 @@
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5,
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -458,7 +480,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
@@ -497,13 +520,15 @@
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6,
       typename base::internal::CallbackParamTraits<A7>::ForwardType a7)
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -529,7 +554,8 @@
       JSBool call_result = JS::Call(context_, this_value, function,
           kNumArguments, args, return_value.address());
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
index 810dcc5..9bfb5f2 100644
--- a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
+++ b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
@@ -32,7 +32,9 @@
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/mozjs/conversion_helpers.h"
 #include "cobalt/script/mozjs/convert_callback_return_value.h"
+#include "cobalt/script/mozjs/util/exception_helpers.h"
 #include "cobalt/script/mozjs/weak_heap_object.h"
+#include "nb/memory_scope.h"
 #include "third_party/mozjs/js/src/jsapi.h"
 #include "third_party/mozjs/js/src/jscntxt.h"
 
@@ -76,13 +78,15 @@
 
       typename base::internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
       const OVERRIDE {
+    TRACK_MEMORY_SCOPE("Javascript");
+    TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
+    JSAutoRequest auto_request(context_);
     JS::RootedObject function(context_, weak_function_.Get());
     if (!function) {
       DLOG(WARNING) << "Function was garbage collected.";
       callback_result.exception = true;
     } else {
-      JSAutoRequest auto_request(context_);
       JSAutoCompartment auto_compartment(context_, function);
       JSExceptionState* previous_exception_state =
           JS_SaveExceptionState(context_);
@@ -109,7 +113,8 @@
 ]]
 
       if (!call_result) {
-        DLOG(WARNING) << "Exception in callback.";
+        DLOG(WARNING) << "Exception in callback: "
+                      << util::GetExceptionString(context_);
         callback_result.exception = true;
       } else {
         callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
diff --git a/src/cobalt/script/mozjs/mozjs_engine.cc b/src/cobalt/script/mozjs/mozjs_engine.cc
index be18f1e..7b62f3f 100644
--- a/src/cobalt/script/mozjs/mozjs_engine.cc
+++ b/src/cobalt/script/mozjs/mozjs_engine.cc
@@ -18,8 +18,10 @@
 
 #include <algorithm>
 
+#include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "cobalt/base/c_val.h"
+#include "cobalt/browser/stack_size_constants.h"
 #include "cobalt/script/mozjs/mozjs_global_environment.h"
 #include "third_party/mozjs/cobalt_config/include/jscustomallocator.h"
 #include "third_party/mozjs/js/src/jsapi.h"
@@ -86,15 +88,33 @@
                     "Total JavaScript engine registered.") {
 }
 
+// Pretend we always preserve wrappers since we never call
+// SetPreserveWrapperCallback anywhere else. This is necessary for
+// TryPreserveReflector called by WeakMap to not crash. Disabling
+// bindings to WeakMap does not appear to be an easy option because
+// of its use in selfhosted.js. See bugzilla discussion linked where
+// they decided to include a similar dummy in the mozjs shell.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=829798
+bool DummyPreserveWrapperCallback(JSContext *cx, JSObject *obj) {
+  return true;
+}
+
 }  // namespace
 
 MozjsEngine::MozjsEngine() : accumulated_extra_memory_cost_(0) {
+  TRACE_EVENT0("cobalt::script", "MozjsEngine::MozjsEngine()");
   // TODO: Investigate the benefit of helper threads and things like
   // parallel compilation.
   runtime_ =
       JS_NewRuntime(kGarbageCollectionThresholdBytes, JS_NO_HELPER_THREADS);
   CHECK(runtime_);
 
+  // Sets the size of the native stack that should not be exceeded.
+  // Setting three quarters of the web module stack size to ensure that native
+  // stack won't exceed the stack size.
+  JS_SetNativeStackQuota(runtime_,
+                         cobalt::browser::kWebModuleStackSize / 4 * 3);
+
   JS_SetRuntimePrivate(runtime_, this);
 
   JS_SetSecurityCallbacks(runtime_, &security_callbacks);
@@ -117,6 +137,8 @@
   // Callback to be called during garbage collection during the sweep phase.
   JS_SetFinalizeCallback(runtime_, &MozjsEngine::FinalizeCallback);
 
+  js::SetPreserveWrapperCallback(runtime_, DummyPreserveWrapperCallback);
+
   EngineStats::GetInstance()->EngineCreated();
 }
 
@@ -127,11 +149,13 @@
 }
 
 scoped_refptr<GlobalEnvironment> MozjsEngine::CreateGlobalEnvironment() {
+  TRACE_EVENT0("cobalt::script", "MozjsEngine::CreateGlobalEnvironment()");
   DCHECK(thread_checker_.CalledOnValidThread());
   return new MozjsGlobalEnvironment(runtime_);
 }
 
 void MozjsEngine::CollectGarbage() {
+  TRACE_EVENT0("cobalt::script", "MozjsEngine::CollectGarbage()");
   DCHECK(thread_checker_.CalledOnValidThread());
   JS_GC(runtime_);
 }
@@ -176,15 +200,18 @@
     MozjsGlobalEnvironment* global_environment =
         MozjsGlobalEnvironment::GetFromContext(engine->contexts_[i]);
     if (status == JSGC_BEGIN) {
+      TRACE_EVENT_BEGIN0("cobalt::script", "SpiderMonkey Garbage Collection");
       global_environment->BeginGarbageCollection();
     } else if (status == JSGC_END) {
       global_environment->EndGarbageCollection();
+      TRACE_EVENT_END0("cobalt::script", "SpiderMonkey Garbage Collection");
     }
   }
 }
 
 void MozjsEngine::FinalizeCallback(JSFreeOp* free_op, JSFinalizeStatus status,
                                    JSBool is_compartment) {
+  TRACE_EVENT0("cobalt::script", "MozjsEngine::FinalizeCallback()");
   MozjsEngine* engine =
       static_cast<MozjsEngine*>(JS_GetRuntimePrivate(free_op->runtime()));
   DCHECK(engine->thread_checker_.CalledOnValidThread());
@@ -200,6 +227,7 @@
 }  // namespace mozjs
 
 scoped_ptr<JavaScriptEngine> JavaScriptEngine::CreateEngine() {
+  TRACE_EVENT0("cobalt::script", "JavaScriptEngine::CreateEngine()");
   return make_scoped_ptr<JavaScriptEngine>(new mozjs::MozjsEngine());
 }
 
diff --git a/src/cobalt/script/mozjs/mozjs_exception_state.cc b/src/cobalt/script/mozjs/mozjs_exception_state.cc
index 9b2a5ec..39a611a 100644
--- a/src/cobalt/script/mozjs/mozjs_exception_state.cc
+++ b/src/cobalt/script/mozjs/mozjs_exception_state.cc
@@ -44,6 +44,8 @@
     case kURIError:
       return JSEXN_URIERR;
   }
+  NOTREACHED();
+  return JSEXN_ERR;
 }
 
 // JSErrorCallback.
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.cc b/src/cobalt/script/mozjs/mozjs_global_environment.cc
index d7456fe..b9c05a9 100644
--- a/src/cobalt/script/mozjs/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs/mozjs_global_environment.cc
@@ -28,6 +28,7 @@
 #include "cobalt/script/mozjs/proxy_handler.h"
 #include "cobalt/script/mozjs/referenced_object_map.h"
 #include "cobalt/script/mozjs/util/exception_helpers.h"
+#include "nb/memory_scope.h"
 #include "third_party/mozjs/js/src/jsfriendapi.h"
 #include "third_party/mozjs/js/src/jsfun.h"
 #include "third_party/mozjs/js/src/jsobj.h"
@@ -128,6 +129,7 @@
       environment_settings_(NULL),
       last_error_message_(NULL),
       eval_enabled_(false) {
+  TRACK_MEMORY_SCOPE("Javascript");
   context_ = JS_NewContext(runtime, kStackChunkSize);
   DCHECK(context_);
   // Set a pointer to this class inside the JSContext.
@@ -166,6 +168,7 @@
 }
 
 void MozjsGlobalEnvironment::CreateGlobalObject() {
+  TRACK_MEMORY_SCOPE("Javascript");
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(!global_object_proxy_);
 
@@ -194,45 +197,31 @@
 bool MozjsGlobalEnvironment::EvaluateScript(
     const scoped_refptr<SourceCode>& source_code,
     std::string* out_result_utf8) {
+  TRACK_MEMORY_SCOPE("Javascript");
   DCHECK(thread_checker_.CalledOnValidThread());
-  MozjsSourceCode* mozjs_source_code =
-      base::polymorphic_downcast<MozjsSourceCode*>(source_code.get());
-
-  const std::string& script = mozjs_source_code->source_utf8();
-  const base::SourceLocation location = mozjs_source_code->location();
 
   JSAutoRequest auto_request(context_);
   JSAutoCompartment auto_compartment(context_, global_object_proxy_);
   JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_);
   JS::RootedValue result_value(context_);
+
   std::string error_message;
   last_error_message_ = &error_message;
-  JS::RootedObject global_object(
-      context_, js::GetProxyTargetObject(global_object_proxy_));
 
-  size_t length = script.size();
-  jschar* inflated_buffer =
-      js::InflateUTF8String(context_, script.c_str(), &length);
-  DCHECK(inflated_buffer);
-  bool success = false;
-  if (inflated_buffer) {
-    success = JS_EvaluateUCScript(context_, global_object, inflated_buffer,
-                                  length, location.file_path.c_str(),
-                                  location.line_number, result_value.address());
-    js_free(inflated_buffer);
-  }
-
+  bool success = EvaluateScriptInternal(source_code, &result_value);
   if (out_result_utf8) {
     if (success) {
       MozjsExceptionState exception_state(context_);
       FromJSValue(context_, result_value, kNoConversionFlags, &exception_state,
                   out_result_utf8);
-    } else {
+    } else if (last_error_message_) {
       *out_result_utf8 = *last_error_message_;
+    } else {
+      DLOG(ERROR) << "Script execution failed.";
     }
   }
-  JS_RestoreExceptionState(context_, previous_exception_state);
   last_error_message_ = NULL;
+  JS_RestoreExceptionState(context_, previous_exception_state);
   return success;
 }
 
@@ -240,15 +229,14 @@
     const scoped_refptr<SourceCode>& source_code,
     const scoped_refptr<Wrappable>& owning_object,
     base::optional<OpaqueHandleHolder::Reference>* out_opaque_handle) {
+  TRACK_MEMORY_SCOPE("Javascript");
   DCHECK(thread_checker_.CalledOnValidThread());
   JSAutoRequest auto_request(context_);
   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_,
@@ -256,12 +244,13 @@
     out_opaque_handle->emplace(owning_object.get(), mozjs_object_holder);
   }
   JS_RestoreExceptionState(context_, previous_exception_state);
-  return true;
+  return success;
 }
 
 bool MozjsGlobalEnvironment::EvaluateScriptInternal(
     const scoped_refptr<SourceCode>& source_code,
     JS::MutableHandleValue out_result) {
+  TRACK_MEMORY_SCOPE("Javascript");
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(global_object_proxy_);
   MozjsSourceCode* mozjs_source_code =
@@ -276,13 +265,14 @@
   size_t length = script.size();
   jschar* inflated_buffer =
       js::InflateUTF8String(context_, script.c_str(), &length);
-  DCHECK(inflated_buffer);
   bool success = false;
   if (inflated_buffer) {
     success = JS_EvaluateUCScript(context_, global_object, inflated_buffer,
                                   length, location.file_path.c_str(),
                                   location.line_number, out_result.address());
     js_free(inflated_buffer);
+  } else {
+    DLOG(ERROR) << "Malformed UTF-8 script.";
   }
 
   return success;
@@ -307,6 +297,7 @@
 
 void MozjsGlobalEnvironment::AllowGarbageCollection(
     const scoped_refptr<Wrappable>& wrappable) {
+  TRACK_MEMORY_SCOPE("Javascript");
   DCHECK(thread_checker_.CalledOnValidThread());
   CachedWrapperMultiMap::iterator it =
       kept_alive_objects_.find(wrappable.get());
@@ -336,6 +327,7 @@
 
 void MozjsGlobalEnvironment::Bind(const std::string& identifier,
                                   const scoped_refptr<Wrappable>& impl) {
+  TRACK_MEMORY_SCOPE("Javascript");
   JSAutoRequest auto_request(context_);
   JSAutoCompartment auto_compartment(context_, global_object_proxy_);
 
@@ -358,12 +350,14 @@
 }
 
 void MozjsGlobalEnvironment::DoSweep() {
+  TRACK_MEMORY_SCOPE("Javascript");
   weak_object_manager_.SweepUnmarkedObjects();
   // Remove NULL references after sweeping weak references.
   referenced_objects_->RemoveNullReferences();
 }
 
 void MozjsGlobalEnvironment::BeginGarbageCollection() {
+  TRACK_MEMORY_SCOPE("Javascript");
   // It's possible that a GC could be triggered from within the
   // BeginGarbageCollection callback. Only create the OpaqueRootState the first
   // time we enter.
@@ -477,6 +471,7 @@
 }
 
 JSBool MozjsGlobalEnvironment::CheckEval(JSContext* context) {
+  TRACK_MEMORY_SCOPE("Javascript");
   MozjsGlobalEnvironment* global_object_proxy = GetFromContext(context);
   DCHECK(global_object_proxy);
   if (!global_object_proxy->report_eval_.is_null()) {
diff --git a/src/cobalt/script/mozjs/mozjs_trace_logging.cc b/src/cobalt/script/mozjs/mozjs_trace_logging.cc
new file mode 100644
index 0000000..2e11ce8
--- /dev/null
+++ b/src/cobalt/script/mozjs/mozjs_trace_logging.cc
@@ -0,0 +1,157 @@
+/*
+ * 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 "third_party/mozjs/js/src/TraceLogging.h"
+
+#include "base/debug/trace_event.h"
+#include "base/memory/singleton.h"
+
+namespace js {
+
+namespace {
+
+// Container class to transform the 3rd-party-declared TraceLogging class
+// into our Singleton type.
+class TraceLoggingContainer {
+ public:
+  TraceLoggingContainer() {}
+
+  static TraceLoggingContainer* GetInstance() {
+    return Singleton<TraceLoggingContainer,
+      StaticMemorySingletonTraits<TraceLoggingContainer> >::get();
+  }
+
+  TraceLogging* GetLogger() {
+    return &logger_;
+  }
+
+ private:
+  TraceLogging logger_;
+
+  DISALLOW_COPY_AND_ASSIGN(TraceLoggingContainer);
+};
+
+const char kTraceLoggingCategory[] = "JavaScript";
+
+}  // namespace
+
+const char* const TraceLogging::type_name[] = {
+  "IonCompile",
+  "IonCompile",
+  "IonCannon",
+  "IonCannon",
+  "IonCannon",
+  "IonSideCannon",
+  "IonSideCannon",
+  "IonSideCannon",
+  "YarrJIT",
+  "YarrJIT",
+  "JMSafepoint",
+  "JMSafepoint",
+  "JMNormal",
+  "JMNormal",
+  "JMCompile",
+  "JMCompile",
+  "GarbageCollect",
+  "GarbageCollect",
+  "Interpreter",
+  "Interpreter",
+  "Info",
+};
+
+// None of the member variables will be needed. Instead, logging will be
+// funneled to the trace event system.
+TraceLogging::TraceLogging() {
+}
+
+TraceLogging::~TraceLogging() {
+}
+
+// static
+TraceLogging* TraceLogging::defaultLogger() {
+  return TraceLoggingContainer::GetInstance()->GetLogger();
+}
+
+// static
+void TraceLogging::releaseDefaultLogger() {
+  // Do nothing. Lifetime is managed by the TraceLoggingContainer singleton.
+}
+
+void TraceLogging::log(Type type, const char* filename, unsigned int line) {
+  // Unfortunately, we don't have access to the enum, so can't declare a
+  // "count"-type enum. Instead, INFO is assumed to be the last enumeration.
+  COMPILE_ASSERT(ARRAYSIZE_UNSAFE(type_name)-1 == Type::INFO, array_mismatch);
+
+  switch (type) {
+    // "Start" types
+    case ION_COMPILE_START:
+    case ION_CANNON_START:
+    case ION_SIDE_CANNON_START:
+    case YARR_JIT_START:
+    case JM_SAFEPOINT_START:
+    case JM_START:
+    case JM_COMPILE_START:
+    case GC_START:
+    case INTERPRETER_START:
+      if (filename != NULL) {
+        TRACE_EVENT_BEGIN2(kTraceLoggingCategory, type_name[type],
+                          "file", TRACE_STR_COPY(filename),
+                          "line", line);
+      } else {
+        TRACE_EVENT_BEGIN0(kTraceLoggingCategory, type_name[type]);
+      }
+      break;
+
+    // Ignored types
+    case INFO:
+      break;
+
+    // "Stop" types
+    default:
+      TRACE_EVENT_END0(kTraceLoggingCategory, type_name[type]);
+      break;
+  }
+}
+
+void TraceLogging::log(Type type, JSScript* script) {
+  log(type, script->filename(), script->lineno);
+}
+
+void TraceLogging::log(const char* log) {
+  UNREFERENCED_PARAMETER(log);
+}
+
+void TraceLogging::log(Type type) {
+  log(type, NULL, 0);
+}
+
+void TraceLogging::flush() {
+}
+
+// Helper functions declared in TraceLogging.h
+void TraceLog(TraceLogging* logger, TraceLogging::Type type, JSScript* script) {
+  logger->log(type, script);
+}
+
+void TraceLog(TraceLogging* logger, const char* log) {
+  logger->log(log);
+}
+
+void TraceLog(TraceLogging* logger, TraceLogging::Type type) {
+  logger->log(type);
+}
+
+}  // namespace js
diff --git a/src/cobalt/script/mozjs/mozjs_user_object_holder.h b/src/cobalt/script/mozjs/mozjs_user_object_holder.h
index 2ee384f..c70ad1a 100644
--- a/src/cobalt/script/mozjs/mozjs_user_object_holder.h
+++ b/src/cobalt/script/mozjs/mozjs_user_object_holder.h
@@ -51,6 +51,7 @@
         wrapper_factory_(wrapper_factory) {}
 
   void RegisterOwner(Wrappable* owner) OVERRIDE {
+    JSAutoRequest auto_request(context_);
     JS::RootedObject owned_object(context_, js_object());
     DLOG_IF(WARNING, !owned_object)
         << "Owned object has been garbage collected.";
@@ -65,6 +66,7 @@
 
   void DeregisterOwner(Wrappable* owner) OVERRIDE {
     // |owner| may be in the process of being destructed, so don't use it.
+    JSAutoRequest auto_request(context_);
     JS::RootedObject owned_object(context_, js_object());
     if (owned_object) {
       MozjsGlobalEnvironment* global_environment =
@@ -82,6 +84,7 @@
 
   scoped_ptr<BaseClass> MakeCopy() const OVERRIDE {
     DCHECK(object_handle_);
+    JSAutoRequest auto_request(context_);
     JS::RootedObject rooted_object(context_, js_object());
     return make_scoped_ptr<BaseClass>(
         new MozjsUserObjectHolder(rooted_object, context_, wrapper_factory_));
diff --git a/src/cobalt/script/mozjs/opaque_root_tracker.cc b/src/cobalt/script/mozjs/opaque_root_tracker.cc
index 1ae8d32..dfc405e 100644
--- a/src/cobalt/script/mozjs/opaque_root_tracker.cc
+++ b/src/cobalt/script/mozjs/opaque_root_tracker.cc
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "cobalt/script/mozjs/weak_heap_object.h"
+#include "third_party/mozjs/js/src/jsapi.h"
 
 namespace cobalt {
 namespace script {
@@ -45,6 +46,7 @@
   }
 
   ~OpaqueRootStateImpl() {
+    JSAutoRequest auto_request(context_);
     for (ReferencedObjectPairVector::iterator it = referenced_objects_.begin();
          it != referenced_objects_.end(); ++it) {
       if (it->second.Get()) {
diff --git a/src/cobalt/script/mozjs/util/exception_helpers.cc b/src/cobalt/script/mozjs/util/exception_helpers.cc
index 030f45a..3a4b80c 100644
--- a/src/cobalt/script/mozjs/util/exception_helpers.cc
+++ b/src/cobalt/script/mozjs/util/exception_helpers.cc
@@ -20,6 +20,7 @@
 
 #include "cobalt/script/mozjs/conversion_helpers.h"
 #include "cobalt/script/mozjs/mozjs_exception_state.h"
+#include "third_party/mozjs/js/src/jsapi.h"
 #include "third_party/mozjs/js/src/jsdbgapi.h"
 #include "third_party/mozjs/js/src/jsscript.h"
 
@@ -27,7 +28,27 @@
 namespace script {
 namespace mozjs {
 namespace util {
+std::string GetExceptionString(JSContext* context) {
+  if (!JS_IsExceptionPending(context)) {
+    return std::string("No exception pending.");
+  }
+  JS::RootedValue exception(context);
+  JS_GetPendingException(context, exception.address());
+  JS_ReportPendingException(context);
+  return GetExceptionString(context, exception);
+}
+
+std::string GetExceptionString(JSContext* context,
+                               JS::HandleValue exception) {
+  std::string exception_string;
+  MozjsExceptionState exception_state(context);
+  FromJSValue(context, exception, kNoConversionFlags, &exception_state,
+              &exception_string);
+  return exception_string;
+}
+
 std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames) {
+  JSAutoRequest auto_request(context);
   JS::StackDescription* stack_description =
       JS::DescribeStack(context, max_frames);
   if (max_frames == 0) {
diff --git a/src/cobalt/script/mozjs/util/exception_helpers.h b/src/cobalt/script/mozjs/util/exception_helpers.h
index 75dff27..7e5e563 100644
--- a/src/cobalt/script/mozjs/util/exception_helpers.h
+++ b/src/cobalt/script/mozjs/util/exception_helpers.h
@@ -16,6 +16,7 @@
 #ifndef COBALT_SCRIPT_MOZJS_UTIL_EXCEPTION_HELPERS_H_
 #define COBALT_SCRIPT_MOZJS_UTIL_EXCEPTION_HELPERS_H_
 
+#include <string>
 #include <vector>
 
 #include "cobalt/script/stack_frame.h"
@@ -25,6 +26,10 @@
 namespace script {
 namespace mozjs {
 namespace util {
+std::string GetExceptionString(JSContext* context);
+
+std::string GetExceptionString(JSContext* context, JS::HandleValue exception);
+
 std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames);
 }  // namespace util
 }  // namespace mozjs
diff --git a/src/cobalt/script/mozjs/wrapper_factory.cc b/src/cobalt/script/mozjs/wrapper_factory.cc
index 72b2edb..d533458 100644
--- a/src/cobalt/script/mozjs/wrapper_factory.cc
+++ b/src/cobalt/script/mozjs/wrapper_factory.cc
@@ -79,8 +79,14 @@
       new MozjsWrapperHandle(wrapper_private));
 }
 
-bool WrapperFactory::DoesObjectImplementInterface(JSObject* object,
+bool WrapperFactory::DoesObjectImplementInterface(JS::HandleObject object,
                                                   base::TypeId type_id) const {
+  // If the object doesn't have a wrapper private which means it is not a
+  // platform object, so the object doesn't implement the interface.
+  if (!WrapperPrivate::HasWrapperPrivate(context_, object)) {
+    return false;
+  }
+
   WrappableTypeFunctionsHashMap::const_iterator it =
       wrappable_type_functions_.find(type_id);
   if (it == wrappable_type_functions_.end()) {
diff --git a/src/cobalt/script/mozjs/wrapper_factory.h b/src/cobalt/script/mozjs/wrapper_factory.h
index 828bdac..79be1b7 100644
--- a/src/cobalt/script/mozjs/wrapper_factory.h
+++ b/src/cobalt/script/mozjs/wrapper_factory.h
@@ -51,7 +51,8 @@
   // Returns true if this JSObject is a Wrapper object.
   bool IsWrapper(JS::HandleObject wrapper) const;
 
-  bool DoesObjectImplementInterface(JSObject*, base::TypeId) const;
+  bool DoesObjectImplementInterface(JS::HandleObject object,
+                                    base::TypeId id) const;
 
  private:
   struct WrappableTypeFunctions {
diff --git a/src/cobalt/script/mozjs/wrapper_private.cc b/src/cobalt/script/mozjs/wrapper_private.cc
index 7dc1410..1bf15ca 100644
--- a/src/cobalt/script/mozjs/wrapper_private.cc
+++ b/src/cobalt/script/mozjs/wrapper_private.cc
@@ -20,6 +20,7 @@
 #include "cobalt/script/mozjs/proxy_handler.h"
 #include "cobalt/script/mozjs/referenced_object_map.h"
 #include "third_party/mozjs/js/src/jsapi.h"
+#include "third_party/mozjs/js/src/jsobj.h"
 #include "third_party/mozjs/js/src/jsproxy.h"
 
 namespace cobalt {
@@ -65,6 +66,17 @@
 }
 
 // static
+bool WrapperPrivate::HasWrapperPrivate(JSContext* context,
+                                       JS::HandleObject object) {
+  if (js::IsProxy(object)) {
+    JS::RootedObject target_object(context, js::GetProxyTargetObject(object));
+    return WrapperPrivate::HasWrapperPrivate(context, target_object);
+  }
+
+  return object->hasPrivate();
+}
+
+// static
 WrapperPrivate* WrapperPrivate::GetFromWrappable(
     const scoped_refptr<Wrappable>& wrappable, JSContext* context,
     WrapperFactory* wrapper_factory) {
diff --git a/src/cobalt/script/mozjs/wrapper_private.h b/src/cobalt/script/mozjs/wrapper_private.h
index d7ebfb1..ae0286d 100644
--- a/src/cobalt/script/mozjs/wrapper_private.h
+++ b/src/cobalt/script/mozjs/wrapper_private.h
@@ -69,6 +69,9 @@
                    GetReachableWrappablesFunction());
   }
 
+  // Return true if the object has wrapper private.
+  static bool HasWrapperPrivate(JSContext* context, JS::HandleObject object);
+
   // Get the WrapperPrivate associated with the given Wrappable. A new JSObject
   // and WrapperPrivate object may be created.
   static WrapperPrivate* GetFromWrappable(
diff --git a/src/cobalt/script/script.gyp b/src/cobalt/script/script.gyp
index c827de2..46cc245 100644
--- a/src/cobalt/script/script.gyp
+++ b/src/cobalt/script/script.gyp
@@ -38,6 +38,7 @@
       ],
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/nb/nb.gyp:nb',
       ]
     },
     {
diff --git a/src/cobalt/script/script_runner.cc b/src/cobalt/script/script_runner.cc
index d680966..3900885 100644
--- a/src/cobalt/script/script_runner.cc
+++ b/src/cobalt/script/script_runner.cc
@@ -19,12 +19,51 @@
 #include "base/logging.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/source_code.h"
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#define HANDLE_CORE_DUMP
+#include "base/lazy_instance.h"
+#include "starboard/ps4/core_dump_handler.h"
+#endif  // SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
+#endif  // defined(OS_STARBOARD)
 
 namespace cobalt {
 namespace script {
 
 namespace {
 
+#if defined(HANDLE_CORE_DUMP)
+
+class ScriptRunnerLog {
+ public:
+  ScriptRunnerLog() : success_count_(0), fail_count_(0) {
+    SbCoreDumpRegisterHandler(CoreDumpHandler, this);
+  }
+  ~ScriptRunnerLog() { SbCoreDumpUnregisterHandler(CoreDumpHandler, this); }
+
+  static void CoreDumpHandler(void* context) {
+    SbCoreDumpLogInteger(
+        "ScriptRunner successful executions",
+        static_cast<ScriptRunnerLog*>(context)->success_count_);
+    SbCoreDumpLogInteger("ScriptRunner failed executions",
+                         static_cast<ScriptRunnerLog*>(context)->fail_count_);
+  }
+
+  void IncrementSuccessCount() { success_count_++; }
+  void IncrementFailCount() { fail_count_++; }
+
+ private:
+  int success_count_;
+  int fail_count_;
+  DISALLOW_COPY_AND_ASSIGN(ScriptRunnerLog);
+};
+
+base::LazyInstance<ScriptRunnerLog> script_runner_log =
+    LAZY_INSTANCE_INITIALIZER;
+
+#endif  // defined(HANDLE_CORE_DUMP)
+
 class ScriptRunnerImpl : public ScriptRunner {
  public:
   explicit ScriptRunnerImpl(
@@ -53,8 +92,14 @@
   std::string result;
   if (!global_environment_->EvaluateScript(source_code, &result)) {
     DLOG(WARNING) << "Failed to execute JavaScript: " << result;
+#if defined(HANDLE_CORE_DUMP)
+    script_runner_log.Get().IncrementFailCount();
+#endif
     return "";
   }
+#if defined(HANDLE_CORE_DUMP)
+  script_runner_log.Get().IncrementSuccessCount();
+#endif
   return result;
 }
 
diff --git a/src/cobalt/speech/SpeechRecognition.idl b/src/cobalt/speech/SpeechRecognition.idl
index 1db9e80..1bb1a24 100644
--- a/src/cobalt/speech/SpeechRecognition.idl
+++ b/src/cobalt/speech/SpeechRecognition.idl
@@ -28,7 +28,7 @@
   attribute unsigned long maxAlternatives;
 
   // methods to drive the speech interaction
-  void start();
+  [RaisesException] void start();
   void stop();
   void abort();
 
diff --git a/src/cobalt/speech/SpeechRecognitionResult.idl b/src/cobalt/speech/SpeechRecognitionResult.idl
index 6cc3071..ef1e97d 100644
--- a/src/cobalt/speech/SpeechRecognitionResult.idl
+++ b/src/cobalt/speech/SpeechRecognitionResult.idl
@@ -19,5 +19,6 @@
 interface SpeechRecognitionResult {
   readonly attribute unsigned long length;
   getter SpeechRecognitionAlternative item(unsigned long index);
-  readonly attribute boolean final;
+  // isFinal is different from the spec., but follows with Chromium.
+  readonly attribute boolean isFinal;
 };
diff --git a/src/cobalt/speech/audio_encoder_flac.cc b/src/cobalt/speech/audio_encoder_flac.cc
new file mode 100644
index 0000000..16c5640
--- /dev/null
+++ b/src/cobalt/speech/audio_encoder_flac.cc
@@ -0,0 +1,130 @@
+/*
+ * 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/audio_encoder_flac.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+const char kContentTypeFLAC[] = "audio/x-flac; rate=";
+const int kFLACCompressionLevel = 0;  // 0 for speed
+const int kBitsPerSample = 16;
+const float kMaxInt16AsFloat32 = 32767.0f;
+}  // namespace
+
+AudioEncoderFlac::AudioEncoderFlac(int sample_rate)
+    : encoder_(FLAC__stream_encoder_new()) {
+  DCHECK(encoder_);
+
+  // Set the number of channels to be encoded.
+  FLAC__stream_encoder_set_channels(encoder_, 1);
+  // Set the sample resolution of the input to be encoded.
+  FLAC__stream_encoder_set_bits_per_sample(encoder_, kBitsPerSample);
+  // Set the sample rate (in Hz) of the input to be encoded.
+  FLAC__stream_encoder_set_sample_rate(encoder_,
+                                       static_cast<uint32>(sample_rate));
+  // Set the compression level. A higher level usually means more computation
+  // but higher compression.
+  FLAC__stream_encoder_set_compression_level(encoder_, kFLACCompressionLevel);
+
+  // Initialize the encoder instance to encode native FLAC stream.
+  FLAC__StreamEncoderInitStatus encoder_status =
+      FLAC__stream_encoder_init_stream(encoder_, WriteCallback, NULL, NULL,
+                                       NULL, this);
+  DCHECK_EQ(encoder_status, FLAC__STREAM_ENCODER_INIT_STATUS_OK);
+}
+
+AudioEncoderFlac::~AudioEncoderFlac() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  FLAC__stream_encoder_delete(encoder_);
+}
+
+void AudioEncoderFlac::Encode(const ShellAudioBus* audio_bus) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  DCHECK_EQ(audio_bus->channels(), 1);
+  uint32 frames = static_cast<uint32>(audio_bus->frames());
+  scoped_array<FLAC__int32> flac_samples(new FLAC__int32[frames]);
+  for (uint32 i = 0; i < frames; ++i) {
+    if (audio_bus->sample_type() == ShellAudioBus::kFloat32) {
+      flac_samples[i] = static_cast<FLAC__int32>(
+          audio_bus->GetFloat32Sample(0, i) * kMaxInt16AsFloat32);
+    } else {
+      DCHECK_EQ(audio_bus->sample_type(), ShellAudioBus::kInt16);
+      flac_samples[i] =
+          static_cast<FLAC__int32>(audio_bus->GetInt16Sample(0, i));
+    }
+  }
+
+  FLAC__int32* flac_samples_ptr = flac_samples.get();
+  // Submit data for encoding.
+  FLAC__bool success =
+      FLAC__stream_encoder_process(encoder_, &flac_samples_ptr, frames);
+  DCHECK(success);
+}
+
+void AudioEncoderFlac::Finish() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Finish the encoding. It causes the encoder to encode any data still in
+  // its input pipe, and finally reset the encoder to the unintialized state.
+  FLAC__stream_encoder_finish(encoder_);
+}
+
+std::string AudioEncoderFlac::GetMimeType() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  return std::string(kContentTypeFLAC) +
+         base::UintToString(FLAC__stream_encoder_get_sample_rate(encoder_));
+}
+
+std::string AudioEncoderFlac::GetAndClearAvailableEncodedData() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  std::string result = encoded_data_;
+  encoded_data_.clear();
+  return result;
+}
+
+// A write callback which will be called anytime there is a raw encoded data to
+// write. The call to FLAC__stream_encoder_init_stream() currently will also
+// immediately call the write callback several times, once with the FLAC
+// signature, and once for each encoded metadata block.
+FLAC__StreamEncoderWriteStatus AudioEncoderFlac::WriteCallback(
+    const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes,
+    unsigned int samples, unsigned int current_frame, void* client_data) {
+  UNREFERENCED_PARAMETER(encoder);
+  UNREFERENCED_PARAMETER(samples);
+  UNREFERENCED_PARAMETER(current_frame);
+
+  AudioEncoderFlac* audio_encoder =
+      reinterpret_cast<AudioEncoderFlac*>(client_data);
+  DCHECK(audio_encoder);
+  DCHECK(audio_encoder->thread_checker_.CalledOnValidThread());
+
+  audio_encoder->encoded_data_.append(reinterpret_cast<const char*>(buffer),
+                                      bytes);
+
+  return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+}  // namespace speech
+}  // namespace cobalt
diff --git a/src/cobalt/speech/audio_encoder_flac.h b/src/cobalt/speech/audio_encoder_flac.h
new file mode 100644
index 0000000..62fcfb1
--- /dev/null
+++ b/src/cobalt/speech/audio_encoder_flac.h
@@ -0,0 +1,66 @@
+/*
+ * 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_AUDIO_ENCODER_FLAC_H_
+#define COBALT_SPEECH_AUDIO_ENCODER_FLAC_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/threading/thread_checker.h"
+#include "media/base/shell_audio_bus.h"
+#include "third_party/flac/include/FLAC/stream_encoder.h"
+
+namespace cobalt {
+namespace speech {
+
+// Encode raw audio to using FLAC codec.
+class AudioEncoderFlac {
+ public:
+  typedef ::media::ShellAudioBus ShellAudioBus;
+
+  explicit AudioEncoderFlac(int sample_rate);
+  ~AudioEncoderFlac();
+
+  // Encode raw audio data.
+  void Encode(const ShellAudioBus* audio_bus);
+  // Finish encoding.
+  void Finish();
+
+  // Returns mime Type of audio data.
+  std::string GetMimeType() const;
+  // Returns and clears the available encoded audio data.
+  std::string GetAndClearAvailableEncodedData();
+
+ private:
+  // FLAC encoder callback.
+  static FLAC__StreamEncoderWriteStatus WriteCallback(
+      const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[],
+      size_t bytes, unsigned int samples, unsigned int current_frame,
+      void* client_data);
+
+  base::ThreadChecker thread_checker_;
+  // FLAC encoder.
+  FLAC__StreamEncoder* encoder_;
+  // Cached encoded data.
+  std::string encoded_data_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // COBALT_SPEECH_AUDIO_ENCODER_FLAC_H_
diff --git a/src/cobalt/speech/endpointer_delegate.cc b/src/cobalt/speech/endpointer_delegate.cc
new file mode 100644
index 0000000..f8a826f
--- /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;
+  content::EpStatus status = endpointer_.Status(&ep_time);
+  if (status == content::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..db6a528
--- /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 "content/browser/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.
+  content::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/google_streaming_api.pb.cc b/src/cobalt/speech/google_streaming_api.pb.cc
new file mode 100644
index 0000000..1d33d80
--- /dev/null
+++ b/src/cobalt/speech/google_streaming_api.pb.cc
@@ -0,0 +1,757 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+
+#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION
+
+#include <algorithm>
+
+#include "google_streaming_api.pb.h"
+
+#include <google/protobuf/stubs/once.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/wire_format_lite_inl.h>
+// @@protoc_insertion_point(includes)
+
+namespace cobalt {
+namespace speech {
+namespace proto {
+
+void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto() {
+  delete SpeechRecognitionEvent::default_instance_;
+  delete SpeechRecognitionResult::default_instance_;
+  delete SpeechRecognitionAlternative::default_instance_;
+}
+
+void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto() {
+  static bool already_here = false;
+  if (already_here) return;
+  already_here = true;
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  SpeechRecognitionEvent::default_instance_ = new SpeechRecognitionEvent();
+  SpeechRecognitionResult::default_instance_ = new SpeechRecognitionResult();
+  SpeechRecognitionAlternative::default_instance_ = new SpeechRecognitionAlternative();
+  SpeechRecognitionEvent::default_instance_->InitAsDefaultInstance();
+  SpeechRecognitionResult::default_instance_->InitAsDefaultInstance();
+  SpeechRecognitionAlternative::default_instance_->InitAsDefaultInstance();
+  ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto);
+}
+
+// Force AddDescriptors() to be called at static initialization time.
+struct StaticDescriptorInitializer_google_5fstreaming_5fapi_2eproto {
+  StaticDescriptorInitializer_google_5fstreaming_5fapi_2eproto() {
+    protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();
+  }
+} static_descriptor_initializer_google_5fstreaming_5fapi_2eproto_;
+
+
+// ===================================================================
+
+bool SpeechRecognitionEvent_StatusCode_IsValid(int value) {
+  switch(value) {
+    case 0:
+    case 1:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+      return true;
+    default:
+      return false;
+  }
+}
+
+#ifndef _MSC_VER
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_SUCCESS;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NO_SPEECH;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_ABORTED;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NETWORK;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NOT_ALLOWED;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_BAD_GRAMMAR;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::StatusCode_MIN;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::StatusCode_MAX;
+const int SpeechRecognitionEvent::StatusCode_ARRAYSIZE;
+#endif  // _MSC_VER
+bool SpeechRecognitionEvent_EndpointerEventType_IsValid(int value) {
+  switch(value) {
+    case 0:
+    case 1:
+    case 2:
+    case 3:
+      return true;
+    default:
+      return false;
+  }
+}
+
+#ifndef _MSC_VER
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::START_OF_SPEECH;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_SPEECH;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_AUDIO;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_UTTERANCE;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::EndpointerEventType_MIN;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::EndpointerEventType_MAX;
+const int SpeechRecognitionEvent::EndpointerEventType_ARRAYSIZE;
+#endif  // _MSC_VER
+#ifndef _MSC_VER
+const int SpeechRecognitionEvent::kStatusFieldNumber;
+const int SpeechRecognitionEvent::kResultFieldNumber;
+const int SpeechRecognitionEvent::kEndpointFieldNumber;
+#endif  // !_MSC_VER
+
+SpeechRecognitionEvent::SpeechRecognitionEvent()
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+}
+
+void SpeechRecognitionEvent::InitAsDefaultInstance() {
+}
+
+SpeechRecognitionEvent::SpeechRecognitionEvent(const SpeechRecognitionEvent& from)
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+  MergeFrom(from);
+}
+
+void SpeechRecognitionEvent::SharedCtor() {
+  _cached_size_ = 0;
+  status_ = 0;
+  endpoint_ = 0;
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+SpeechRecognitionEvent::~SpeechRecognitionEvent() {
+  SharedDtor();
+}
+
+void SpeechRecognitionEvent::SharedDtor() {
+  if (this != default_instance_) {
+  }
+}
+
+void SpeechRecognitionEvent::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const SpeechRecognitionEvent& SpeechRecognitionEvent::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();  return *default_instance_;
+}
+
+SpeechRecognitionEvent* SpeechRecognitionEvent::default_instance_ = NULL;
+
+SpeechRecognitionEvent* SpeechRecognitionEvent::New() const {
+  return new SpeechRecognitionEvent;
+}
+
+void SpeechRecognitionEvent::Clear() {
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    status_ = 0;
+    endpoint_ = 0;
+  }
+  result_.Clear();
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+bool SpeechRecognitionEvent::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) return false
+  ::google::protobuf::uint32 tag;
+  while ((tag = input->ReadTag()) != 0) {
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS];
+      case 1: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+          int value;
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>(
+                 input, &value)));
+          if (::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode_IsValid(value)) {
+            set_status(static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode >(value));
+          }
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(18)) goto parse_result;
+        break;
+      }
+      
+      // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2;
+      case 2: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) {
+         parse_result:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+                input, add_result()));
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(18)) goto parse_result;
+        if (input->ExpectTag(32)) goto parse_endpoint;
+        break;
+      }
+      
+      // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4;
+      case 4: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+         parse_endpoint:
+          int value;
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>(
+                 input, &value)));
+          if (::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType_IsValid(value)) {
+            set_endpoint(static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType >(value));
+          }
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectAtEnd()) return true;
+        break;
+      }
+      
+      default: {
+      handle_uninterpreted:
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          return true;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag));
+        break;
+      }
+    }
+  }
+  return true;
+#undef DO_
+}
+
+void SpeechRecognitionEvent::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS];
+  if (has_status()) {
+    ::google::protobuf::internal::WireFormatLite::WriteEnum(
+      1, this->status(), output);
+  }
+  
+  // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2;
+  for (int i = 0; i < this->result_size(); i++) {
+    ::google::protobuf::internal::WireFormatLite::WriteMessage(
+      2, this->result(i), output);
+  }
+  
+  // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4;
+  if (has_endpoint()) {
+    ::google::protobuf::internal::WireFormatLite::WriteEnum(
+      4, this->endpoint(), output);
+  }
+  
+}
+
+int SpeechRecognitionEvent::ByteSize() const {
+  int total_size = 0;
+  
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS];
+    if (has_status()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::EnumSize(this->status());
+    }
+    
+    // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4;
+    if (has_endpoint()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::EnumSize(this->endpoint());
+    }
+    
+  }
+  // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2;
+  total_size += 1 * this->result_size();
+  for (int i = 0; i < this->result_size(); i++) {
+    total_size +=
+      ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+        this->result(i));
+  }
+  
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void SpeechRecognitionEvent::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionEvent*>(&from));
+}
+
+void SpeechRecognitionEvent::MergeFrom(const SpeechRecognitionEvent& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  result_.MergeFrom(from.result_);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_status()) {
+      set_status(from.status());
+    }
+    if (from.has_endpoint()) {
+      set_endpoint(from.endpoint());
+    }
+  }
+}
+
+void SpeechRecognitionEvent::CopyFrom(const SpeechRecognitionEvent& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool SpeechRecognitionEvent::IsInitialized() const {
+  
+  return true;
+}
+
+void SpeechRecognitionEvent::Swap(SpeechRecognitionEvent* other) {
+  if (other != this) {
+    std::swap(status_, other->status_);
+    result_.Swap(&other->result_);
+    std::swap(endpoint_, other->endpoint_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::std::string SpeechRecognitionEvent::GetTypeName() const {
+  return "cobalt.speech.proto.SpeechRecognitionEvent";
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int SpeechRecognitionResult::kAlternativeFieldNumber;
+const int SpeechRecognitionResult::kFinalFieldNumber;
+const int SpeechRecognitionResult::kStabilityFieldNumber;
+#endif  // !_MSC_VER
+
+SpeechRecognitionResult::SpeechRecognitionResult()
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+}
+
+void SpeechRecognitionResult::InitAsDefaultInstance() {
+}
+
+SpeechRecognitionResult::SpeechRecognitionResult(const SpeechRecognitionResult& from)
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+  MergeFrom(from);
+}
+
+void SpeechRecognitionResult::SharedCtor() {
+  _cached_size_ = 0;
+  final_ = false;
+  stability_ = 0;
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+SpeechRecognitionResult::~SpeechRecognitionResult() {
+  SharedDtor();
+}
+
+void SpeechRecognitionResult::SharedDtor() {
+  if (this != default_instance_) {
+  }
+}
+
+void SpeechRecognitionResult::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const SpeechRecognitionResult& SpeechRecognitionResult::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();  return *default_instance_;
+}
+
+SpeechRecognitionResult* SpeechRecognitionResult::default_instance_ = NULL;
+
+SpeechRecognitionResult* SpeechRecognitionResult::New() const {
+  return new SpeechRecognitionResult;
+}
+
+void SpeechRecognitionResult::Clear() {
+  if (_has_bits_[1 / 32] & (0xffu << (1 % 32))) {
+    final_ = false;
+    stability_ = 0;
+  }
+  alternative_.Clear();
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+bool SpeechRecognitionResult::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) return false
+  ::google::protobuf::uint32 tag;
+  while ((tag = input->ReadTag()) != 0) {
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1;
+      case 1: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) {
+         parse_alternative:
+          DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+                input, add_alternative()));
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(10)) goto parse_alternative;
+        if (input->ExpectTag(16)) goto parse_final;
+        break;
+      }
+      
+      // optional bool final = 2 [default = false];
+      case 2: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+         parse_final:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>(
+                 input, &final_)));
+          set_has_final();
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(29)) goto parse_stability;
+        break;
+      }
+      
+      // optional float stability = 3;
+      case 3: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_FIXED32) {
+         parse_stability:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
+                 input, &stability_)));
+          set_has_stability();
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectAtEnd()) return true;
+        break;
+      }
+      
+      default: {
+      handle_uninterpreted:
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          return true;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag));
+        break;
+      }
+    }
+  }
+  return true;
+#undef DO_
+}
+
+void SpeechRecognitionResult::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1;
+  for (int i = 0; i < this->alternative_size(); i++) {
+    ::google::protobuf::internal::WireFormatLite::WriteMessage(
+      1, this->alternative(i), output);
+  }
+  
+  // optional bool final = 2 [default = false];
+  if (has_final()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBool(2, this->final(), output);
+  }
+  
+  // optional float stability = 3;
+  if (has_stability()) {
+    ::google::protobuf::internal::WireFormatLite::WriteFloat(3, this->stability(), output);
+  }
+  
+}
+
+int SpeechRecognitionResult::ByteSize() const {
+  int total_size = 0;
+  
+  if (_has_bits_[1 / 32] & (0xffu << (1 % 32))) {
+    // optional bool final = 2 [default = false];
+    if (has_final()) {
+      total_size += 1 + 1;
+    }
+    
+    // optional float stability = 3;
+    if (has_stability()) {
+      total_size += 1 + 4;
+    }
+    
+  }
+  // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1;
+  total_size += 1 * this->alternative_size();
+  for (int i = 0; i < this->alternative_size(); i++) {
+    total_size +=
+      ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+        this->alternative(i));
+  }
+  
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void SpeechRecognitionResult::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionResult*>(&from));
+}
+
+void SpeechRecognitionResult::MergeFrom(const SpeechRecognitionResult& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  alternative_.MergeFrom(from.alternative_);
+  if (from._has_bits_[1 / 32] & (0xffu << (1 % 32))) {
+    if (from.has_final()) {
+      set_final(from.final());
+    }
+    if (from.has_stability()) {
+      set_stability(from.stability());
+    }
+  }
+}
+
+void SpeechRecognitionResult::CopyFrom(const SpeechRecognitionResult& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool SpeechRecognitionResult::IsInitialized() const {
+  
+  return true;
+}
+
+void SpeechRecognitionResult::Swap(SpeechRecognitionResult* other) {
+  if (other != this) {
+    alternative_.Swap(&other->alternative_);
+    std::swap(final_, other->final_);
+    std::swap(stability_, other->stability_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::std::string SpeechRecognitionResult::GetTypeName() const {
+  return "cobalt.speech.proto.SpeechRecognitionResult";
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int SpeechRecognitionAlternative::kTranscriptFieldNumber;
+const int SpeechRecognitionAlternative::kConfidenceFieldNumber;
+#endif  // !_MSC_VER
+
+SpeechRecognitionAlternative::SpeechRecognitionAlternative()
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+}
+
+void SpeechRecognitionAlternative::InitAsDefaultInstance() {
+}
+
+SpeechRecognitionAlternative::SpeechRecognitionAlternative(const SpeechRecognitionAlternative& from)
+  : ::google::protobuf::MessageLite() {
+  SharedCtor();
+  MergeFrom(from);
+}
+
+void SpeechRecognitionAlternative::SharedCtor() {
+  _cached_size_ = 0;
+  transcript_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString);
+  confidence_ = 0;
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+SpeechRecognitionAlternative::~SpeechRecognitionAlternative() {
+  SharedDtor();
+}
+
+void SpeechRecognitionAlternative::SharedDtor() {
+  if (transcript_ != &::google::protobuf::internal::kEmptyString) {
+    delete transcript_;
+  }
+  if (this != default_instance_) {
+  }
+}
+
+void SpeechRecognitionAlternative::SetCachedSize(int size) const {
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const SpeechRecognitionAlternative& SpeechRecognitionAlternative::default_instance() {
+  if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();  return *default_instance_;
+}
+
+SpeechRecognitionAlternative* SpeechRecognitionAlternative::default_instance_ = NULL;
+
+SpeechRecognitionAlternative* SpeechRecognitionAlternative::New() const {
+  return new SpeechRecognitionAlternative;
+}
+
+void SpeechRecognitionAlternative::Clear() {
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (has_transcript()) {
+      if (transcript_ != &::google::protobuf::internal::kEmptyString) {
+        transcript_->clear();
+      }
+    }
+    confidence_ = 0;
+  }
+  ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+bool SpeechRecognitionAlternative::MergePartialFromCodedStream(
+    ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) return false
+  ::google::protobuf::uint32 tag;
+  while ((tag = input->ReadTag()) != 0) {
+    switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+      // optional string transcript = 1;
+      case 1: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) {
+          DO_(::google::protobuf::internal::WireFormatLite::ReadString(
+                input, this->mutable_transcript()));
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectTag(21)) goto parse_confidence;
+        break;
+      }
+      
+      // optional float confidence = 2;
+      case 2: {
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_FIXED32) {
+         parse_confidence:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
+                 input, &confidence_)));
+          set_has_confidence();
+        } else {
+          goto handle_uninterpreted;
+        }
+        if (input->ExpectAtEnd()) return true;
+        break;
+      }
+      
+      default: {
+      handle_uninterpreted:
+        if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+            ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+          return true;
+        }
+        DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag));
+        break;
+      }
+    }
+  }
+  return true;
+#undef DO_
+}
+
+void SpeechRecognitionAlternative::SerializeWithCachedSizes(
+    ::google::protobuf::io::CodedOutputStream* output) const {
+  // optional string transcript = 1;
+  if (has_transcript()) {
+    ::google::protobuf::internal::WireFormatLite::WriteString(
+      1, this->transcript(), output);
+  }
+  
+  // optional float confidence = 2;
+  if (has_confidence()) {
+    ::google::protobuf::internal::WireFormatLite::WriteFloat(2, this->confidence(), output);
+  }
+  
+}
+
+int SpeechRecognitionAlternative::ByteSize() const {
+  int total_size = 0;
+  
+  if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    // optional string transcript = 1;
+    if (has_transcript()) {
+      total_size += 1 +
+        ::google::protobuf::internal::WireFormatLite::StringSize(
+          this->transcript());
+    }
+    
+    // optional float confidence = 2;
+    if (has_confidence()) {
+      total_size += 1 + 4;
+    }
+    
+  }
+  GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+  _cached_size_ = total_size;
+  GOOGLE_SAFE_CONCURRENT_WRITES_END();
+  return total_size;
+}
+
+void SpeechRecognitionAlternative::CheckTypeAndMergeFrom(
+    const ::google::protobuf::MessageLite& from) {
+  MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionAlternative*>(&from));
+}
+
+void SpeechRecognitionAlternative::MergeFrom(const SpeechRecognitionAlternative& from) {
+  GOOGLE_CHECK_NE(&from, this);
+  if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+    if (from.has_transcript()) {
+      set_transcript(from.transcript());
+    }
+    if (from.has_confidence()) {
+      set_confidence(from.confidence());
+    }
+  }
+}
+
+void SpeechRecognitionAlternative::CopyFrom(const SpeechRecognitionAlternative& from) {
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool SpeechRecognitionAlternative::IsInitialized() const {
+  
+  return true;
+}
+
+void SpeechRecognitionAlternative::Swap(SpeechRecognitionAlternative* other) {
+  if (other != this) {
+    std::swap(transcript_, other->transcript_);
+    std::swap(confidence_, other->confidence_);
+    std::swap(_has_bits_[0], other->_has_bits_[0]);
+    std::swap(_cached_size_, other->_cached_size_);
+  }
+}
+
+::std::string SpeechRecognitionAlternative::GetTypeName() const {
+  return "cobalt.speech.proto.SpeechRecognitionAlternative";
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace proto
+}  // namespace speech
+}  // namespace cobalt
+
+// @@protoc_insertion_point(global_scope)
diff --git a/src/cobalt/speech/google_streaming_api.pb.h b/src/cobalt/speech/google_streaming_api.pb.h
new file mode 100644
index 0000000..b666423
--- /dev/null
+++ b/src/cobalt/speech/google_streaming_api.pb.h
@@ -0,0 +1,613 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: google_streaming_api.proto
+
+#ifndef PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED
+#define PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED
+
+#include <string>
+
+#include <google/protobuf/stubs/common.h>
+
+#if GOOGLE_PROTOBUF_VERSION < 2004000
+#error This file was generated by a newer version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please update
+#error your headers.
+#endif
+#if 2004002 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
+#error This file was generated by an older version of protoc which is
+#error incompatible with your Protocol Buffer headers.  Please
+#error regenerate this file with a newer version of protoc.
+#endif
+
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/repeated_field.h>
+#include <google/protobuf/extension_set.h>
+// @@protoc_insertion_point(includes)
+
+namespace cobalt {
+namespace speech {
+namespace proto {
+
+// Internal implementation detail -- do not call these.
+void  protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();
+void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto();
+void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto();
+
+class SpeechRecognitionEvent;
+class SpeechRecognitionResult;
+class SpeechRecognitionAlternative;
+
+enum SpeechRecognitionEvent_StatusCode {
+  SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS = 0,
+  SpeechRecognitionEvent_StatusCode_STATUS_NO_SPEECH = 1,
+  SpeechRecognitionEvent_StatusCode_STATUS_ABORTED = 2,
+  SpeechRecognitionEvent_StatusCode_STATUS_AUDIO_CAPTURE = 3,
+  SpeechRecognitionEvent_StatusCode_STATUS_NETWORK = 4,
+  SpeechRecognitionEvent_StatusCode_STATUS_NOT_ALLOWED = 5,
+  SpeechRecognitionEvent_StatusCode_STATUS_SERVICE_NOT_ALLOWED = 6,
+  SpeechRecognitionEvent_StatusCode_STATUS_BAD_GRAMMAR = 7,
+  SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED = 8
+};
+bool SpeechRecognitionEvent_StatusCode_IsValid(int value);
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent_StatusCode_StatusCode_MIN = SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS;
+const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent_StatusCode_StatusCode_MAX = SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED;
+const int SpeechRecognitionEvent_StatusCode_StatusCode_ARRAYSIZE = SpeechRecognitionEvent_StatusCode_StatusCode_MAX + 1;
+
+enum SpeechRecognitionEvent_EndpointerEventType {
+  SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH = 0,
+  SpeechRecognitionEvent_EndpointerEventType_END_OF_SPEECH = 1,
+  SpeechRecognitionEvent_EndpointerEventType_END_OF_AUDIO = 2,
+  SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE = 3
+};
+bool SpeechRecognitionEvent_EndpointerEventType_IsValid(int value);
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MIN = SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH;
+const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX = SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE;
+const int SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_ARRAYSIZE = SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX + 1;
+
+// ===================================================================
+
+class SpeechRecognitionEvent : public ::google::protobuf::MessageLite {
+ public:
+  SpeechRecognitionEvent();
+  virtual ~SpeechRecognitionEvent();
+  
+  SpeechRecognitionEvent(const SpeechRecognitionEvent& from);
+  
+  inline SpeechRecognitionEvent& operator=(const SpeechRecognitionEvent& from) {
+    CopyFrom(from);
+    return *this;
+  }
+  
+  static const SpeechRecognitionEvent& default_instance();
+  
+  void Swap(SpeechRecognitionEvent* other);
+  
+  // implements Message ----------------------------------------------
+  
+  SpeechRecognitionEvent* New() const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const SpeechRecognitionEvent& from);
+  void MergeFrom(const SpeechRecognitionEvent& from);
+  void Clear();
+  bool IsInitialized() const;
+  
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  
+  ::std::string GetTypeName() const;
+  
+  // nested types ----------------------------------------------------
+  
+  typedef SpeechRecognitionEvent_StatusCode StatusCode;
+  static const StatusCode STATUS_SUCCESS = SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS;
+  static const StatusCode STATUS_NO_SPEECH = SpeechRecognitionEvent_StatusCode_STATUS_NO_SPEECH;
+  static const StatusCode STATUS_ABORTED = SpeechRecognitionEvent_StatusCode_STATUS_ABORTED;
+  static const StatusCode STATUS_AUDIO_CAPTURE = SpeechRecognitionEvent_StatusCode_STATUS_AUDIO_CAPTURE;
+  static const StatusCode STATUS_NETWORK = SpeechRecognitionEvent_StatusCode_STATUS_NETWORK;
+  static const StatusCode STATUS_NOT_ALLOWED = SpeechRecognitionEvent_StatusCode_STATUS_NOT_ALLOWED;
+  static const StatusCode STATUS_SERVICE_NOT_ALLOWED = SpeechRecognitionEvent_StatusCode_STATUS_SERVICE_NOT_ALLOWED;
+  static const StatusCode STATUS_BAD_GRAMMAR = SpeechRecognitionEvent_StatusCode_STATUS_BAD_GRAMMAR;
+  static const StatusCode STATUS_LANGUAGE_NOT_SUPPORTED = SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED;
+  static inline bool StatusCode_IsValid(int value) {
+    return SpeechRecognitionEvent_StatusCode_IsValid(value);
+  }
+  static const StatusCode StatusCode_MIN =
+    SpeechRecognitionEvent_StatusCode_StatusCode_MIN;
+  static const StatusCode StatusCode_MAX =
+    SpeechRecognitionEvent_StatusCode_StatusCode_MAX;
+  static const int StatusCode_ARRAYSIZE =
+    SpeechRecognitionEvent_StatusCode_StatusCode_ARRAYSIZE;
+  
+  typedef SpeechRecognitionEvent_EndpointerEventType EndpointerEventType;
+  static const EndpointerEventType START_OF_SPEECH = SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH;
+  static const EndpointerEventType END_OF_SPEECH = SpeechRecognitionEvent_EndpointerEventType_END_OF_SPEECH;
+  static const EndpointerEventType END_OF_AUDIO = SpeechRecognitionEvent_EndpointerEventType_END_OF_AUDIO;
+  static const EndpointerEventType END_OF_UTTERANCE = SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE;
+  static inline bool EndpointerEventType_IsValid(int value) {
+    return SpeechRecognitionEvent_EndpointerEventType_IsValid(value);
+  }
+  static const EndpointerEventType EndpointerEventType_MIN =
+    SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MIN;
+  static const EndpointerEventType EndpointerEventType_MAX =
+    SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX;
+  static const int EndpointerEventType_ARRAYSIZE =
+    SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_ARRAYSIZE;
+  
+  // accessors -------------------------------------------------------
+  
+  // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS];
+  inline bool has_status() const;
+  inline void clear_status();
+  static const int kStatusFieldNumber = 1;
+  inline ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode status() const;
+  inline void set_status(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode value);
+  
+  // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2;
+  inline int result_size() const;
+  inline void clear_result();
+  static const int kResultFieldNumber = 2;
+  inline const ::cobalt::speech::proto::SpeechRecognitionResult& result(int index) const;
+  inline ::cobalt::speech::proto::SpeechRecognitionResult* mutable_result(int index);
+  inline ::cobalt::speech::proto::SpeechRecognitionResult* add_result();
+  inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >&
+      result() const;
+  inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >*
+      mutable_result();
+  
+  // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4;
+  inline bool has_endpoint() const;
+  inline void clear_endpoint();
+  static const int kEndpointFieldNumber = 4;
+  inline ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType endpoint() const;
+  inline void set_endpoint(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType value);
+  
+  // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionEvent)
+ private:
+  inline void set_has_status();
+  inline void clear_has_status();
+  inline void set_has_endpoint();
+  inline void clear_has_endpoint();
+  
+  ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult > result_;
+  int status_;
+  int endpoint_;
+  
+  mutable int _cached_size_;
+  ::google::protobuf::uint32 _has_bits_[(3 + 31) / 32];
+  
+  friend void  protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto();
+  
+  void InitAsDefaultInstance();
+  static SpeechRecognitionEvent* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class SpeechRecognitionResult : public ::google::protobuf::MessageLite {
+ public:
+  SpeechRecognitionResult();
+  virtual ~SpeechRecognitionResult();
+  
+  SpeechRecognitionResult(const SpeechRecognitionResult& from);
+  
+  inline SpeechRecognitionResult& operator=(const SpeechRecognitionResult& from) {
+    CopyFrom(from);
+    return *this;
+  }
+  
+  static const SpeechRecognitionResult& default_instance();
+  
+  void Swap(SpeechRecognitionResult* other);
+  
+  // implements Message ----------------------------------------------
+  
+  SpeechRecognitionResult* New() const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const SpeechRecognitionResult& from);
+  void MergeFrom(const SpeechRecognitionResult& from);
+  void Clear();
+  bool IsInitialized() const;
+  
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  
+  ::std::string GetTypeName() const;
+  
+  // nested types ----------------------------------------------------
+  
+  // accessors -------------------------------------------------------
+  
+  // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1;
+  inline int alternative_size() const;
+  inline void clear_alternative();
+  static const int kAlternativeFieldNumber = 1;
+  inline const ::cobalt::speech::proto::SpeechRecognitionAlternative& alternative(int index) const;
+  inline ::cobalt::speech::proto::SpeechRecognitionAlternative* mutable_alternative(int index);
+  inline ::cobalt::speech::proto::SpeechRecognitionAlternative* add_alternative();
+  inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >&
+      alternative() const;
+  inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >*
+      mutable_alternative();
+  
+  // optional bool final = 2 [default = false];
+  inline bool has_final() const;
+  inline void clear_final();
+  static const int kFinalFieldNumber = 2;
+  inline bool final() const;
+  inline void set_final(bool value);
+  
+  // optional float stability = 3;
+  inline bool has_stability() const;
+  inline void clear_stability();
+  static const int kStabilityFieldNumber = 3;
+  inline float stability() const;
+  inline void set_stability(float value);
+  
+  // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionResult)
+ private:
+  inline void set_has_final();
+  inline void clear_has_final();
+  inline void set_has_stability();
+  inline void clear_has_stability();
+  
+  ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative > alternative_;
+  bool final_;
+  float stability_;
+  
+  mutable int _cached_size_;
+  ::google::protobuf::uint32 _has_bits_[(3 + 31) / 32];
+  
+  friend void  protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto();
+  
+  void InitAsDefaultInstance();
+  static SpeechRecognitionResult* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class SpeechRecognitionAlternative : public ::google::protobuf::MessageLite {
+ public:
+  SpeechRecognitionAlternative();
+  virtual ~SpeechRecognitionAlternative();
+  
+  SpeechRecognitionAlternative(const SpeechRecognitionAlternative& from);
+  
+  inline SpeechRecognitionAlternative& operator=(const SpeechRecognitionAlternative& from) {
+    CopyFrom(from);
+    return *this;
+  }
+  
+  static const SpeechRecognitionAlternative& default_instance();
+  
+  void Swap(SpeechRecognitionAlternative* other);
+  
+  // implements Message ----------------------------------------------
+  
+  SpeechRecognitionAlternative* New() const;
+  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
+  void CopyFrom(const SpeechRecognitionAlternative& from);
+  void MergeFrom(const SpeechRecognitionAlternative& from);
+  void Clear();
+  bool IsInitialized() const;
+  
+  int ByteSize() const;
+  bool MergePartialFromCodedStream(
+      ::google::protobuf::io::CodedInputStream* input);
+  void SerializeWithCachedSizes(
+      ::google::protobuf::io::CodedOutputStream* output) const;
+  int GetCachedSize() const { return _cached_size_; }
+  private:
+  void SharedCtor();
+  void SharedDtor();
+  void SetCachedSize(int size) const;
+  public:
+  
+  ::std::string GetTypeName() const;
+  
+  // nested types ----------------------------------------------------
+  
+  // accessors -------------------------------------------------------
+  
+  // optional string transcript = 1;
+  inline bool has_transcript() const;
+  inline void clear_transcript();
+  static const int kTranscriptFieldNumber = 1;
+  inline const ::std::string& transcript() const;
+  inline void set_transcript(const ::std::string& value);
+  inline void set_transcript(const char* value);
+  inline void set_transcript(const char* value, size_t size);
+  inline ::std::string* mutable_transcript();
+  inline ::std::string* release_transcript();
+  
+  // optional float confidence = 2;
+  inline bool has_confidence() const;
+  inline void clear_confidence();
+  static const int kConfidenceFieldNumber = 2;
+  inline float confidence() const;
+  inline void set_confidence(float value);
+  
+  // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionAlternative)
+ private:
+  inline void set_has_transcript();
+  inline void clear_has_transcript();
+  inline void set_has_confidence();
+  inline void clear_has_confidence();
+  
+  ::std::string* transcript_;
+  float confidence_;
+  
+  mutable int _cached_size_;
+  ::google::protobuf::uint32 _has_bits_[(2 + 31) / 32];
+  
+  friend void  protobuf_AddDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto();
+  friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto();
+  
+  void InitAsDefaultInstance();
+  static SpeechRecognitionAlternative* default_instance_;
+};
+// ===================================================================
+
+
+// ===================================================================
+
+// SpeechRecognitionEvent
+
+// optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS];
+inline bool SpeechRecognitionEvent::has_status() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void SpeechRecognitionEvent::set_has_status() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void SpeechRecognitionEvent::clear_has_status() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void SpeechRecognitionEvent::clear_status() {
+  status_ = 0;
+  clear_has_status();
+}
+inline ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::status() const {
+  return static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode >(status_);
+}
+inline void SpeechRecognitionEvent::set_status(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode value) {
+  GOOGLE_DCHECK(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode_IsValid(value));
+  set_has_status();
+  status_ = value;
+}
+
+// repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2;
+inline int SpeechRecognitionEvent::result_size() const {
+  return result_.size();
+}
+inline void SpeechRecognitionEvent::clear_result() {
+  result_.Clear();
+}
+inline const ::cobalt::speech::proto::SpeechRecognitionResult& SpeechRecognitionEvent::result(int index) const {
+  return result_.Get(index);
+}
+inline ::cobalt::speech::proto::SpeechRecognitionResult* SpeechRecognitionEvent::mutable_result(int index) {
+  return result_.Mutable(index);
+}
+inline ::cobalt::speech::proto::SpeechRecognitionResult* SpeechRecognitionEvent::add_result() {
+  return result_.Add();
+}
+inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >&
+SpeechRecognitionEvent::result() const {
+  return result_;
+}
+inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >*
+SpeechRecognitionEvent::mutable_result() {
+  return &result_;
+}
+
+// optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4;
+inline bool SpeechRecognitionEvent::has_endpoint() const {
+  return (_has_bits_[0] & 0x00000004u) != 0;
+}
+inline void SpeechRecognitionEvent::set_has_endpoint() {
+  _has_bits_[0] |= 0x00000004u;
+}
+inline void SpeechRecognitionEvent::clear_has_endpoint() {
+  _has_bits_[0] &= ~0x00000004u;
+}
+inline void SpeechRecognitionEvent::clear_endpoint() {
+  endpoint_ = 0;
+  clear_has_endpoint();
+}
+inline ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::endpoint() const {
+  return static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType >(endpoint_);
+}
+inline void SpeechRecognitionEvent::set_endpoint(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType value) {
+  GOOGLE_DCHECK(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType_IsValid(value));
+  set_has_endpoint();
+  endpoint_ = value;
+}
+
+// -------------------------------------------------------------------
+
+// SpeechRecognitionResult
+
+// repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1;
+inline int SpeechRecognitionResult::alternative_size() const {
+  return alternative_.size();
+}
+inline void SpeechRecognitionResult::clear_alternative() {
+  alternative_.Clear();
+}
+inline const ::cobalt::speech::proto::SpeechRecognitionAlternative& SpeechRecognitionResult::alternative(int index) const {
+  return alternative_.Get(index);
+}
+inline ::cobalt::speech::proto::SpeechRecognitionAlternative* SpeechRecognitionResult::mutable_alternative(int index) {
+  return alternative_.Mutable(index);
+}
+inline ::cobalt::speech::proto::SpeechRecognitionAlternative* SpeechRecognitionResult::add_alternative() {
+  return alternative_.Add();
+}
+inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >&
+SpeechRecognitionResult::alternative() const {
+  return alternative_;
+}
+inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >*
+SpeechRecognitionResult::mutable_alternative() {
+  return &alternative_;
+}
+
+// optional bool final = 2 [default = false];
+inline bool SpeechRecognitionResult::has_final() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void SpeechRecognitionResult::set_has_final() {
+  _has_bits_[0] |= 0x00000002u;
+}
+inline void SpeechRecognitionResult::clear_has_final() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+inline void SpeechRecognitionResult::clear_final() {
+  final_ = false;
+  clear_has_final();
+}
+inline bool SpeechRecognitionResult::final() const {
+  return final_;
+}
+inline void SpeechRecognitionResult::set_final(bool value) {
+  set_has_final();
+  final_ = value;
+}
+
+// optional float stability = 3;
+inline bool SpeechRecognitionResult::has_stability() const {
+  return (_has_bits_[0] & 0x00000004u) != 0;
+}
+inline void SpeechRecognitionResult::set_has_stability() {
+  _has_bits_[0] |= 0x00000004u;
+}
+inline void SpeechRecognitionResult::clear_has_stability() {
+  _has_bits_[0] &= ~0x00000004u;
+}
+inline void SpeechRecognitionResult::clear_stability() {
+  stability_ = 0;
+  clear_has_stability();
+}
+inline float SpeechRecognitionResult::stability() const {
+  return stability_;
+}
+inline void SpeechRecognitionResult::set_stability(float value) {
+  set_has_stability();
+  stability_ = value;
+}
+
+// -------------------------------------------------------------------
+
+// SpeechRecognitionAlternative
+
+// optional string transcript = 1;
+inline bool SpeechRecognitionAlternative::has_transcript() const {
+  return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void SpeechRecognitionAlternative::set_has_transcript() {
+  _has_bits_[0] |= 0x00000001u;
+}
+inline void SpeechRecognitionAlternative::clear_has_transcript() {
+  _has_bits_[0] &= ~0x00000001u;
+}
+inline void SpeechRecognitionAlternative::clear_transcript() {
+  if (transcript_ != &::google::protobuf::internal::kEmptyString) {
+    transcript_->clear();
+  }
+  clear_has_transcript();
+}
+inline const ::std::string& SpeechRecognitionAlternative::transcript() const {
+  return *transcript_;
+}
+inline void SpeechRecognitionAlternative::set_transcript(const ::std::string& value) {
+  set_has_transcript();
+  if (transcript_ == &::google::protobuf::internal::kEmptyString) {
+    transcript_ = new ::std::string;
+  }
+  transcript_->assign(value);
+}
+inline void SpeechRecognitionAlternative::set_transcript(const char* value) {
+  set_has_transcript();
+  if (transcript_ == &::google::protobuf::internal::kEmptyString) {
+    transcript_ = new ::std::string;
+  }
+  transcript_->assign(value);
+}
+inline void SpeechRecognitionAlternative::set_transcript(const char* value, size_t size) {
+  set_has_transcript();
+  if (transcript_ == &::google::protobuf::internal::kEmptyString) {
+    transcript_ = new ::std::string;
+  }
+  transcript_->assign(reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* SpeechRecognitionAlternative::mutable_transcript() {
+  set_has_transcript();
+  if (transcript_ == &::google::protobuf::internal::kEmptyString) {
+    transcript_ = new ::std::string;
+  }
+  return transcript_;
+}
+inline ::std::string* SpeechRecognitionAlternative::release_transcript() {
+  clear_has_transcript();
+  if (transcript_ == &::google::protobuf::internal::kEmptyString) {
+    return NULL;
+  } else {
+    ::std::string* temp = transcript_;
+    transcript_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString);
+    return temp;
+  }
+}
+
+// optional float confidence = 2;
+inline bool SpeechRecognitionAlternative::has_confidence() const {
+  return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void SpeechRecognitionAlternative::set_has_confidence() {
+  _has_bits_[0] |= 0x00000002u;
+}
+inline void SpeechRecognitionAlternative::clear_has_confidence() {
+  _has_bits_[0] &= ~0x00000002u;
+}
+inline void SpeechRecognitionAlternative::clear_confidence() {
+  confidence_ = 0;
+  clear_has_confidence();
+}
+inline float SpeechRecognitionAlternative::confidence() const {
+  return confidence_;
+}
+inline void SpeechRecognitionAlternative::set_confidence(float value) {
+  set_has_confidence();
+  confidence_ = value;
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+}  // namespace proto
+}  // namespace speech
+}  // namespace cobalt
+
+// @@protoc_insertion_point(global_scope)
+
+#endif  // PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED
diff --git a/src/cobalt/speech/google_streaming_api.proto b/src/cobalt/speech/google_streaming_api.proto
new file mode 100644
index 0000000..b9b5db8
--- /dev/null
+++ b/src/cobalt/speech/google_streaming_api.proto
@@ -0,0 +1,79 @@
+// 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.
+
+// Command to generate google_streaming_api.pb.h and google_streaming_api.pb.cc
+// protoc --proto_path=. --cpp_out=. google_streaming_api.proto
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+// TODO(hans): Commented out due to compilation errors.
+// option cc_api_version = 2;
+
+package cobalt.speech.proto;
+
+// SpeechRecognitionEvent is the only message type sent to client.
+//
+// The first SpeechRecognitionEvent is an empty (default) message to indicate
+// as early as possible that the stream connection has been established.
+message SpeechRecognitionEvent {
+  enum StatusCode {
+    // Note: in JavaScript API SpeechRecognitionError 0 is "OTHER" error.
+    STATUS_SUCCESS = 0;
+    STATUS_NO_SPEECH = 1;
+    STATUS_ABORTED = 2;
+    STATUS_AUDIO_CAPTURE = 3;
+    STATUS_NETWORK = 4;
+    STATUS_NOT_ALLOWED = 5;
+    STATUS_SERVICE_NOT_ALLOWED = 6;
+    STATUS_BAD_GRAMMAR = 7;
+    STATUS_LANGUAGE_NOT_SUPPORTED = 8;
+  }
+  optional StatusCode status = 1 [default = STATUS_SUCCESS];
+
+  // May contain zero or one final=true result (the newly settled portion).
+  // May also contain zero or more final=false results.
+  // (Note that this differs from JavaScript API resultHistory in that no more
+  // than one final=true result is returned, so client must accumulate
+  // resultHistory by concatenating the final=true results.)
+  repeated SpeechRecognitionResult result = 2;
+
+  enum EndpointerEventType {
+    START_OF_SPEECH = 0;
+    END_OF_SPEECH = 1;
+    END_OF_AUDIO = 2;  // End of audio stream has been reached.
+    // End of utterance indicates that no more speech segments are expected.
+    END_OF_UTTERANCE = 3;
+  }
+
+  optional EndpointerEventType endpoint = 4;
+};
+
+message SpeechRecognitionResult {
+  repeated SpeechRecognitionAlternative alternative = 1;
+
+  // True if this is the final time the speech service will return this
+  // particular SpeechRecognitionResult. If false, then this represents an
+  // interim result that could still be changed.
+  optional bool final = 2 [default = false];
+
+  // An estimate of the probability that the recognizer will not change its
+  // guess about this interim result.  Values range from 0.0 (completely
+  // unstable) to 1.0 (completely stable).  Note that this is not the same as
+  // "confidence", which estimate the probability that a recognition result
+  // is correct. This field is only provided for interim (final=false) results.
+  optional float stability = 3;
+};
+
+// Item in N-best list.
+message SpeechRecognitionAlternative {
+  // Spoken text.
+  optional string transcript = 1;
+
+  // The confidence estimate between 0.0 and 1.0.  A higher number means the
+  // system is more confident that the recognition is correct.
+  // This field is typically provided only for the top hypothesis and only for
+  // final results.
+  optional float confidence = 2;
+}
diff --git a/src/cobalt/speech/mic.h b/src/cobalt/speech/mic.h
deleted file mode 100644
index 4eac5e1..0000000
--- a/src/cobalt/speech/mic.h
+++ /dev/null
@@ -1,66 +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 "base/callback.h"
-#include "media/base/audio_bus.h"
-
-namespace cobalt {
-namespace speech {
-
-// An abstract class is used for interacting platform specific microphone.
-class Mic {
- public:
-  typedef ::media::AudioBus AudioBus;
-  typedef base::Callback<void(scoped_ptr<AudioBus>)> 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_;
-};
-
-}  // 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 5621116..0000000
--- a/src/cobalt/speech/mic_linux.cc
+++ /dev/null
@@ -1,42 +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));
-}
-
-}  // 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 1b164ba..0000000
--- a/src/cobalt/speech/mic_starboard.cc
+++ /dev/null
@@ -1,42 +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));
-}
-
-}  // 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 2025566..0000000
--- a/src/cobalt/speech/mic_win.cc
+++ /dev/null
@@ -1,42 +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));
-}
-
-}  // namespace speech
-}  // namespace cobalt
diff --git a/src/cobalt/speech/microphone.h b/src/cobalt/speech/microphone.h
new file mode 100644
index 0000000..4c0d87b
--- /dev/null
+++ b/src/cobalt/speech/microphone.h
@@ -0,0 +1,72 @@
+/*
+ * 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/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
+
+namespace cobalt {
+namespace speech {
+
+// An abstract class is used for interacting platform specific microphone.
+class Microphone {
+ public:
+  struct Options {
+    Options()
+        : enable_fake_microphone(false),
+          external_audio_data(NULL),
+          audio_data_size(0) {}
+
+    // Use fake microphone if this flag is set to true.
+    bool enable_fake_microphone;
+    // The following members are only applicable to the fake microphone.
+    //
+    // External audio input data for microphone input.
+    const uint8* external_audio_data;
+    // Audio data size.
+    int audio_data_size;
+    // Input file path.
+    base::optional<FilePath> file_path;
+  };
+
+  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..0dad830
--- /dev/null
+++ b/src/cobalt/speech/microphone_fake.cc
@@ -0,0 +1,189 @@
+/*
+ * 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 "cobalt/audio/audio_file_reader.h"
+#include "starboard/file.h"
+#include "starboard/memory.h"
+#include "starboard/time.h"
+
+namespace cobalt {
+namespace speech {
+
+namespace {
+const int kMaxBufferSize = 1024 * 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/300.
+const int kReadRange = 300;
+// 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(const Options& options)
+    : Microphone(),
+      read_data_from_file_(options.audio_data_size == 0),
+      file_length_(-1),
+      read_index_(0),
+      is_valid_(!ShouldFail(kCreationRange)) {
+  if (!is_valid_) {
+    SB_DLOG(WARNING) << "Mocking microphone creation failed.";
+    return;
+  }
+
+  if (read_data_from_file_) {
+    if (options.file_path) {
+      SB_DCHECK(!options.file_path->empty());
+      // External input file.
+      file_paths_.push_back(options.file_path.value());
+    } else {
+      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);
+      }
+    }
+  } else {
+    file_length_ = std::min(options.audio_data_size, kMaxBufferSize);
+    SB_DCHECK(file_length_ > 0);
+    file_buffer_.reset(new uint8[file_length_]);
+    SbMemoryCopy(file_buffer_.get(), options.external_audio_data, file_length_);
+  }
+}
+
+bool MicrophoneFake::Open() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (ShouldFail(kOpenRange)) {
+    SB_DLOG(WARNING) << "Mocking microphone open failed.";
+    return false;
+  }
+
+  if (read_data_from_file_) {
+    // Read from local files.
+    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);
+
+    scoped_array<char> audio_input(new char[file_buffer_size]);
+    int read_bytes = file.ReadAll(audio_input.get(), file_buffer_size);
+    if (read_bytes < 0) {
+      return false;
+    }
+
+    scoped_ptr<audio::AudioFileReader> reader(audio::AudioFileReader::TryCreate(
+        reinterpret_cast<const uint8*>(audio_input.get()), file_buffer_size,
+        audio::kSampleTypeInt16));
+    const float kSupportedSampleRate = 16000.0f;
+    const int kSupportedMonoChannel = 1;
+    if (!reader) {
+      // If it is not a WAV file, read audio data as raw audio.
+      file_buffer_.reset(new uint8[file_buffer_size]);
+      SbMemoryCopy(file_buffer_.get(), audio_input.get(), file_buffer_size);
+      file_length_ = file_buffer_size;
+    } else if (reader->sample_type() != ::media::ShellAudioBus::kInt16 ||
+               reader->sample_rate() != kSupportedSampleRate ||
+               reader->number_of_channels() != kSupportedMonoChannel) {
+      // If it is a WAV file but it doesn't meet the audio input criteria, treat
+      // it as an error.
+      return false;
+    } else {
+      // Read WAV PCM16 audio data.
+      int size =
+          static_cast<int>(reader->number_of_frames() *
+                           audio::GetSampleTypeSize(reader->sample_type()));
+      file_buffer_.reset(new uint8[size]);
+      SbMemoryCopy(file_buffer_.get(), reader->sample_data().get(), size);
+      file_length_ = size;
+    }
+  }
+  return true;
+}
+
+int MicrophoneFake::Read(char* out_data, int data_size) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  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() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (read_data_from_file_) {
+    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..068eca3
--- /dev/null
+++ b/src/cobalt/speech/microphone_fake.h
@@ -0,0 +1,64 @@
+/*
+ * 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/callback.h"
+#include "base/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.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:
+  explicit MicrophoneFake(const Options& options);
+  ~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:
+  base::ThreadChecker thread_checker_;
+
+  bool read_data_from_file_;
+  std::vector<FilePath> file_paths_;
+  scoped_array<uint8> 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..4414a77
--- /dev/null
+++ b/src/cobalt/speech/microphone_manager.cc
@@ -0,0 +1,165 @@
+/*
+ * 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"
+
+#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(
+    const DataReceivedCallback& data_received,
+    const CompletionCallback& completion, const ErrorCallback& error,
+    const MicrophoneCreator& microphone_creator)
+    : data_received_callback_(data_received),
+      completion_callback_(completion),
+      error_callback_(error),
+      microphone_creator_(microphone_creator),
+      state_(kStopped),
+      thread_("microphone_thread") {
+  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;
+  }
+
+  microphone_ = microphone_creator_.Run(kBufferSizeInBytes);
+  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);
+
+  int16_t samples[kBufferSizeInBytes / sizeof(int16_t)];
+  int read_bytes =
+      microphone_->Read(reinterpret_cast<char*>(samples), kBufferSizeInBytes);
+  // If |read_bytes| is zero, nothing should happen.
+  if (read_bytes > 0 && read_bytes % sizeof(int16_t) == 0) {
+    size_t frames = read_bytes / sizeof(int16_t);
+    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;
+  poll_mic_events_timer_ = base::nullopt;
+}
+
+}  // 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..08eaf6e
--- /dev/null
+++ b/src/cobalt/speech/microphone_manager.h
@@ -0,0 +1,88 @@
+/*
+ * 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;
+  typedef base::Callback<scoped_ptr<Microphone>(int)> MicrophoneCreator;
+
+  MicrophoneManager(const DataReceivedCallback& data_received,
+                    const CompletionCallback& completion,
+                    const ErrorCallback& error,
+                    const MicrophoneCreator& microphone_creator);
+
+  ~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();
+
+  const DataReceivedCallback data_received_callback_;
+  const CompletionCallback completion_callback_;
+  const ErrorCallback error_callback_;
+  const MicrophoneCreator microphone_creator_;
+
+  scoped_ptr<Microphone> 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/sandbox/audio_loader.cc b/src/cobalt/speech/sandbox/audio_loader.cc
new file mode 100644
index 0000000..1f83132
--- /dev/null
+++ b/src/cobalt/speech/sandbox/audio_loader.cc
@@ -0,0 +1,105 @@
+/*
+ * 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/sandbox/audio_loader.h"
+
+#include <vector>
+
+namespace cobalt {
+namespace speech {
+namespace sandbox {
+
+namespace {
+class DummyDecoder : public loader::Decoder {
+ public:
+  typedef base::Callback<void(const uint8*, int)> DoneCallback;
+
+  explicit DummyDecoder(const DoneCallback& done_callback)
+      : done_callback_(done_callback) {}
+  ~DummyDecoder() OVERRIDE {}
+
+  // This function is used for binding callback for creating DummyDecoder.
+  static scoped_ptr<Decoder> Create(const DoneCallback& done_callback) {
+    return scoped_ptr<Decoder>(new DummyDecoder(done_callback));
+  }
+
+  // From Decoder.
+  void DecodeChunk(const char* data, size_t size) OVERRIDE {
+    DCHECK(thread_checker_.CalledOnValidThread());
+
+    // Because we load data into memory, set a maximum buffer size.
+    const int kMaxBufferSize = 1024 * 1024;
+    if (buffer_.size() + size > kMaxBufferSize) {
+      return;
+    }
+    buffer_.insert(buffer_.end(), data, data + size);
+  }
+
+  void Finish() OVERRIDE {
+    DCHECK(thread_checker_.CalledOnValidThread());
+
+    if (buffer_.size() == 0) {
+      // No data loaded.
+      done_callback_.Run(NULL, 0);
+      return;
+    }
+
+    done_callback_.Run(reinterpret_cast<uint8*>(&buffer_[0]), buffer_.size());
+  }
+  bool Suspend() OVERRIDE {
+    NOTIMPLEMENTED();
+    return false;
+  }
+  void Resume(render_tree::ResourceProvider* /*resource_provider*/) OVERRIDE {
+    NOTIMPLEMENTED();
+  }
+
+ private:
+  base::ThreadChecker thread_checker_;
+  std::vector<char> buffer_;
+  DoneCallback done_callback_;
+};
+}  // namespace
+
+AudioLoader::AudioLoader(const GURL& url,
+                         network::NetworkModule* network_module,
+                         const DoneCallback& callback)
+    : network_module_(network_module), done_callback_(callback) {
+  DCHECK(url.is_valid());
+  DCHECK(!callback.is_null());
+
+  fetcher_factory_.reset(new loader::FetcherFactory(network_module_));
+  loader_ = make_scoped_ptr(new loader::Loader(
+      base::Bind(&loader::FetcherFactory::CreateFetcher,
+                 base::Unretained(fetcher_factory_.get()), url),
+      scoped_ptr<loader::Decoder>(new DummyDecoder(
+          base::Bind(&AudioLoader::OnLoadingDone, base::Unretained(this)))),
+      base::Bind(&AudioLoader::OnLoadingError, base::Unretained(this))));
+}
+
+AudioLoader::~AudioLoader() {}
+
+void AudioLoader::OnLoadingDone(const uint8* data, int size) {
+  done_callback_.Run(data, size);
+}
+
+void AudioLoader::OnLoadingError(const std::string& error) {
+  DLOG(WARNING) << "OnLoadingError with error message: " << error;
+}
+
+}  // namespace sandbox
+}  // namespace speech
+}  // namespace cobalt
diff --git a/src/cobalt/speech/sandbox/audio_loader.h b/src/cobalt/speech/sandbox/audio_loader.h
new file mode 100644
index 0000000..921713e
--- /dev/null
+++ b/src/cobalt/speech/sandbox/audio_loader.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_SANDBOX_AUDIO_LOADER_H_
+#define COBALT_SPEECH_SANDBOX_AUDIO_LOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/loader.h"
+#include "cobalt/network/network_module.h"
+
+namespace cobalt {
+namespace speech {
+namespace sandbox {
+
+class AudioLoader {
+ public:
+  typedef base::Callback<void(const uint8*, int)> DoneCallback;
+  AudioLoader(const GURL& url, network::NetworkModule* network_module,
+              const DoneCallback& callback);
+  ~AudioLoader();
+
+ private:
+  void OnLoadingDone(const uint8* data, int size);
+  void OnLoadingError(const std::string& error);
+
+  const DoneCallback done_callback_;
+  network::NetworkModule* network_module_;
+  scoped_ptr<loader::FetcherFactory> fetcher_factory_;
+  scoped_ptr<loader::Loader> loader_;
+};
+
+}  // namespace sandbox
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // COBALT_SPEECH_SANDBOX_AUDIO_LOADER_H_
diff --git a/src/cobalt/speech/sandbox/sandbox.gyp b/src/cobalt/speech/sandbox/sandbox.gyp
new file mode 100644
index 0000000..82f36b4
--- /dev/null
+++ b/src/cobalt/speech/sandbox/sandbox.gyp
@@ -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.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# Speech API.
+
+{
+  'targets': [
+    {
+      'target_name': 'speech_sandbox',
+      'type': '<(final_executable_type)',
+      'sources': [
+        'audio_loader.cc',
+        'audio_loader.h',
+        'speech_sandbox.cc',
+        'speech_sandbox.h',
+        'speech_sandbox_main.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/audio/audio.gyp:audio',
+        '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/cobalt/loader/loader.gyp:loader',
+        '<(DEPTH)/cobalt/network/network.gyp:network',
+        '<(DEPTH)/cobalt/script/engine.gyp:engine',
+        '<(DEPTH)/cobalt/speech/speech.gyp:speech',
+        '<(DEPTH)/cobalt/trace_event/trace_event.gyp:trace_event',
+        '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
+      ],
+    },
+
+    {
+      'target_name': 'speech_sandbox_deploy',
+      'type': 'none',
+      'dependencies': [
+        'speech_sandbox',
+      ],
+      'variables': {
+        'executable_name': 'speech_sandbox',
+      },
+      'includes': [ '../../../starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/cobalt/speech/sandbox/speech_sandbox.cc b/src/cobalt/speech/sandbox/speech_sandbox.cc
new file mode 100644
index 0000000..b0946b6
--- /dev/null
+++ b/src/cobalt/speech/sandbox/speech_sandbox.cc
@@ -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.
+ */
+
+#include "cobalt/speech/sandbox/speech_sandbox.h"
+
+#include "base/path_service.h"
+#include "cobalt/script/environment_settings.h"
+
+namespace cobalt {
+namespace speech {
+namespace sandbox {
+
+namespace {
+// The maximum number of element depth in the DOM tree. Elements at a level
+// deeper than this could be discarded, and will not be rendered.
+const int kDOMMaxElementDepth = 32;
+}  // namespace
+
+SpeechSandbox::SpeechSandbox(const std::string& file_path_string,
+                             const FilePath& trace_log_path) {
+  trace_to_file_.reset(new trace_event::ScopedTraceToFile(trace_log_path));
+
+  network::NetworkModule::Options network_options;
+  network_options.require_https = false;
+  network_module_.reset(new network::NetworkModule(network_options));
+
+  GURL url(file_path_string);
+  if (url.is_valid()) {
+    audio_loader_.reset(new AudioLoader(
+        url, network_module_.get(),
+        base::Bind(&SpeechSandbox::OnLoadingDone, base::Unretained(this))));
+  } else {
+    FilePath file_path(file_path_string);
+    if (!file_path.IsAbsolute()) {
+      FilePath exe_path;
+      PathService::Get(base::FILE_EXE, &exe_path);
+      DCHECK(exe_path.IsAbsolute());
+      std::string exe_path_string(exe_path.value());
+      std::size_t found = exe_path_string.find_last_of("/\\");
+      DCHECK_NE(found, std::string::npos);
+      // Find the executable directory. Using exe_path.DirName() doesn't work
+      // on Windows based platforms due to the path is mixed with "/" and "\".
+      exe_path_string = exe_path_string.substr(0, found);
+      file_path = FilePath(exe_path_string).Append(file_path_string);
+      DCHECK(file_path.IsAbsolute());
+    }
+
+    dom::DOMSettings::Options dom_settings_options;
+    dom_settings_options.microphone_options.enable_fake_microphone = true;
+    dom_settings_options.microphone_options.file_path = file_path;
+
+    StartRecognition(dom_settings_options);
+  }
+}
+
+SpeechSandbox::~SpeechSandbox() {
+  if (speech_recognition_) {
+    speech_recognition_->Stop();
+  }
+}
+
+void SpeechSandbox::StartRecognition(
+    const dom::DOMSettings::Options& dom_settings_options) {
+  scoped_ptr<script::EnvironmentSettings> environment_settings(
+      new dom::DOMSettings(kDOMMaxElementDepth, NULL, network_module_.get(),
+                           NULL, NULL, NULL, NULL, NULL, NULL,
+                           dom_settings_options));
+  DCHECK(environment_settings);
+
+  speech_recognition_ = new SpeechRecognition(environment_settings.get());
+  speech_recognition_->set_continuous(true);
+  speech_recognition_->set_interim_results(true);
+  speech_recognition_->Start(NULL);
+}
+
+void SpeechSandbox::OnLoadingDone(const uint8* data, int size) {
+  dom::DOMSettings::Options dom_settings_options;
+  dom_settings_options.microphone_options.enable_fake_microphone = true;
+  dom_settings_options.microphone_options.external_audio_data = data;
+  dom_settings_options.microphone_options.audio_data_size = size;
+
+  StartRecognition(dom_settings_options);
+}
+
+}  // namespace sandbox
+}  // namespace speech
+}  // namespace cobalt
diff --git a/src/cobalt/speech/sandbox/speech_sandbox.h b/src/cobalt/speech/sandbox/speech_sandbox.h
new file mode 100644
index 0000000..15bbd60
--- /dev/null
+++ b/src/cobalt/speech/sandbox/speech_sandbox.h
@@ -0,0 +1,60 @@
+/*
+ * 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_SANDBOX_SPEECH_SANDBOX_H_
+#define COBALT_SPEECH_SANDBOX_SPEECH_SANDBOX_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/threading/thread.h"
+#include "cobalt/dom/dom_settings.h"
+#include "cobalt/speech/sandbox/audio_loader.h"
+#include "cobalt/speech/speech_recognition.h"
+#include "cobalt/trace_event/scoped_trace_to_file.h"
+
+namespace cobalt {
+namespace speech {
+namespace sandbox {
+
+// This class is for speech sandbox application to experiment with voice search.
+// It takes a wav audio as audio input for a fake microphone, and starts/stops
+// speech recognition.
+class SpeechSandbox {
+ public:
+  // The constructor takes a file path string for an audio input and a log path
+  // for tracing.
+  SpeechSandbox(const std::string& file_path_string,
+                const FilePath& trace_log_path);
+  ~SpeechSandbox();
+
+ private:
+  void StartRecognition(const dom::DOMSettings::Options& dom_settings_options);
+  void OnLoadingDone(const uint8* data, int size);
+
+  scoped_ptr<trace_event::ScopedTraceToFile> trace_to_file_;
+  scoped_ptr<network::NetworkModule> network_module_;
+  scoped_ptr<AudioLoader> audio_loader_;
+  scoped_refptr<SpeechRecognition> speech_recognition_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(SpeechSandbox);
+};
+
+}  // namespace sandbox
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // COBALT_SPEECH_SANDBOX_SPEECH_SANDBOX_H_
diff --git a/src/cobalt/speech/sandbox/speech_sandbox_main.cc b/src/cobalt/speech/sandbox/speech_sandbox_main.cc
new file mode 100644
index 0000000..2cbb874
--- /dev/null
+++ b/src/cobalt/speech/sandbox/speech_sandbox_main.cc
@@ -0,0 +1,68 @@
+/*
+ * 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 "base/file_path.h"
+#include "base/path_service.h"
+#include "base/string_number_conversions.h"
+#include "cobalt/base/wrap_main.h"
+#include "cobalt/speech/sandbox/speech_sandbox.h"
+#include "googleurl/src/gurl.h"
+
+namespace cobalt {
+namespace speech {
+namespace sandbox {
+
+SpeechSandbox* g_speech_sandbox = NULL;
+
+// The application takes an audio url or path, and a timeout in second.
+// The timeout is optional. If it is not set or set to 0, the application
+// doesn't shut down.
+void StartApplication(int argc, char** argv, const char* /*link*/,
+                      const base::Closure& quit_closure) {
+  if (argc != 3 && argc != 2) {
+    LOG(ERROR) << "Usage: " << argv[0]
+               << " <audio url|path> [timeout in seconds]";
+    return;
+  }
+
+  int timeout = 0;
+  if (argc == 3) {
+    base::StringToInt(argv[2], &timeout);
+  }
+
+  DCHECK(!g_speech_sandbox);
+  g_speech_sandbox = new SpeechSandbox(
+      std::string(argv[1]),
+      FilePath(FILE_PATH_LITERAL("speech_sandbox_trace.json")));
+
+  if (timeout != 0) {
+    MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, quit_closure, base::TimeDelta::FromSeconds(timeout));
+  }
+}
+
+void StopApplication() {
+  DCHECK(g_speech_sandbox);
+  delete g_speech_sandbox;
+  g_speech_sandbox = NULL;
+}
+
+}  // namespace sandbox
+}  // namespace speech
+}  // namespace cobalt
+
+COBALT_WRAP_BASE_MAIN(cobalt::speech::sandbox::StartApplication,
+                      cobalt::speech::sandbox::StopApplication);
diff --git a/src/cobalt/speech/speech.gyp b/src/cobalt/speech/speech.gyp
index 83960c6..c016372 100644
--- a/src/cobalt/speech/speech.gyp
+++ b/src/cobalt/speech/speech.gyp
@@ -20,8 +20,23 @@
     {
       'target_name': 'speech',
       'type': 'static_library',
+      'msvs_disabled_warnings': [
+        # Dereferencing NULL pointer in generated file
+        # google_streaming_api.pb.cc.
+        6011,
+      ],
       'sources': [
-        'mic.h',
+        'audio_encoder_flac.cc',
+        'audio_encoder_flac.h',
+        'endpointer_delegate.cc',
+        'endpointer_delegate.h',
+        'google_streaming_api.pb.cc',
+        'google_streaming_api.pb.h',
+        'google_streaming_api.pb.proto',
+        'microphone.h',
+        'microphone_manager.cc',
+        'microphone_manager.h',
+        'speech_configuration.h',
         'speech_recognition.cc',
         'speech_recognition.h',
         'speech_recognition_alternative.cc',
@@ -43,18 +58,51 @@
       'dependencies': [
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/content/browser/speech/speech.gyp:speech',
+        '<(DEPTH)/third_party/flac/flac.gyp:libflac',
+        '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite',
+      ],
+      'include_dirs': [
+        # Get protobuf headers from the chromium tree.
+        '<(DEPTH)/third_party/protobuf/src',
       ],
       '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',
+            'url_fetcher_fake.cc',
+            'url_fetcher_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 90383ae..06deca0 100644
--- a/src/cobalt/speech/speech_recognition.cc
+++ b/src/cobalt/speech/speech_recognition.cc
@@ -28,15 +28,26 @@
 SpeechRecognition::SpeechRecognition(script::EnvironmentSettings* settings)
     : ALLOW_THIS_IN_INITIALIZER_LIST(
           manager_(base::polymorphic_downcast<dom::DOMSettings*>(settings)
-                       ->fetcher_factory())),
+                       ->network_module(),
+                   base::Bind(&SpeechRecognition::OnEventAvailable,
+                              base::Unretained(this)),
+                   base::polymorphic_downcast<dom::DOMSettings*>(settings)
+                       ->microphone_options())),
       config_("" /*lang*/, false /*continuous*/, false /*interim_results*/,
               1 /*max alternatives*/) {}
 
-void SpeechRecognition::Start() { manager_.Start(config_); }
+void SpeechRecognition::Start(script::ExceptionState* exception_state) {
+  manager_.Start(config_, exception_state);
+}
 
 void SpeechRecognition::Stop() { manager_.Stop(); }
 
-void SpeechRecognition::Abort() { NOTIMPLEMENTED(); }
+void SpeechRecognition::Abort() { manager_.Abort(); }
+
+bool SpeechRecognition::OnEventAvailable(
+    const scoped_refptr<dom::Event>& event) {
+  return DispatchEvent(event);
+}
 
 }  // namespace speech
 }  // namespace cobalt
diff --git a/src/cobalt/speech/speech_recognition.h b/src/cobalt/speech/speech_recognition.h
index 4622a9c..020229f 100644
--- a/src/cobalt/speech/speech_recognition.h
+++ b/src/cobalt/speech/speech_recognition.h
@@ -36,7 +36,7 @@
 
   // When the start method is called, it represents the moment in time the web
   // application wishes to begin recognition.
-  void Start();
+  void Start(script::ExceptionState* exception_state);
   // The stop method represents an instruction to the recognition service to
   // stop listening to more audio, and to try and return a result using just the
   // audio that it has already received for this recognition.
@@ -107,6 +107,9 @@
  private:
   ~SpeechRecognition() OVERRIDE {}
 
+  // Callback from recognition manager.
+  bool OnEventAvailable(const scoped_refptr<dom::Event>& event);
+
   // Handles main operations of speech recognition including audio encoding,
   // mic audio retrieving and audio data recognizing.
   SpeechRecognitionManager manager_;
diff --git a/src/cobalt/speech/speech_recognition_manager.cc b/src/cobalt/speech/speech_recognition_manager.cc
index 61b50c7..4eab7de 100644
--- a/src/cobalt/speech/speech_recognition_manager.cc
+++ b/src/cobalt/speech/speech_recognition_manager.cc
@@ -17,45 +17,154 @@
 #include "cobalt/speech/speech_recognition_manager.h"
 
 #include "base/bind.h"
+#include "cobalt/base/tokens.h"
+#include "cobalt/dom/dom_exception.h"
+#if defined(ENABLE_FAKE_MICROPHONE)
+#include "cobalt/speech/microphone_fake.h"
+#include "cobalt/speech/url_fetcher_fake.h"
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#include "cobalt/speech/microphone_manager.h"
+#if defined(SB_USE_SB_MICROPHONE)
+#include "cobalt/speech/microphone_starboard.h"
+#endif  // defined(SB_USE_SB_MICROPHONE)
+#include "net/url_request/url_fetcher.h"
 
 namespace cobalt {
 namespace speech {
 
 namespace {
 const int kSampleRate = 16000;
+const float kAudioPacketDurationInSeconds = 0.1f;
+
+scoped_ptr<net::URLFetcher> CreateURLFetcher(
+    const GURL& url, net::URLFetcher::RequestType request_type,
+    net::URLFetcherDelegate* delegate) {
+  return make_scoped_ptr<net::URLFetcher>(
+      net::URLFetcher::Create(url, request_type, delegate));
+}
+
+scoped_ptr<Microphone> CreateMicrophone(int buffer_size_bytes) {
+#if defined(SB_USE_SB_MICROPHONE)
+  return make_scoped_ptr<Microphone>(
+      new MicrophoneStarboard(kSampleRate, buffer_size_bytes));
+#else
+  UNREFERENCED_PARAMETER(buffer_size_bytes);
+  return scoped_ptr<Microphone>();
+#endif  // defined(SB_USE_SB_MICROPHONE)
+}
+
+#if defined(SB_USE_SB_MICROPHONE)
+#if defined(ENABLE_FAKE_MICROPHONE)
+scoped_ptr<net::URLFetcher> CreateFakeURLFetcher(
+    const GURL& url, net::URLFetcher::RequestType request_type,
+    net::URLFetcherDelegate* delegate) {
+  return make_scoped_ptr<net::URLFetcher>(
+      new URLFetcherFake(url, request_type, delegate));
+}
+
+scoped_ptr<Microphone> CreateFakeMicrophone(const Microphone::Options& options,
+                                            int /*buffer_size_bytes*/) {
+  return make_scoped_ptr<Microphone>(new MicrophoneFake(options));
+}
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#endif  // defined(SB_USE_SB_MICROPHONE)
+
 }  // namespace
 
 SpeechRecognitionManager::SpeechRecognitionManager(
-    loader::FetcherFactory* fetcher_factory)
+    network::NetworkModule* network_module, const EventCallback& event_callback,
+    const Microphone::Options& microphone_options)
     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       weak_this_(weak_ptr_factory_.GetWeakPtr()),
       main_message_loop_(base::MessageLoopProxy::current()),
-      recognizer_(fetcher_factory),
-      ALLOW_THIS_IN_INITIALIZER_LIST(mic_(Mic::Create(
-          kSampleRate, base::Bind(&SpeechRecognitionManager::OnDataReceived,
-                                  base::Unretained(this)),
-          base::Bind(&SpeechRecognitionManager::OnDataCompletion,
-                     base::Unretained(this)),
-          base::Bind(&SpeechRecognitionManager::OnError,
-                     base::Unretained(this))))) {}
+      event_callback_(event_callback),
+      endpointer_delegate_(kSampleRate),
+      state_(kStopped) {
+  UNREFERENCED_PARAMETER(microphone_options);
+
+  SpeechRecognizer::URLFetcherCreator url_fetcher_creator =
+      base::Bind(&CreateURLFetcher);
+  MicrophoneManager::MicrophoneCreator microphone_creator =
+      base::Bind(&CreateMicrophone);
+
+#if defined(SB_USE_SB_MICROPHONE)
+#if defined(ENABLE_FAKE_MICROPHONE)
+  if (microphone_options.enable_fake_microphone) {
+    // If fake microphone is enabled, fake URL fetchers should be enabled as
+    // well.
+    url_fetcher_creator = base::Bind(&CreateFakeURLFetcher);
+    microphone_creator = base::Bind(&CreateFakeMicrophone, microphone_options);
+  }
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#endif  // defined(SB_USE_SB_MICROPHONE)
+
+  recognizer_.reset(new SpeechRecognizer(
+      network_module, base::Bind(&SpeechRecognitionManager::OnRecognizerEvent,
+                                 base::Unretained(this)),
+      url_fetcher_creator));
+  microphone_manager_.reset(new MicrophoneManager(
+      base::Bind(&SpeechRecognitionManager::OnDataReceived,
+                 base::Unretained(this)),
+      base::Bind(&SpeechRecognitionManager::OnDataCompletion,
+                 base::Unretained(this)),
+      base::Bind(&SpeechRecognitionManager::OnMicError, base::Unretained(this)),
+      microphone_creator));
+}
 
 SpeechRecognitionManager::~SpeechRecognitionManager() { Stop(); }
 
-void SpeechRecognitionManager::Start(const SpeechRecognitionConfig& config) {
+void SpeechRecognitionManager::Start(const SpeechRecognitionConfig& config,
+                                     script::ExceptionState* exception_state) {
   DCHECK(main_message_loop_->BelongsToCurrentThread());
 
-  recognizer_.Start(config);
-  mic_->Start();
+  // If the start method is called on an already started object, the user agent
+  // MUST throw an InvalidStateError exception and ignore the call.
+  if (state_ == kStarted) {
+    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+                             exception_state);
+    return;
+  }
+
+  recognizer_->Start(config, kSampleRate);
+  microphone_manager_->Open();
+  endpointer_delegate_.Start();
+  state_ = kStarted;
 }
 
 void SpeechRecognitionManager::Stop() {
   DCHECK(main_message_loop_->BelongsToCurrentThread());
 
-  mic_->Stop();
-  recognizer_.Stop();
+  // If the stop method is called on an object which is already stopped or being
+  // stopped, the user agent MUST ignore the call.
+  if (state_ != kStarted) {
+    return;
+  }
+
+  endpointer_delegate_.Stop();
+  microphone_manager_->Close();
+  recognizer_->Stop();
+  state_ = kStopped;
+  event_callback_.Run(new dom::Event(base::Tokens::soundend()));
 }
 
-void SpeechRecognitionManager::OnDataReceived(scoped_ptr<AudioBus> audio_bus) {
+void SpeechRecognitionManager::Abort() {
+  DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+  // If the abort method is called on an object which is already stopped or
+  // aborting, the user agent MUST ignore the call.
+  if (state_ != kStarted) {
+    return;
+  }
+
+  endpointer_delegate_.Stop();
+  microphone_manager_->Close();
+  recognizer_->Stop();
+  state_ = kAborted;
+  event_callback_.Run(new dom::Event(base::Tokens::soundend()));
+}
+
+void SpeechRecognitionManager::OnDataReceived(
+    scoped_ptr<ShellAudioBus> audio_bus) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
     // Called from mic thread.
     main_message_loop_->PostTask(
@@ -64,9 +173,13 @@
     return;
   }
 
-  // TODO: Encode audio data, and then send it to recognizer. After
-  // receiving the recognition result from recognizer, fire a speech recognition
-  // event.
+  // 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);
+  }
 }
 
 void SpeechRecognitionManager::OnDataCompletion() {
@@ -78,19 +191,52 @@
     return;
   }
 
-  // TODO: Handle the case that no audio data would be received
-  // afterwards.
+  // Stop recognizing if in the abort state.
+  if (state_ != kAborted) {
+    // The encoder requires a non-empty final buffer, so encoding a packet of
+    // silence at the end in case encoder had no data already.
+    size_t dummy_frames =
+        static_cast<size_t>(kSampleRate * kAudioPacketDurationInSeconds);
+    scoped_ptr<ShellAudioBus> dummy_audio_bus(new ShellAudioBus(
+        1, dummy_frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+    dummy_audio_bus->ZeroAllFrames();
+    recognizer_->RecognizeAudio(dummy_audio_bus.Pass(), true);
+  }
 }
 
-void SpeechRecognitionManager::OnError() {
+void SpeechRecognitionManager::OnRecognizerEvent(
+    const scoped_refptr<dom::Event>& event) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
-    // Called from mic thread.
+    // Called from recognizer thread.
     main_message_loop_->PostTask(
-        FROM_HERE, base::Bind(&SpeechRecognitionManager::OnError, weak_this_));
+        FROM_HERE, base::Bind(&SpeechRecognitionManager::OnRecognizerEvent,
+                              weak_this_, event));
     return;
   }
 
-  // TODO: Handle the case that an error occurred.
+  // Do not return any information if in the abort state.
+  if (state_ != kAborted) {
+    event_callback_.Run(event);
+  }
+}
+
+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_, event));
+    return;
+  }
+
+  event_callback_.Run(event);
+
+  // 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 022d82c..2d622dd 100644
--- a/src/cobalt/speech/speech_recognition_manager.h
+++ b/src/cobalt/speech/speech_recognition_manager.h
@@ -19,14 +19,22 @@
 
 #include <string>
 
-#include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/speech/mic.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/exception_state.h"
+#include "cobalt/speech/endpointer_delegate.h"
+#include "cobalt/speech/microphone.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"
 #include "cobalt/speech/speech_recognizer.h"
+#include "media/base/shell_audio_bus.h"
 
 namespace cobalt {
 namespace speech {
 
+class MicrophoneManager;
+
 // Owned by SpeechRecognition to manage major speech recognition logic.
 // This class interacts with microphone, speech recognition service and audio
 // encoder. It provides the interface to start/stop microphone and
@@ -34,30 +42,52 @@
 // class would encode the audio data, then send it to recogniton service.
 class SpeechRecognitionManager {
  public:
-  typedef ::media::AudioBus AudioBus;
+  typedef ::media::ShellAudioBus ShellAudioBus;
+  typedef base::Callback<bool(const scoped_refptr<dom::Event>&)> EventCallback;
 
-  explicit SpeechRecognitionManager(loader::FetcherFactory* fetcher_factory);
+  SpeechRecognitionManager(network::NetworkModule* network_module,
+                           const EventCallback& event_callback,
+                           const Microphone::Options& microphone_options);
   ~SpeechRecognitionManager();
 
   // Start/Stop speech recognizer and microphone. Multiple calls would be
   // managed by their own class.
-  void Start(const SpeechRecognitionConfig& config);
+  void Start(const SpeechRecognitionConfig& config,
+             script::ExceptionState* exception_state);
   void Stop();
+  void Abort();
 
  private:
+  enum State {
+    kStarted,
+    kStopped,
+    kAborted,
+  };
+
   // Callbacks from mic.
-  void OnDataReceived(scoped_ptr<AudioBus> audio_bus);
+  void OnDataReceived(scoped_ptr<ShellAudioBus> audio_bus);
   void OnDataCompletion();
-  void OnError();
+  void OnMicError(const scoped_refptr<dom::Event>& event);
+
+  // Callbacks from recognizer.
+  void OnRecognizerEvent(const scoped_refptr<dom::Event>& event);
 
   base::WeakPtrFactory<SpeechRecognitionManager> weak_ptr_factory_;
   // We construct a WeakPtr upon SpeechRecognitionManager's construction in
   // order to associate the WeakPtr with the constructing thread.
   base::WeakPtr<SpeechRecognitionManager> weak_this_;
-
   scoped_refptr<base::MessageLoopProxy> const main_message_loop_;
-  SpeechRecognizer recognizer_;
-  scoped_ptr<Mic> mic_;
+
+  // Callback for sending dom events if available.
+  EventCallback event_callback_;
+  scoped_ptr<SpeechRecognizer> recognizer_;
+
+  scoped_ptr<MicrophoneManager> microphone_manager_;
+
+  // Delegate of endpointer which is used for detecting sound energy.
+  EndPointerDelegate endpointer_delegate_;
+
+  State state_;
 };
 
 }  // namespace speech
diff --git a/src/cobalt/speech/speech_recognition_result.h b/src/cobalt/speech/speech_recognition_result.h
index 6ebb193..a7432b1 100644
--- a/src/cobalt/speech/speech_recognition_result.h
+++ b/src/cobalt/speech/speech_recognition_result.h
@@ -49,7 +49,7 @@
   // The final MUST be set to true if this is the final time the speech service
   // will return this particular index value. If the value is false, then this
   // represents an interim result that could still be changed.
-  bool final() const { return final_; }
+  bool is_final() const { return final_; }
 
   DEFINE_WRAPPABLE_TYPE(SpeechRecognitionResult);
 
diff --git a/src/cobalt/speech/speech_recognizer.cc b/src/cobalt/speech/speech_recognizer.cc
index 6355ae7..17806b8 100644
--- a/src/cobalt/speech/speech_recognizer.cc
+++ b/src/cobalt/speech/speech_recognizer.cc
@@ -17,26 +17,176 @@
 #include "cobalt/speech/speech_recognizer.h"
 
 #include "base/bind.h"
+#include "base/rand_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "cobalt/deprecated/platform_delegate.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/speech/google_streaming_api.pb.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/system.h"
+#endif  // defined(SB_USE_SB_MICROPHONE)
 
 namespace cobalt {
 namespace speech {
 
-SpeechRecognizer::SpeechRecognizer(loader::FetcherFactory* fetcher_factory)
-    : fetcher_factory_(fetcher_factory),
-      thread_("speech_recognizer"),
-      started_(false) {
+namespace {
+const char kBaseStreamURL[] =
+    "https://www.google.com/speech-api/full-duplex/v1";
+const char kUp[] = "up";
+const char kDown[] = "down";
+const char kClient[] = "com.speech.tv";
+
+GURL AppendPath(const GURL& url, const std::string& value) {
+  std::string path(url.path());
+
+  if (!path.empty()) path += "/";
+
+  path += net::EscapePath(value);
+  GURL::Replacements replacements;
+  replacements.SetPathStr(path);
+  return url.ReplaceComponents(replacements);
+}
+
+GURL AppendQueryParameter(const GURL& url, const std::string& new_query,
+                          const std::string& value) {
+  std::string query(url.query());
+
+  if (!query.empty()) query += "&";
+
+  query += net::EscapeQueryParamValue(new_query, true);
+
+  if (!value.empty()) {
+    query += "=" + net::EscapeQueryParamValue(value, true);
+  }
+
+  GURL::Replacements replacements;
+  replacements.SetQueryStr(query);
+  return url.ReplaceComponents(replacements);
+}
+
+SpeechRecognitionResultList::SpeechRecognitionResults
+ProcessProtoSuccessResults(const proto::SpeechRecognitionEvent& event) {
+  DCHECK_EQ(event.status(), proto::SpeechRecognitionEvent::STATUS_SUCCESS);
+
+  SpeechRecognitionResultList::SpeechRecognitionResults results;
+  for (int i = 0; i < event.result_size(); ++i) {
+    SpeechRecognitionResult::SpeechRecognitionAlternatives alternatives;
+    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(proto_alternative.transcript(),
+                                           confidence));
+      alternatives.push_back(alternative);
+    }
+
+    bool final = proto_result.has_final() && proto_result.final();
+    scoped_refptr<SpeechRecognitionResult> recognition_result(
+        new SpeechRecognitionResult(alternatives, final));
+    results.push_back(recognition_result);
+  }
+  return results;
+}
+
+// TODO: Feed error messages when creating SpeechRecognitionError.
+void ProcessAndFireErrorEvent(
+    const proto::SpeechRecognitionEvent& event,
+    const SpeechRecognizer::EventCallback& event_callback) {
+  scoped_refptr<dom::Event> error_event;
+  switch (event.status()) {
+    case proto::SpeechRecognitionEvent::STATUS_SUCCESS:
+      NOTREACHED();
+      return;
+    case proto::SpeechRecognitionEvent::STATUS_NO_SPEECH:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kNoSpeech, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_ABORTED:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kAborted, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kAudioCapture, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_NETWORK:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kNetwork, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_NOT_ALLOWED:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kNotAllowed, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED:
+      error_event = new SpeechRecognitionError(
+          SpeechRecognitionError::kServiceNotAllowed, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_BAD_GRAMMAR:
+      error_event =
+          new SpeechRecognitionError(SpeechRecognitionError::kBadGrammar, "");
+      break;
+    case proto::SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED:
+      error_event = new SpeechRecognitionError(
+          SpeechRecognitionError::kLanguageNotSupported, "");
+      break;
+  }
+
+  DCHECK(error_event);
+  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,
+                                   const EventCallback& event_callback,
+                                   const URLFetcherCreator& fetcher_creator)
+    : network_module_(network_module),
+      started_(false),
+      event_callback_(event_callback),
+      fetcher_creator_(fetcher_creator),
+      thread_("speech_recognizer") {
   thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0));
 }
 
 SpeechRecognizer::~SpeechRecognizer() {
   Stop();
+  // Stopping the thread here to ensure that StopInternal has completed before
+  // we finish running the destructor.
+  thread_.Stop();
 }
 
-void SpeechRecognizer::Start(const SpeechRecognitionConfig& config) {
+void SpeechRecognizer::Start(const SpeechRecognitionConfig& config,
+                             int sample_rate) {
   // Called by the speech recognition manager thread.
-  thread_.message_loop()->PostTask(FROM_HERE,
-                                   base::Bind(&SpeechRecognizer::StartInternal,
-                                              base::Unretained(this), config));
+  thread_.message_loop()->PostTask(
+      FROM_HERE, base::Bind(&SpeechRecognizer::StartInternal,
+                            base::Unretained(this), config, sample_rate));
 }
 
 void SpeechRecognizer::Stop() {
@@ -46,69 +196,195 @@
       base::Bind(&SpeechRecognizer::StopInternal, base::Unretained(this)));
 }
 
-void SpeechRecognizer::RecognizeAudio(scoped_array<uint8> encoded_audio_data,
-                                      size_t size, bool is_last_chunk) {
+void SpeechRecognizer::RecognizeAudio(scoped_ptr<ShellAudioBus> audio_bus,
+                                      bool is_last_chunk) {
   // Called by the speech recognition manager thread.
   thread_.message_loop()->PostTask(
-      FROM_HERE,
-      base::Bind(&SpeechRecognizer::UploadAudioDataInternal,
-                 base::Unretained(this), base::Passed(&encoded_audio_data),
-                 size, is_last_chunk));
+      FROM_HERE, base::Bind(&SpeechRecognizer::UploadAudioDataInternal,
+                            base::Unretained(this), base::Passed(&audio_bus),
+                            is_last_chunk));
 }
 
 void SpeechRecognizer::OnURLFetchDownloadData(
     const net::URLFetcher* source, scoped_ptr<std::string> download_data) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
 
-  // TODO: process the download data.
-  NOTIMPLEMENTED();
+  const net::URLRequestStatus& status = source->GetStatus();
+  const int response_code = source->GetResponseCode();
 
-  UNREFERENCED_PARAMETER(source);
-  UNREFERENCED_PARAMETER(download_data);
+  if (source == downstream_fetcher_.get()) {
+    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;
+        }
+
+        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."));
+    }
+  }
 }
 
 void SpeechRecognizer::OnURLFetchComplete(const net::URLFetcher* source) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
   UNREFERENCED_PARAMETER(source);
+  // no-op.
 }
 
-void SpeechRecognizer::StartInternal(const SpeechRecognitionConfig& config) {
+void SpeechRecognizer::StartInternal(const SpeechRecognitionConfig& config,
+                                     int sample_rate) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
   if (started_) {
     // Recognizer is already started.
     return;
   }
   started_ = true;
 
-  // TODO: set up url fetchers with this URLFetcherDelegate.
-  NOTIMPLEMENTED();
+  encoder_.reset(new AudioEncoderFlac(sample_rate));
 
-  UNREFERENCED_PARAMETER(config);
-  UNREFERENCED_PARAMETER(fetcher_factory_);
+  // Required for streaming on both up and down connections.
+  std::string pair = base::Uint64ToString(base::RandUint64());
+
+  // Set up down stream first.
+  GURL down_url(kBaseStreamURL);
+  down_url = AppendPath(down_url, kDown);
+  down_url = AppendQueryParameter(down_url, "pair", pair);
+  // Use protobuffer as the output format.
+  down_url = AppendQueryParameter(down_url, "output", "pb");
+
+  downstream_fetcher_ =
+      fetcher_creator_.Run(down_url, net::URLFetcher::GET, this);
+  downstream_fetcher_->SetRequestContext(
+      network_module_->url_request_context_getter());
+  downstream_fetcher_->Start();
+
+  // Up stream.
+  GURL up_url(kBaseStreamURL);
+  up_url = AppendPath(up_url, kUp);
+  up_url = AppendQueryParameter(up_url, "client", kClient);
+  up_url = AppendQueryParameter(up_url, "pair", pair);
+  up_url = AppendQueryParameter(up_url, "output", "pb");
+
+  const char* speech_api_key = "";
+#if defined(OS_STARBOARD)
+#if SB_VERSION(2)
+  const int kSpeechApiKeyLength = 100;
+  char buffer[kSpeechApiKeyLength] = {0};
+  bool result = SbSystemGetProperty(kSbSystemPropertySpeechApiKey, buffer,
+                                    SB_ARRAY_SIZE_INT(buffer));
+  SB_DCHECK(result);
+  speech_api_key = result ? buffer : "";
+#endif  // SB_VERSION(2)
+#endif  // defined(OS_STARBOARD)
+
+  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()) {
+    up_url = AppendQueryParameter(up_url, "lang", config.lang);
+  } else {
+    up_url = AppendQueryParameter(
+        up_url, "lang",
+        cobalt::deprecated::PlatformDelegate::Get()->GetSystemLanguage());
+  }
+
+  if (config.max_alternatives) {
+    up_url = AppendQueryParameter(up_url, "maxAlternatives",
+                                  base::UintToString(config.max_alternatives));
+  }
+
+  if (config.continuous) {
+    up_url = AppendQueryParameter(up_url, "continuous", "");
+  }
+  if (config.interim_results) {
+    up_url = AppendQueryParameter(up_url, "interim", "");
+  }
+
+  upstream_fetcher_ = fetcher_creator_.Run(up_url, net::URLFetcher::POST, this);
+  upstream_fetcher_->SetRequestContext(
+      network_module_->url_request_context_getter());
+  upstream_fetcher_->SetChunkedUpload(encoder_->GetMimeType());
+  upstream_fetcher_->Start();
 }
 
 void SpeechRecognizer::StopInternal() {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+
   if (!started_) {
     // Recognizer is not started.
     return;
   }
   started_ = false;
 
-  // TODO: terminate url fetchers.
-  NOTIMPLEMENTED();
+  upstream_fetcher_.reset();
+  downstream_fetcher_.reset();
+  encoder_.reset();
+
+  // Clear the final results.
+  final_results_.clear();
+  // Clear any remaining audio data.
+  chunked_byte_buffer_.Clear();
 }
 
 void SpeechRecognizer::UploadAudioDataInternal(
-    scoped_array<uint8> encoded_audio_data, size_t size, bool is_last_chunk) {
+    scoped_ptr<ShellAudioBus> audio_bus, bool is_last_chunk) {
+  DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
+  DCHECK(audio_bus);
+
+  std::string encoded_audio_data;
+  if (encoder_) {
+    encoder_->Encode(audio_bus.get());
+    if (is_last_chunk) {
+      encoder_->Finish();
+    }
+    encoded_audio_data = encoder_->GetAndClearAvailableEncodedData();
+  }
+
+  if (upstream_fetcher_ && !encoded_audio_data.empty()) {
+    upstream_fetcher_->AppendChunkToUpload(encoded_audio_data, is_last_chunk);
+  }
+}
+
+void SpeechRecognizer::ProcessAndFireSuccessEvent(
+    const SpeechRecognitionResults& new_results) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
 
-  // TODO: upload encoded audio data chunk.
-  NOTIMPLEMENTED();
+  SpeechRecognitionResults success_results;
+  size_t total_size = final_results_.size() + new_results.size();
+  success_results.reserve(total_size);
+  success_results = final_results_;
+  success_results.insert(success_results.end(), new_results.begin(),
+                         new_results.end());
 
-  UNREFERENCED_PARAMETER(encoded_audio_data);
-  UNREFERENCED_PARAMETER(size);
-  UNREFERENCED_PARAMETER(is_last_chunk);
+  size_t result_index = final_results_.size();
+  // Update final results list.
+  for (size_t i = 0; i < new_results.size(); ++i) {
+    if (new_results[i]->is_final()) {
+      final_results_.push_back(new_results[i]);
+    }
+  }
+
+  scoped_refptr<SpeechRecognitionResultList> recognition_list(
+      new SpeechRecognitionResultList(success_results));
+  scoped_refptr<SpeechRecognitionEvent> recognition_event(
+      new SpeechRecognitionEvent(SpeechRecognitionEvent::kResult,
+                                 static_cast<uint32>(result_index),
+                                 recognition_list));
+  event_callback_.Run(recognition_event);
 }
 
 }  // namespace speech
diff --git a/src/cobalt/speech/speech_recognizer.h b/src/cobalt/speech/speech_recognizer.h
index c1900c7..5dac2c6 100644
--- a/src/cobalt/speech/speech_recognizer.h
+++ b/src/cobalt/speech/speech_recognizer.h
@@ -18,17 +18,23 @@
 #define COBALT_SPEECH_SPEECH_RECOGNIZER_H_
 
 #include <string>
+#include <vector>
 
 #include "base/threading/thread.h"
-#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/speech/audio_encoder_flac.h"
 #include "cobalt/speech/speech_recognition_config.h"
+#include "cobalt/speech/speech_recognition_event.h"
+#include "content/browser/speech/chunked_byte_buffer.h"
+#include "media/base/audio_bus.h"
+#include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_fetcher_delegate.h"
 
 namespace cobalt {
 namespace speech {
 
-// Interacts with Google speech recogniton service, and then parses recognition
-// results and forms speech recogniton event.
+// Interacts with Google speech recognition service, and then parses recognition
+// results and forms speech recognition event.
 // It creates an upstream fetcher to upload the encoded audio and a downstream
 // fetcher to fetch the speech recognition result. The fetched speech
 // recognition result is parsed by JSON parser and a SpeechRecognitionEvent,
@@ -36,18 +42,27 @@
 // manager.
 class SpeechRecognizer : public net::URLFetcherDelegate {
  public:
-  explicit SpeechRecognizer(loader::FetcherFactory* fetcher_factory);
+  typedef ::media::ShellAudioBus ShellAudioBus;
+  typedef base::Callback<void(const scoped_refptr<dom::Event>&)> EventCallback;
+  typedef SpeechRecognitionResultList::SpeechRecognitionResults
+      SpeechRecognitionResults;
+  typedef base::Callback<scoped_ptr<net::URLFetcher>(
+      const GURL&, net::URLFetcher::RequestType, net::URLFetcherDelegate*)>
+      URLFetcherCreator;
+
+  SpeechRecognizer(network::NetworkModule* network_module,
+                   const EventCallback& event_callback,
+                   const URLFetcherCreator& fetcher_creator);
   ~SpeechRecognizer() OVERRIDE;
 
   // Multiple calls to Start/Stop are allowed, the implementation should take
   // care of multiple calls.
   // Start speech recognizer.
-  void Start(const SpeechRecognitionConfig& config);
+  void Start(const SpeechRecognitionConfig& config, int sample_rate);
   // Stop speech recognizer.
   void Stop();
   // An encoded audio data is available and ready to be recognized.
-  void RecognizeAudio(scoped_array<uint8> encoded_audio_data, size_t size,
-                      bool is_last_chunk);
+  void RecognizeAudio(scoped_ptr<ShellAudioBus> audio_bus, bool is_last_chunk);
 
   // net::URLFetcherDelegate interface
   void OnURLFetchDownloadData(const net::URLFetcher* source,
@@ -58,18 +73,33 @@
                                 int64 /*current*/, int64 /*total*/) OVERRIDE {}
 
  private:
-  void StartInternal(const SpeechRecognitionConfig& config);
+  void StartInternal(const SpeechRecognitionConfig& config, int sample_rate);
   void StopInternal();
-
-  void UploadAudioDataInternal(scoped_array<uint8> encoded_audio_data,
-                               size_t size, bool is_last_chunk);
+  void UploadAudioDataInternal(scoped_ptr<ShellAudioBus> audio_bus,
+                               bool is_last_chunk);
+  void ProcessAndFireSuccessEvent(const SpeechRecognitionResults& new_results);
 
   // This is used for creating fetchers.
-  loader::FetcherFactory* fetcher_factory_;
-  // Speech recognizer is operating in its own thread.
-  base::Thread thread_;
+  network::NetworkModule* network_module_;
   // Track the start/stop state of speech recognizer.
   bool started_;
+
+  // Encoder for encoding raw audio data to flac codec.
+  scoped_ptr<AudioEncoderFlac> encoder_;
+  // Fetcher for posting the audio data.
+  scoped_ptr<net::URLFetcher> upstream_fetcher_;
+  // Fetcher for receiving the streaming results.
+  scoped_ptr<net::URLFetcher> downstream_fetcher_;
+  // Used to send speech recognition event.
+  const EventCallback event_callback_;
+  // Used to create url fetcher.
+  const URLFetcherCreator fetcher_creator_;
+  // Used for processing proto buffer data.
+  content::ChunkedByteBuffer chunked_byte_buffer_;
+  // Used for accumulating final results.
+  SpeechRecognitionResults final_results_;
+  // Speech recognizer is operating in its own thread.
+  base::Thread thread_;
 };
 
 }  // namespace speech
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/speech/url_fetcher_fake.cc b/src/cobalt/speech/url_fetcher_fake.cc
new file mode 100644
index 0000000..2d621cb
--- /dev/null
+++ b/src/cobalt/speech/url_fetcher_fake.cc
@@ -0,0 +1,176 @@
+/*
+ * 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/url_fetcher_fake.h"
+
+#if defined(ENABLE_FAKE_MICROPHONE)
+
+#include "base/basictypes.h"
+#include "base/sys_byteorder.h"
+#include "cobalt/speech/google_streaming_api.pb.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace cobalt {
+namespace speech {
+namespace {
+const int kDownloadTimerInterval = 100;
+
+struct RecognitionAlternative {
+  const char* transcript;
+  float confidence;
+};
+
+const RecognitionAlternative kAlternatives_1[] = {
+    {"tube", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_2[] = {
+    {"to be", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_3[] = {
+    {" or not", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_4[] = {
+    {" or not to be", 0.0f}, {"to be", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_5[] = {
+    {"to be or not to be", 0.728f}, {"2 B or not to be", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_6[] = {
+    {"that", 0.0f}, {"is the", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_7[] = {
+    {"that", 0.0f}, {"is the question", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_8[] = {
+    {"that is", 0.0f}, {"the question", 0.0f},
+};
+
+const RecognitionAlternative kAlternatives_9[] = {
+    {"that is the question", 0.8577f}, {"that is a question", 0.0f},
+};
+
+struct RecognitionResult {
+  const RecognitionAlternative* alternatives;
+  bool final;
+  size_t number_of_alternatives;
+};
+
+const RecognitionResult kRecognitionResults[] = {
+    {kAlternatives_1, false, SB_ARRAY_SIZE_INT(kAlternatives_1)},
+    {kAlternatives_2, false, SB_ARRAY_SIZE_INT(kAlternatives_2)},
+    {kAlternatives_3, false, SB_ARRAY_SIZE_INT(kAlternatives_3)},
+    {kAlternatives_4, false, SB_ARRAY_SIZE_INT(kAlternatives_4)},
+    {kAlternatives_5, true, SB_ARRAY_SIZE_INT(kAlternatives_5)},
+    {kAlternatives_6, false, SB_ARRAY_SIZE_INT(kAlternatives_6)},
+    {kAlternatives_7, false, SB_ARRAY_SIZE_INT(kAlternatives_7)},
+    {kAlternatives_8, false, SB_ARRAY_SIZE_INT(kAlternatives_8)},
+    {kAlternatives_9, true, SB_ARRAY_SIZE_INT(kAlternatives_9)},
+};
+
+std::string GetMockProtoResult(int index) {
+  proto::SpeechRecognitionEvent proto_event;
+  proto_event.set_status(proto::SpeechRecognitionEvent::STATUS_SUCCESS);
+  proto::SpeechRecognitionResult* proto_result = proto_event.add_result();
+  proto_result->set_final(kRecognitionResults[index].final);
+  const RecognitionAlternative* recognition_alternatives =
+      kRecognitionResults[index].alternatives;
+
+  for (size_t i = 0; i < kRecognitionResults[index].number_of_alternatives;
+       ++i) {
+    proto::SpeechRecognitionAlternative* proto_alternative =
+        proto_result->add_alternative();
+    proto_alternative->set_confidence(recognition_alternatives[i].confidence);
+    proto_alternative->set_transcript(recognition_alternatives[i].transcript);
+  }
+
+  std::string response_string;
+  proto_event.SerializeToString(&response_string);
+
+  // Prepend 4 byte prefix length indication to the protobuf message as
+  // envisaged by the google streaming recognition webservice protocol.
+  uint32_t prefix =
+      base::HostToNet32(static_cast<uint32_t>(response_string.size()));
+  response_string.insert(0, reinterpret_cast<char*>(&prefix), sizeof(prefix));
+  return response_string;
+}
+
+}  // namespace
+
+URLFetcherFake::URLFetcherFake(const GURL& url,
+                               net::URLFetcher::RequestType /*request_type*/,
+                               net::URLFetcherDelegate* delegate)
+    : original_url_(url),
+      delegate_(delegate),
+      is_chunked_upload_(false),
+      download_index_(0) {}
+
+URLFetcherFake::~URLFetcherFake() {}
+
+void URLFetcherFake::SetChunkedUpload(
+    const std::string& /*upload_content_type*/) {
+  is_chunked_upload_ = true;
+}
+
+void URLFetcherFake::AppendChunkToUpload(const std::string& /*data*/,
+                                         bool /*is_last_chunk*/) {
+  SB_DCHECK(is_chunked_upload_);
+  // no-op.
+}
+
+void URLFetcherFake::SetRequestContext(
+    net::URLRequestContextGetter* /*request_context_getter*/) {
+  // no-op.
+}
+
+void URLFetcherFake::Start() {
+  if (!is_chunked_upload_) {
+    download_timer_.emplace();
+    download_timer_->Start(
+        FROM_HERE, base::TimeDelta::FromMilliseconds(kDownloadTimerInterval),
+        this, &URLFetcherFake::OnURLFetchDownloadData);
+  }
+}
+
+const net::URLRequestStatus& URLFetcherFake::GetStatus() const {
+  return fake_status_;
+}
+
+int URLFetcherFake::GetResponseCode() const { return 200; }
+
+void URLFetcherFake::OnURLFetchDownloadData() {
+  SB_DCHECK(!is_chunked_upload_);
+  std::string result = GetMockProtoResult(download_index_);
+  delegate_->OnURLFetchDownloadData(
+      this, make_scoped_ptr<std::string>(new std::string(result)));
+  download_index_++;
+  if (download_index_ ==
+      static_cast<int>(SB_ARRAY_SIZE_INT(kRecognitionResults))) {
+    download_index_ = 0;
+    download_timer_->Stop();
+  }
+}
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
diff --git a/src/cobalt/speech/url_fetcher_fake.h b/src/cobalt/speech/url_fetcher_fake.h
new file mode 100644
index 0000000..cf3c150
--- /dev/null
+++ b/src/cobalt/speech/url_fetcher_fake.h
@@ -0,0 +1,156 @@
+/*
+ * 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_URL_FETCHER_FAKE_H_
+#define COBALT_SPEECH_URL_FETCHER_FAKE_H_
+
+#include "cobalt/speech/speech_configuration.h"
+
+#if defined(ENABLE_FAKE_MICROPHONE)
+
+#include <string>
+
+#include "base/optional.h"
+#include "base/timer.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_status.h"
+
+namespace cobalt {
+namespace speech {
+
+class URLFetcherFake : public net::URLFetcher {
+ public:
+  URLFetcherFake(const GURL& url, net::URLFetcher::RequestType request_type,
+                 net::URLFetcherDelegate* delegate);
+  virtual ~URLFetcherFake();
+
+  // URLFetcher implementation:
+  void SetUploadData(const std::string& /*upload_content_type*/,
+                     const std::string& /*upload_content*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void SetChunkedUpload(const std::string& /*upload_content_type*/) OVERRIDE;
+  void AppendChunkToUpload(const std::string& data,
+                           bool is_last_chunk) OVERRIDE;
+  void SetLoadFlags(int /*load_flags*/) OVERRIDE { NOTREACHED(); }
+  int GetLoadFlags() const OVERRIDE {
+    NOTREACHED();
+    return 0;
+  }
+  void SetReferrer(const std::string& /*referrer*/) OVERRIDE { NOTREACHED(); }
+  void SetExtraRequestHeaders(
+      const std::string& /*extra_request_headers*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void AddExtraRequestHeader(const std::string& /*header_line*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void GetExtraRequestHeaders(
+      net::HttpRequestHeaders* /*headers*/) const OVERRIDE {
+    NOTREACHED();
+  }
+  void SetRequestContext(
+      net::URLRequestContextGetter* request_context_getter) OVERRIDE;
+  void SetFirstPartyForCookies(
+      const GURL& /*first_party_for_cookies*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void SetURLRequestUserData(
+      const void* /*key*/,
+      const CreateDataCallback& /*create_data_callback*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void SetStopOnRedirect(bool /*stop_on_redirect*/) OVERRIDE { NOTREACHED(); }
+  void SetAutomaticallyRetryOn5xx(bool /*retry*/) OVERRIDE { NOTREACHED(); }
+  void SetMaxRetriesOn5xx(int /*max_retries*/) OVERRIDE { NOTREACHED(); }
+  int GetMaxRetriesOn5xx() const OVERRIDE {
+    NOTREACHED();
+    return 0;
+  }
+  base::TimeDelta GetBackoffDelay() const OVERRIDE {
+    NOTREACHED();
+    return base::TimeDelta();
+  }
+  void SetAutomaticallyRetryOnNetworkChanges(int /*max_retries*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void SaveResponseToFileAtPath(
+      const FilePath& /*file_path*/,
+      scoped_refptr<base::TaskRunner> /*file_task_runner*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void SaveResponseToTemporaryFile(
+      scoped_refptr<base::TaskRunner> /*file_task_runner*/) OVERRIDE {
+    NOTREACHED();
+  }
+  void DiscardResponse() OVERRIDE { NOTREACHED(); }
+  net::HttpResponseHeaders* GetResponseHeaders() const OVERRIDE {
+    NOTREACHED();
+    return NULL;
+  }
+  net::HostPortPair GetSocketAddress() const OVERRIDE {
+    NOTREACHED();
+    return net::HostPortPair();
+  }
+  bool WasFetchedViaProxy() const OVERRIDE {
+    NOTREACHED();
+    return false;
+  }
+  void Start() OVERRIDE;
+  const GURL& GetOriginalURL() const OVERRIDE { return original_url_; }
+  const GURL& GetURL() const OVERRIDE { return original_url_; }
+  const net::URLRequestStatus& GetStatus() const OVERRIDE;
+  int GetResponseCode() const OVERRIDE;
+  const net::ResponseCookies& GetCookies() const OVERRIDE {
+    NOTREACHED();
+    return fake_cookies_;
+  }
+  bool FileErrorOccurred(
+      base::PlatformFileError* /*out_error_code*/) const OVERRIDE {
+    NOTREACHED();
+    return false;
+  }
+  void ReceivedContentWasMalformed() OVERRIDE { NOTREACHED(); }
+  bool GetResponseAsString(
+      std::string* /*out_response_string*/) const OVERRIDE {
+    NOTREACHED();
+    return false;
+  }
+  bool GetResponseAsFilePath(bool /*take_ownership*/,
+                             FilePath* /*out_response_path*/) const OVERRIDE {
+    NOTREACHED();
+    return false;
+  }
+
+ private:
+  void OnURLFetchDownloadData();
+
+  GURL original_url_;
+  net::URLFetcherDelegate* delegate_;
+  bool is_chunked_upload_;
+  int download_index_;
+  net::ResponseCookies fake_cookies_;
+  net::URLRequestStatus fake_status_;
+  base::optional<base::RepeatingTimer<URLFetcherFake> > download_timer_;
+};
+
+}  // namespace speech
+}  // namespace cobalt
+
+#endif  // defined(ENABLE_FAKE_MICROPHONE)
+#endif  // COBALT_SPEECH_URL_FETCHER_FAKE_H_
diff --git a/src/cobalt/storage/savegame_thread.cc b/src/cobalt/storage/savegame_thread.cc
index 423e52f..a36cf48 100644
--- a/src/cobalt/storage/savegame_thread.cc
+++ b/src/cobalt/storage/savegame_thread.cc
@@ -26,7 +26,8 @@
 SavegameThread::SavegameThread(const Savegame::Options& options)
     : options_(options),
       initialized_(true /* manual reset */, false /* initially signalled */),
-      thread_(new base::Thread("Savegame I/O")) {
+      thread_(new base::Thread("Savegame I/O")),
+      num_consecutive_flush_failures_(0) {
   TRACE_EVENT0("cobalt::storage", __FUNCTION__);
   thread_->Start();
   thread_->message_loop()->PostTask(
@@ -88,7 +89,15 @@
   DCHECK_EQ(thread_->message_loop(), MessageLoop::current());
   if (raw_bytes_ptr->size() > 0) {
     bool ret = savegame_->Write(*raw_bytes_ptr);
-    DCHECK(ret);
+    if (ret) {
+      num_consecutive_flush_failures_ = 0;
+    } else {
+      DLOG(ERROR) << "Save failed.";
+      const int kMaxConsecutiveFlushFailures = 2;
+      DCHECK_LT(++num_consecutive_flush_failures_,
+                kMaxConsecutiveFlushFailures);
+      return;
+    }
   }
 
   if (!on_flush_complete.is_null()) {
diff --git a/src/cobalt/storage/savegame_thread.h b/src/cobalt/storage/savegame_thread.h
index 1f9b90f..1ffbd77 100644
--- a/src/cobalt/storage/savegame_thread.h
+++ b/src/cobalt/storage/savegame_thread.h
@@ -52,7 +52,7 @@
   // I/O thread.
   void ShutdownOnIOThread();
 
-  // runs on the I/O thread to write the database to the savegame's persistent
+  // Runs on the I/O thread to write the database to the savegame's persistent
   // storage.
   void FlushOnIOThread(scoped_ptr<Savegame::ByteVector> raw_bytes_ptr,
                        const base::Closure& on_flush_complete);
@@ -74,6 +74,11 @@
 
   // Interface to platform-specific savegame data.
   scoped_ptr<Savegame> savegame_;
+
+  // How many flush failures have occurred since the last successful flush.
+  // Flushes (storage writes) may sometimes fail, but we want to make sure
+  // they're not consistently failing.
+  int num_consecutive_flush_failures_;
 };
 
 }  // namespace storage
diff --git a/src/cobalt/storage/virtual_file.cc b/src/cobalt/storage/virtual_file.cc
index 64904c2..dcb93ed 100644
--- a/src/cobalt/storage/virtual_file.cc
+++ b/src/cobalt/storage/virtual_file.cc
@@ -87,7 +87,10 @@
     buffer_.resize(offset + bytes);
   }
 
-  memcpy(&buffer_[offset], source, bytes);
+  if (!buffer_.empty()) {
+    // std::vector does not define access to underlying array when empty
+    memcpy(&buffer_[offset], source, bytes);
+  }
   return bytes_in;
 }
 
diff --git a/src/cobalt/system_window/starboard/system_window.cc b/src/cobalt/system_window/starboard/system_window.cc
index 331e028..2f1b24a 100644
--- a/src/cobalt/system_window/starboard/system_window.cc
+++ b/src/cobalt/system_window/starboard/system_window.cc
@@ -103,6 +103,15 @@
   return math::Size(window_size.width, window_size.height);
 }
 
+float SystemWindowStarboard::GetVideoPixelRatio() const {
+  SbWindowSize window_size;
+  if (!SbWindowGetSize(window_, &window_size)) {
+    DLOG(WARNING) << "SbWindowGetSize() failed.";
+    return 1.0;
+  }
+  return window_size.video_pixel_ratio;
+}
+
 SbWindow SystemWindowStarboard::GetSbWindow() { return window_; }
 
 void* SystemWindowStarboard::GetWindowHandle() {
diff --git a/src/cobalt/system_window/starboard/system_window.h b/src/cobalt/system_window/starboard/system_window.h
index 7d6a76e..b733bb9 100644
--- a/src/cobalt/system_window/starboard/system_window.h
+++ b/src/cobalt/system_window/starboard/system_window.h
@@ -36,6 +36,7 @@
   ~SystemWindowStarboard() OVERRIDE;
 
   math::Size GetWindowSize() const OVERRIDE;
+  float GetVideoPixelRatio() const OVERRIDE;
 
   // Returns a handle to the Starboard window object.
   SbWindow GetSbWindow();
diff --git a/src/cobalt/system_window/system_window.h b/src/cobalt/system_window/system_window.h
index 4695652..82683e0 100644
--- a/src/cobalt/system_window/system_window.h
+++ b/src/cobalt/system_window/system_window.h
@@ -74,6 +74,11 @@
   // Returns the dimensions of the window.
   virtual math::Size GetWindowSize() const = 0;
 
+  // video pixel ratio = resolution of video output / resolution of window.  Its
+  // value is usually 1.0.  Set it to a value greater than 1.0 allows the video
+  // to be played in higher resolution than the window.
+  virtual float GetVideoPixelRatio() const { return 1.f; }
+
   base::EventDispatcher* event_dispatcher() const { return event_dispatcher_; }
 
  private:
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 28e8c13..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 "2"
+#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/element_driver.cc b/src/cobalt/webdriver/element_driver.cc
index a5c3315..d295974 100644
--- a/src/cobalt/webdriver/element_driver.cc
+++ b/src/cobalt/webdriver/element_driver.cc
@@ -64,10 +64,12 @@
 ElementDriver::ElementDriver(
     const protocol::ElementId& element_id,
     const base::WeakPtr<dom::Element>& element, ElementMapping* element_mapping,
+    KeyboardEventInjector keyboard_event_injector,
     const scoped_refptr<base::MessageLoopProxy>& message_loop)
     : element_id_(element_id),
       element_(element),
       element_mapping_(element_mapping),
+      keyboard_injector_(keyboard_event_injector),
       element_message_loop_(message_loop) {}
 
 util::CommandResult<std::string> ElementDriver::GetTagName() {
@@ -106,7 +108,8 @@
   return util::CallOnMessageLoop(
       element_message_loop_,
       base::Bind(&ElementDriver::SendKeysInternal, base::Unretained(this),
-                 base::Passed(&events)));
+                 base::Passed(&events)),
+      protocol::Response::kStaleElementReference);
 }
 
 util::CommandResult<protocol::ElementId> ElementDriver::FindElement(
@@ -114,7 +117,8 @@
   return util::CallOnMessageLoop(
       element_message_loop_,
       base::Bind(&ElementDriver::FindElementsInternal<protocol::ElementId>,
-                 base::Unretained(this), strategy));
+                 base::Unretained(this), strategy),
+      protocol::Response::kStaleElementReference);
 }
 
 util::CommandResult<std::vector<protocol::ElementId> >
@@ -122,7 +126,8 @@
   return util::CallOnMessageLoop(
       element_message_loop_,
       base::Bind(&ElementDriver::FindElementsInternal<ElementIdVector>,
-                 base::Unretained(this), strategy));
+                 base::Unretained(this), strategy),
+      protocol::Response::kNoSuchElement);
 }
 
 util::CommandResult<bool> ElementDriver::Equals(
@@ -130,7 +135,8 @@
   return util::CallOnMessageLoop(
       element_message_loop_,
       base::Bind(&ElementDriver::EqualsInternal, base::Unretained(this),
-                 other_element_driver));
+                 other_element_driver),
+      protocol::Response::kStaleElementReference);
 }
 
 util::CommandResult<base::optional<std::string> > ElementDriver::GetAttribute(
@@ -157,7 +163,7 @@
 }
 
 util::CommandResult<void> ElementDriver::SendKeysInternal(
-    scoped_ptr<KeyboardEventVector> events) {
+    scoped_ptr<Keyboard::KeyboardEventVector> events) {
   typedef util::CommandResult<void> CommandResult;
   DCHECK_EQ(base::MessageLoopProxy::current(), element_message_loop_);
   if (!element_) {
@@ -174,7 +180,8 @@
     if (!element_) {
       return CommandResult(protocol::Response::kStaleElementReference);
     }
-    element_->DispatchEvent((*events)[i]);
+
+    keyboard_injector_.Run(element_.get(), (*events)[i]);
   }
   return CommandResult(protocol::Response::kSuccess);
 }
diff --git a/src/cobalt/webdriver/element_driver.h b/src/cobalt/webdriver/element_driver.h
index 22e2ee0..645a0e0 100644
--- a/src/cobalt/webdriver/element_driver.h
+++ b/src/cobalt/webdriver/element_driver.h
@@ -28,6 +28,7 @@
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/keyboard_event.h"
 #include "cobalt/webdriver/element_mapping.h"
+#include "cobalt/webdriver/keyboard.h"
 #include "cobalt/webdriver/protocol/element_id.h"
 #include "cobalt/webdriver/protocol/keys.h"
 #include "cobalt/webdriver/protocol/search_strategy.h"
@@ -46,9 +47,14 @@
 // will map to a method on this class.
 class ElementDriver {
  public:
+  typedef base::Callback<void(scoped_refptr<dom::Element>,
+                              const dom::KeyboardEvent::Data&)>
+      KeyboardEventInjector;
+
   ElementDriver(const protocol::ElementId& element_id,
                 const base::WeakPtr<dom::Element>& element,
                 ElementMapping* element_mapping,
+                KeyboardEventInjector keyboard_injector,
                 const scoped_refptr<base::MessageLoopProxy>& message_loop);
   const protocol::ElementId& element_id() { return element_id_; }
 
@@ -67,7 +73,6 @@
       const std::string& property_name);
 
  private:
-  typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector;
   typedef std::vector<protocol::ElementId> ElementIdVector;
 
   // Get the dom::Element* that this ElementDriver wraps. This must be called
@@ -75,7 +80,7 @@
   dom::Element* GetWeakElement();
 
   util::CommandResult<void> SendKeysInternal(
-      scoped_ptr<KeyboardEventVector> keyboard_events);
+      scoped_ptr<Keyboard::KeyboardEventVector> keyboard_events);
 
   // Shared logic between FindElement and FindElements.
   template <typename T>
@@ -90,6 +95,7 @@
   // These should only be accessed from |element_message_loop_|.
   base::WeakPtr<dom::Element> element_;
   ElementMapping* element_mapping_;
+  KeyboardEventInjector keyboard_injector_;
   scoped_refptr<base::MessageLoopProxy> element_message_loop_;
 
   friend class WindowDriver;
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/keyboard.cc b/src/cobalt/webdriver/keyboard.cc
index c69496f..32d4fbc 100644
--- a/src/cobalt/webdriver/keyboard.cc
+++ b/src/cobalt/webdriver/keyboard.cc
@@ -421,7 +421,7 @@
                    KeyLocationCode location) {
     const bool kIsRepeat = false;
     uint32 modifiers = GetModifierStateBitfield();
-    event_vector_->push_back(new dom::KeyboardEvent(
+    event_vector_->push_back(dom::KeyboardEvent::Data(
         type, location, modifiers, key_code, char_code, kIsRepeat));
   }
 
diff --git a/src/cobalt/webdriver/keyboard.h b/src/cobalt/webdriver/keyboard.h
index feeab6f..57a5c5f 100644
--- a/src/cobalt/webdriver/keyboard.h
+++ b/src/cobalt/webdriver/keyboard.h
@@ -32,7 +32,8 @@
     kReleaseModifiers,
     kKeepModifiers,
   };
-  typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector;
+  typedef std::vector<dom::KeyboardEvent::Data>
+      KeyboardEventVector;
   static void TranslateToKeyEvents(const std::string& utf8_keys,
                                    TerminationBehaviour termination_behaviour,
                                    KeyboardEventVector* out_events);
diff --git a/src/cobalt/webdriver/keyboard_test.cc b/src/cobalt/webdriver/keyboard_test.cc
index 1f71598..7994bd5 100644
--- a/src/cobalt/webdriver/keyboard_test.cc
+++ b/src/cobalt/webdriver/keyboard_test.cc
@@ -31,25 +31,34 @@
 namespace webdriver {
 namespace {
 
-int32 GetKeyCode(const scoped_refptr<dom::KeyboardEvent>& event) {
-  DCHECK(event);
-  return event->key_code();
+int32 GetKeyCode(const dom::KeyboardEvent::Data& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+            new dom::KeyboardEvent(event));
+  return keyboard_event->key_code();
 }
 
-int32 GetCharCode(const scoped_refptr<dom::KeyboardEvent>& event) {
-  return event->char_code();
+int32 GetCharCode(const dom::KeyboardEvent::Data& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+            new dom::KeyboardEvent(event));
+  return keyboard_event->char_code();
 }
 
-uint32 GetModifierBitfield(const scoped_refptr<dom::KeyboardEvent>& event) {
-  return event->modifiers();
+uint32 GetModifierBitfield(const dom::KeyboardEvent::Data& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+            new dom::KeyboardEvent(event));
+  return keyboard_event->modifiers();
 }
 
-std::string GetType(const scoped_refptr<dom::KeyboardEvent>& event) {
-  return event->type().c_str();
+std::string GetType(const dom::KeyboardEvent::Data& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+            new dom::KeyboardEvent(event));
+  return keyboard_event->type().c_str();
 }
 
-int GetLocation(const scoped_refptr<dom::KeyboardEvent>& event) {
-  return event->location();
+int GetLocation(const dom::KeyboardEvent::Data& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+            new dom::KeyboardEvent(event));
+  return keyboard_event->location();
 }
 
 class KeyboardTest : public ::testing::Test {
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/protocol/server_status.cc b/src/cobalt/webdriver/protocol/server_status.cc
index e98ebe2..bcb283c 100644
--- a/src/cobalt/webdriver/protocol/server_status.cc
+++ b/src/cobalt/webdriver/protocol/server_status.cc
@@ -16,9 +16,6 @@
 
 #include "cobalt/webdriver/protocol/server_status.h"
 
-// TODO: Support running WebDriver on platforms other than Linux.
-#include <sys/utsname.h>
-
 #include "cobalt/version.h"
 
 namespace cobalt {
@@ -26,11 +23,26 @@
 namespace protocol {
 
 ServerStatus::ServerStatus() {
-  struct utsname name_buffer;
-  if (uname(&name_buffer) == 0) {
-    os_name_ = name_buffer.sysname;
-    os_version_ = name_buffer.version;
-    os_arch_ = name_buffer.machine;
+  const size_t kSystemPropertyMaxLength = 1024;
+  char value[kSystemPropertyMaxLength];
+  bool result;
+
+  result = SbSystemGetProperty(kSbSystemPropertyPlatformName, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    os_name_ = value;
+  }
+
+  result = SbSystemGetProperty(kSbSystemPropertyFirmwareVersion, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    os_version_ = value;
+  }
+
+  result = SbSystemGetProperty(kSbSystemPropertyChipsetModelNumber, value,
+                               kSystemPropertyMaxLength);
+  if (result) {
+    os_arch_ = value;
   }
   build_time_ = __DATE__ " (" __TIME__ ")";
   build_version_ = COBALT_VERSION;
@@ -42,9 +54,16 @@
   build_value->SetString("version", status.build_version_);
 
   scoped_ptr<base::DictionaryValue> os_value(new base::DictionaryValue());
-  os_value->SetString("arch", status.os_arch_);
-  os_value->SetString("name", status.os_name_);
-  os_value->SetString("version", status.os_version_);
+
+  if (status.os_arch_) {
+    os_value->SetString("arch", *status.os_arch_);
+  }
+  if (status.os_name_) {
+    os_value->SetString("name", *status.os_name_);
+  }
+  if (status.os_version_) {
+    os_value->SetString("version", *status.os_version_);
+  }
 
   scoped_ptr<base::DictionaryValue> status_value(new base::DictionaryValue());
   status_value->Set("os", os_value.release());
diff --git a/src/cobalt/webdriver/protocol/server_status.h b/src/cobalt/webdriver/protocol/server_status.h
index dcd825a..cdef9a4 100644
--- a/src/cobalt/webdriver/protocol/server_status.h
+++ b/src/cobalt/webdriver/protocol/server_status.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
 #include "base/values.h"
 
 namespace cobalt {
@@ -36,9 +37,9 @@
   static scoped_ptr<base::Value> ToValue(const ServerStatus& status);
 
  private:
-  std::string os_name_;
-  std::string os_arch_;
-  std::string os_version_;
+  base::optional<std::string> os_name_;
+  base::optional<std::string> os_arch_;
+  base::optional<std::string> os_version_;
   std::string build_time_;
   std::string build_version_;
 };
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..9c69788 100644
--- a/src/cobalt/webdriver/server.cc
+++ b/src/cobalt/webdriver/server.cc
@@ -176,13 +176,13 @@
 };
 }  // namespace
 
-WebDriverServer::WebDriverServer(int port,
+WebDriverServer::WebDriverServer(int port, const std::string& listen_ip,
                                  const HandleRequestCallback& callback)
     : handle_request_callback_(callback) {
-  DLOG(INFO) << "Starting WebDriver server on port " << port;
   // Create http server
-  factory_.reset(new net::TCPListenSocketFactory("0.0.0.0", port));
+  factory_.reset(new net::TCPListenSocketFactory(listen_ip, port));
   server_ = new net::HttpServer(*factory_, this);
+  LOG(INFO) << "Starting WebDriver server on port " << port;
 }
 
 void WebDriverServer::OnHttpRequest(int connection_id,
diff --git a/src/cobalt/webdriver/server.h b/src/cobalt/webdriver/server.h
index a35a947..b81008a 100644
--- a/src/cobalt/webdriver/server.h
+++ b/src/cobalt/webdriver/server.h
@@ -79,7 +79,8 @@
       HttpMethod, const std::string&, scoped_ptr<base::Value>,
       scoped_ptr<ResponseHandler>)> HandleRequestCallback;
 
-  WebDriverServer(int port, const HandleRequestCallback& callback);
+  WebDriverServer(int port, const std::string& listen_ip,
+                  const HandleRequestCallback& callback);
 
  protected:
   // net::HttpServer::Delegate implementation.
diff --git a/src/cobalt/webdriver/session_driver.cc b/src/cobalt/webdriver/session_driver.cc
index 91f3358..5a59853 100644
--- a/src/cobalt/webdriver/session_driver.cc
+++ b/src/cobalt/webdriver/session_driver.cc
@@ -28,6 +28,9 @@
 // Default page-load timeout.
 const int kPageLoadTimeoutInSeconds = 30;
 
+// Max retries for the "can_retry" CommandResult case.
+const int kMaxRetries = 5;
+
 protocol::LogEntry::LogLevel SeverityToLogLevel(int severity) {
   switch (severity) {
     case logging::LOG_INFO:
@@ -82,7 +85,11 @@
 }
 
 util::CommandResult<void> SessionDriver::Navigate(const GURL& url) {
-  util::CommandResult<void> result = window_driver_->Navigate(url);
+  int retries = 0;
+  util::CommandResult<void> result;
+  do {
+    result = window_driver_->Navigate(url);
+  } while (result.can_retry() && (retries++ < kMaxRetries));
   if (result.is_success()) {
     // TODO: Use timeout as specified by the webdriver client.
     wait_for_navigation_.Run(
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/util/call_on_message_loop.h b/src/cobalt/webdriver/util/call_on_message_loop.h
index 8b28a82..c2a19d2 100644
--- a/src/cobalt/webdriver/util/call_on_message_loop.h
+++ b/src/cobalt/webdriver/util/call_on_message_loop.h
@@ -35,26 +35,63 @@
   CallOnMessageLoopHelper(
       const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
       const CallbackType& callback)
-      : completed_event_(false, false) {
+      : completed_event_(true, false), success_(false) {
     DCHECK_NE(base::MessageLoopProxy::current(), message_loop_proxy);
+    scoped_ptr<DeletionSignaler> dt(new DeletionSignaler(&completed_event_));
+    // Note that while MessageLoopProxy::PostTask returns false
+    // after the message loop has gone away, it still can return true
+    // even if tasks are posted during shutdown and will never be run,
+    // so we ignore this return value.
     message_loop_proxy->PostTask(
-        FROM_HERE, base::Bind(&CallOnMessageLoopHelper::CallAndSignal,
-                              base::Unretained(this), callback));
+        FROM_HERE,
+        base::Bind(&CallOnMessageLoopHelper::Call, base::Unretained(this),
+                   callback, base::Passed(&dt)));
   }
 
-  ReturnValue WaitForResult() {
+  // Waits for result, filling |out| with the return value if successful.
+  // Returns true on success or false if the message loop went away
+  // before the task was executed.
+  bool WaitForResult(ReturnValue* out) {
     completed_event_.Wait();
-    return result_;
+    if (success_) {
+      *out = result_;
+    }
+    return success_;
+  }
+
+  ~CallOnMessageLoopHelper() {
+    // We must ensure that we've waited for completion otherwise
+    // DeletionSignaler will have a use-after-free.
+    completed_event_.Wait();
   }
 
  private:
-  void CallAndSignal(const CallbackType& callback) {
+  // DeletionSignaler signals an event when the destructor is called.
+  // This allows us to use the base::Passed mechanism to signal our
+  // completed_event_ both when Call() has been invoked and when
+  // the message loop has been deleted.
+  class DeletionSignaler {
+   public:
+    base::WaitableEvent* to_signal_;
+
+    explicit DeletionSignaler(base::WaitableEvent* to_signal)
+        : to_signal_(to_signal) {}
+
+    ~DeletionSignaler() { to_signal_->Signal(); }
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(DeletionSignaler);
+  };
+
+  void Call(const CallbackType& callback,
+            scoped_ptr<DeletionSignaler> dt ALLOW_UNUSED) {
     result_ = callback.Run();
-    completed_event_.Signal();
+    success_ = true;
   }
 
   base::WaitableEvent completed_event_;
   ReturnValue result_;
+  bool success_;
 };
 
 // Used with CallWeakOnMessageLoop.
@@ -71,14 +108,37 @@
 }  // namespace internal
 
 // Call the base::Callback on the specified message loop and wait for it to
-// complete, then return the result of the callback.
+// complete. Returns true if successful, or false if the underlying
+// PostTask failed. This can happen if a WebModule shuts down due to a page
+// navigation.
+//
+// On success, |out| is set to the result.
 template <class ReturnValue>
-ReturnValue CallOnMessageLoop(
+bool TryCallOnMessageLoop(
     const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
-    const base::Callback<ReturnValue(void)>& callback) {
+    const base::Callback<ReturnValue(void)>& callback, ReturnValue* out) {
   internal::CallOnMessageLoopHelper<ReturnValue> call_helper(message_loop_proxy,
                                                              callback);
-  return call_helper.WaitForResult();
+  return call_helper.WaitForResult(out);
+}
+
+// Tries to call |callback| on messageloop |message_loop_proxy|,
+// but returns a CommandResult of |window_disappeared_code| if the
+// message loop has shut down. This can happen if a WebModule shuts
+// down due to a page navigation.
+template <typename ReturnValue>
+util::CommandResult<ReturnValue> CallOnMessageLoop(
+    const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy,
+    const base::Callback<util::CommandResult<ReturnValue>(void)>& callback,
+    protocol::Response::StatusCode window_disappeared_code) {
+  util::CommandResult<ReturnValue> result;
+  bool success = TryCallOnMessageLoop(message_loop_proxy, callback, &result);
+
+  if (!success) {
+    result =
+        util::CommandResult<ReturnValue>(window_disappeared_code, "", true);
+  }
+  return result;
 }
 
 // Supports a common pattern in the various XXXDriver classes.
@@ -97,10 +157,11 @@
     protocol::Response::StatusCode no_such_object_code) {
   typedef util::CommandResult<ReturnValue> CommandResult;
   typedef base::optional<ReturnValue> InternalResult;
-  InternalResult result = util::CallOnMessageLoop(
+  InternalResult result;
+  bool success = util::TryCallOnMessageLoop(
       message_loop,
-      base::Bind(&internal::RunWeak<T, ReturnValue>, get_weak, cb));
-  if (result) {
+      base::Bind(&internal::RunWeak<T, ReturnValue>, get_weak, cb), &result);
+  if (success && result) {
     return CommandResult(result.value());
   } else {
     return CommandResult(no_such_object_code);
diff --git a/src/cobalt/webdriver/util/command_result.h b/src/cobalt/webdriver/util/command_result.h
index d1254c7..8944d4d 100644
--- a/src/cobalt/webdriver/util/command_result.h
+++ b/src/cobalt/webdriver/util/command_result.h
@@ -32,12 +32,17 @@
  public:
   CommandResult() : status_code_(protocol::Response::kUnknownError) {}
   explicit CommandResult(protocol::Response::StatusCode status)
-      : status_code_(status) {}
+      : status_code_(status), can_retry_(false) {}
   CommandResult(protocol::Response::StatusCode status,
                 const std::string& message)
-      : status_code_(status), error_message_(message) {}
+      : status_code_(status), error_message_(message), can_retry_(false) {}
+  CommandResult(protocol::Response::StatusCode status,
+                const std::string& message, bool can_retry)
+      : status_code_(status), error_message_(message), can_retry_(can_retry) {}
   explicit CommandResult(const T& result)
-      : status_code_(protocol::Response::kSuccess), result_(result) {}
+      : status_code_(protocol::Response::kSuccess),
+        result_(result),
+        can_retry_(false) {}
 
   bool is_success() const {
     return status_code_ == protocol::Response::kSuccess;
@@ -45,11 +50,16 @@
   protocol::Response::StatusCode status_code() const { return status_code_; }
   const T& result() const { return result_.value(); }
   std::string error_message() const { return error_message_.value_or(""); }
+  // Returns true if this result occurred due to a transient issue,
+  // such as the WebModule going away. Retrying w/ a new WebModule
+  // may succeed.
+  bool can_retry() const { return can_retry_; }
 
  private:
   protocol::Response::StatusCode status_code_;
   base::optional<T> result_;
   base::optional<std::string> error_message_;
+  bool can_retry_;
 };
 
 template <>
@@ -57,20 +67,28 @@
  public:
   CommandResult() : status_code_(protocol::Response::kUnknownError) {}
   explicit CommandResult(protocol::Response::StatusCode status)
-      : status_code_(status) {}
+      : status_code_(status), can_retry_(false) {}
   CommandResult(protocol::Response::StatusCode status,
                 const std::string& message)
-      : status_code_(status), error_message_(message) {}
+      : status_code_(status), error_message_(message), can_retry_(false) {}
+  CommandResult(protocol::Response::StatusCode status,
+                const std::string& message, bool can_retry)
+      : status_code_(status), error_message_(message), can_retry_(can_retry) {}
 
   bool is_success() const {
     return status_code_ == protocol::Response::kSuccess;
   }
   protocol::Response::StatusCode status_code() const { return status_code_; }
   std::string error_message() const { return error_message_.value_or(""); }
+  // Returns true if this result occurred due to a transient issue,
+  // such as the WebModule going away. Retrying w/ a new WebModule
+  // may succeed.
+  bool can_retry() const { return can_retry_; }
 
  private:
   protocol::Response::StatusCode status_code_;
   base::optional<std::string> error_message_;
+  bool can_retry_;
 };
 
 }  // namespace util
diff --git a/src/cobalt/webdriver/util/dispatch_command_factory.h b/src/cobalt/webdriver/util/dispatch_command_factory.h
index 1e45e4b..cfed299 100644
--- a/src/cobalt/webdriver/util/dispatch_command_factory.h
+++ b/src/cobalt/webdriver/util/dispatch_command_factory.h
@@ -122,6 +122,9 @@
 template <class DriverClassT>
 class DispatchCommandFactory
     : public base::RefCounted<DispatchCommandFactory<DriverClassT> > {
+  // Max retries for the "can_retry" CommandResult case.
+  static const int kMaxRetries = 5;
+
  public:
   // Typedef'd for less verbose code.
   typedef WebDriverDispatcher::CommandResultHandler CommandResultHandler;
@@ -220,7 +223,11 @@
         DriverClassT* driver, const base::Value* parameters,
         CommandResultHandler* result_handler) {
       // Ignore parameters.
-      util::CommandResult<R> command_result = driver_command.Run(driver);
+      int retries = 0;
+      util::CommandResult<R> command_result;
+      do {
+        command_result = driver_command.Run(driver);
+      } while (command_result.can_retry() && (retries++ < kMaxRetries));
       internal::ReturnResponse(session_id, command_result, result_handler);
     }
 
@@ -245,8 +252,11 @@
         result_handler->SendInvalidRequestResponse(
             WebDriverDispatcher::CommandResultHandler::kInvalidParameters, "");
       } else {
-        util::CommandResult<R> command_result =
-            driver_command.Run(driver, param.value());
+        int retries = 0;
+        util::CommandResult<R> command_result;
+        do {
+          command_result = driver_command.Run(driver, param.value());
+        } while (command_result.can_retry() && (retries++ < kMaxRetries));
         internal::ReturnResponse(session_id, command_result, result_handler);
       }
     }
diff --git a/src/cobalt/webdriver/web_driver_module.cc b/src/cobalt/webdriver/web_driver_module.cc
index 46a77e8..9b4b9d0 100644
--- a/src/cobalt/webdriver/web_driver_module.cc
+++ b/src/cobalt/webdriver/web_driver_module.cc
@@ -159,8 +159,11 @@
 
 }  // namespace
 
+const char WebDriverModule::kDefaultListenIp[] = "0.0.0.0";
+
 WebDriverModule::WebDriverModule(
-    int server_port, const CreateSessionDriverCB& create_session_driver_cb,
+    int server_port, const std::string& listen_ip,
+    const CreateSessionDriverCB& create_session_driver_cb,
     const GetScreenshotFunction& get_screenshot_function,
     const SetProxyFunction& set_proxy_function,
     const base::Closure& shutdown_cb)
@@ -294,6 +297,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)));
@@ -400,7 +408,7 @@
       base::Thread::Options(MessageLoop::TYPE_IO, 0));
   webdriver_thread_.message_loop()->PostTask(
       FROM_HERE, base::Bind(&WebDriverModule::StartServer,
-                            base::Unretained(this), server_port));
+                            base::Unretained(this), server_port, listen_ip));
 }
 
 WebDriverModule::~WebDriverModule() {
@@ -423,11 +431,12 @@
   }
 }
 
-void WebDriverModule::StartServer(int server_port) {
+void WebDriverModule::StartServer(int server_port,
+                                  const std::string& listen_ip) {
   DCHECK(thread_checker_.CalledOnValidThread());
   // Create a new WebDriverServer and pass in the Dispatcher.
   webdriver_server_.reset(new WebDriverServer(
-      server_port,
+      server_port, listen_ip,
       base::Bind(&WebDriverDispatcher::HandleWebDriverServerRequest,
                  base::Unretained(webdriver_dispatcher_.get()))));
 }
diff --git a/src/cobalt/webdriver/web_driver_module.h b/src/cobalt/webdriver/web_driver_module.h
index 2df30dd..b0c89ed 100644
--- a/src/cobalt/webdriver/web_driver_module.h
+++ b/src/cobalt/webdriver/web_driver_module.h
@@ -48,7 +48,10 @@
   typedef base::Callback<void(const ScreenshotCompleteCallback&)>
       GetScreenshotFunction;
   typedef base::Callback<void(const std::string&)> SetProxyFunction;
-  WebDriverModule(int server_port,
+  // Use this as the default listen_ip. It means "any interface on the local
+  // machine" eg INADDR_ANY.
+  static const char kDefaultListenIp[];
+  WebDriverModule(int server_port, const std::string& listen_ip,
                   const CreateSessionDriverCB& create_session_driver_cb,
                   const GetScreenshotFunction& get_screenshot_function,
                   const SetProxyFunction& set_proxy_function,
@@ -65,7 +68,7 @@
   void OnWindowRecreated();
 
  private:
-  void StartServer(int server_port);
+  void StartServer(int server_port, const std::string& listen_ip);
   void StopServer();
   void GetServerStatus(
       const base::Value* parameters,
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 3e77e54..b23abc2 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);
@@ -158,10 +110,12 @@
     const protocol::WindowId& window_id,
     const base::WeakPtr<dom::Window>& window,
     const GetGlobalEnvironmentFunction& get_global_environment_function,
+    KeyboardEventInjector keyboard_injector,
     const scoped_refptr<base::MessageLoopProxy>& message_loop)
     : window_id_(window_id),
       window_(window),
       get_global_environment_(get_global_environment_function),
+      keyboard_injector_(keyboard_injector),
       window_message_loop_(message_loop),
       element_driver_map_deleter_(&element_drivers_),
       next_element_id_(0) {
@@ -182,9 +136,12 @@
     // It's expected that the WebDriver thread is the only other thread to call
     // this function.
     DCHECK(thread_checker_.CalledOnValidThread());
-    return util::CallOnMessageLoop(window_message_loop_,
-        base::Bind(&WindowDriver::GetElementDriver, base::Unretained(this),
-                   element_id));
+    ElementDriver* result;
+    bool success = util::TryCallOnMessageLoop(
+        window_message_loop_, base::Bind(&WindowDriver::GetElementDriver,
+                                         base::Unretained(this), element_id),
+        &result);
+    return success ? result : NULL;
   }
   DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_);
   ElementDriverMap::iterator it = element_drivers_.find(element_id.id());
@@ -207,7 +164,8 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   return util::CallOnMessageLoop(
       window_message_loop_,
-      base::Bind(&WindowDriver::NavigateInternal, base::Unretained(this), url));
+      base::Bind(&WindowDriver::NavigateInternal, base::Unretained(this), url),
+      protocol::Response::kNoSuchWindow);
 }
 
 util::CommandResult<std::string> WindowDriver::GetCurrentUrl() {
@@ -232,17 +190,21 @@
     const protocol::SearchStrategy& strategy) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  return util::CallOnMessageLoop(window_message_loop_,
+  return util::CallOnMessageLoop(
+      window_message_loop_,
       base::Bind(&WindowDriver::FindElementsInternal<protocol::ElementId>,
-                 base::Unretained(this), strategy));
+                 base::Unretained(this), strategy),
+      protocol::Response::kNoSuchElement);
 }
 
 util::CommandResult<std::vector<protocol::ElementId> >
 WindowDriver::FindElements(const protocol::SearchStrategy& strategy) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return util::CallOnMessageLoop(window_message_loop_,
+  return util::CallOnMessageLoop(
+      window_message_loop_,
       base::Bind(&WindowDriver::FindElementsInternal<ElementIdVector>,
-                 base::Unretained(this), strategy));
+                 base::Unretained(this), strategy),
+      protocol::Response::kNoSuchElement);
 }
 
 util::CommandResult<std::string> WindowDriver::GetSource() {
@@ -256,12 +218,53 @@
 
 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),
+      protocol::Response::kNoSuchWindow);
+  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),
+      protocol::Response::kNoSuchWindow);
+
+  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) {
@@ -274,13 +277,15 @@
   return util::CallOnMessageLoop(
       window_message_loop_,
       base::Bind(&WindowDriver::SendKeysInternal, base::Unretained(this),
-                 base::Passed(&events)));
+                 base::Passed(&events)),
+      protocol::Response::kNoSuchWindow);
 }
 
 util::CommandResult<protocol::ElementId> WindowDriver::GetActiveElement() {
   return util::CallOnMessageLoop(
       window_message_loop_, base::Bind(&WindowDriver::GetActiveElementInternal,
-                                       base::Unretained(this)));
+                                       base::Unretained(this)),
+      protocol::Response::kNoSuchWindow);
 }
 
 util::CommandResult<void> WindowDriver::SwitchFrame(
@@ -327,7 +332,8 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   return util::CallOnMessageLoop(window_message_loop_,
                                  base::Bind(&WindowDriver::AddCookieInternal,
-                                            base::Unretained(this), cookie));
+                                            base::Unretained(this), cookie),
+                                 protocol::Response::kNoSuchWindow);
 }
 
 protocol::ElementId WindowDriver::ElementToId(
@@ -351,6 +357,7 @@
   std::pair<ElementDriverMapIt, bool> pair_it =
       element_drivers_.insert(std::make_pair(
           element_id.id(), new ElementDriver(element_id, weak_element, this,
+                                             keyboard_injector_,
                                              window_message_loop_)));
   DCHECK(pair_it.second)
       << "An ElementDriver was already mapped to the element id: "
@@ -373,7 +380,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_) {
@@ -390,7 +399,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);
@@ -401,24 +410,20 @@
   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(
-    scoped_ptr<KeyboardEventVector> events) {
+    scoped_ptr<Keyboard::KeyboardEventVector> events) {
   typedef util::CommandResult<void> CommandResult;
   DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_);
   if (!window_) {
@@ -426,8 +431,7 @@
   }
 
   for (size_t i = 0; i < events->size(); ++i) {
-    // InjectEvent will send to the focused element.
-    window_->InjectEvent((*events)[i]);
+    keyboard_injector_.Run(scoped_refptr<dom::Element>(), (*events)[i]);
   }
   return CommandResult(protocol::Response::kSuccess);
 }
diff --git a/src/cobalt/webdriver/window_driver.h b/src/cobalt/webdriver/window_driver.h
index ae03a9f..e68f06d 100644
--- a/src/cobalt/webdriver/window_driver.h
+++ b/src/cobalt/webdriver/window_driver.h
@@ -34,6 +34,7 @@
 #include "cobalt/dom/window.h"
 #include "cobalt/webdriver/element_driver.h"
 #include "cobalt/webdriver/element_mapping.h"
+#include "cobalt/webdriver/keyboard.h"
 #include "cobalt/webdriver/protocol/cookie.h"
 #include "cobalt/webdriver/protocol/frame_id.h"
 #include "cobalt/webdriver/protocol/keys.h"
@@ -54,11 +55,15 @@
 // will map to a method on this class.
 class WindowDriver : private ElementMapping {
  public:
+  typedef base::Callback<void(scoped_refptr<dom::Element>,
+                              const dom::KeyboardEvent::Data&)>
+      KeyboardEventInjector;
   typedef base::Callback<scoped_refptr<script::GlobalEnvironment>()>
       GetGlobalEnvironmentFunction;
   WindowDriver(const protocol::WindowId& window_id,
                const base::WeakPtr<dom::Window>& window,
                const GetGlobalEnvironmentFunction& get_global_environment,
+               KeyboardEventInjector keyboard_injector,
                const scoped_refptr<base::MessageLoopProxy>& message_loop);
   ~WindowDriver();
   const protocol::WindowId& window_id() { return window_id_; }
@@ -77,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);
@@ -89,7 +96,6 @@
   typedef base::hash_map<std::string, ElementDriver*> ElementDriverMap;
   typedef ElementDriverMap::iterator ElementDriverMapIt;
   typedef std::vector<protocol::ElementId> ElementIdVector;
-  typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector;
 
   // ScriptExecutor::ElementMapping implementation.
   protocol::ElementId ElementToId(
@@ -113,10 +119,12 @@
       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<KeyboardEventVector> keyboard_events);
+      scoped_ptr<Keyboard::KeyboardEventVector> keyboard_events);
 
   util::CommandResult<void> NavigateInternal(const GURL& url);
 
@@ -129,6 +137,8 @@
   // Bound to the WebDriver thread.
   base::ThreadChecker thread_checker_;
 
+  KeyboardEventInjector keyboard_injector_;
+
   // Anything that interacts with the window must be run on this message loop.
   scoped_refptr<base::MessageLoopProxy> window_message_loop_;
 
diff --git a/src/cobalt/webdriver_benchmarks/README.md b/src/cobalt/webdriver_benchmarks/README.md
new file mode 100644
index 0000000..da77083
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/README.md
@@ -0,0 +1,3 @@
+Framework for webdriver-driven benchmarks.
+
+Please see tests/README.md
diff --git a/src/cobalt/webdriver_benchmarks/__init__.py b/src/cobalt/webdriver_benchmarks/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
new file mode 100755
index 0000000..fbba36b
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python2
+"""Webdriver-based benchmarks for partial layout.
+
+Benchmarks for partial layout changes in Cobalt.
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import importlib
+import inspect
+import os
+import re
+import sys
+import thread
+import threading
+import unittest
+
+arg_parser = argparse.ArgumentParser(
+    description="Runs Webdriver-based Cobalt benchmarks")
+arg_parser.add_argument(
+    "-p",
+    "--platform",
+    help="Cobalt platform, eg 'linux-x64x11'."
+    "Fetched from environment if absent.")
+arg_parser.add_argument(
+    "-e",
+    "--executable",
+    help="Path to cobalt executable. "
+    "Auto-derived if absent.")
+arg_parser.add_argument(
+    "-c",
+    "--config",
+    choices=["debug", "devel", "qa", "gold"],
+    help="Build config (eg, 'qa' or 'devel'). Not used if "
+    "--executable is specified. Fetched from environment "
+    "if needed and absent.")
+arg_parser.add_argument(
+    "-d",
+    "--devkit_name",
+    help="Devkit or IP address for app_launcher."
+    "Current hostname used if absent.")
+arg_parser.add_argument(
+    "-o", "--log_file", help="Logfile pathname. stdout if absent.")
+
+# Pattern to match Cobalt log line for when the WebDriver port has been
+# opened.
+RE_WEBDRIVER_LISTEN = re.compile(r"Starting WebDriver server on port (\d+)$")
+
+STARTUP_TIMEOUT_SECONDS = 2 * 60
+
+WEBDRIVER_HTTP_TIMEOUT_SECS = 2 * 60
+
+COBALT_EXIT_TIMEOUT_SECONDS = 5
+
+COBALT_WEBDRIVER_CAPABILITIES = {
+    "browserName": "cobalt",
+    "javascriptEnabled": True,
+    "platform": "LINUX"
+}
+
+_webdriver = None
+
+
+def GetWebDriver():
+  """Returns the active connect WebDriver instance."""
+  return _webdriver
+
+
+def ImportSeleniumModule(submodule=None):
+  """Dynamically imports a selenium.webdriver submodule.
+
+  This is done because selenium 3.0 is not commonly pre-installed
+  on workstations, and we want to have a friendly error message for that
+  case.
+
+  Args:
+    submodule: module subpath underneath "selenium.webdriver"
+  Returns:
+    appropriate module
+  """
+  if submodule:
+    module_path = ".".join(("selenium", submodule))
+  else:
+    module_path = "selenium"
+  # As of this writing, Google uses selenium 3.0.0b2 internally, so
+  # thats what we will target here as well.
+  try:
+    module = importlib.import_module(module_path)
+    if submodule is None:
+      # Only the top-level module has __version__
+      if not module.__version__.startswith("3.0"):
+        raise ImportError("Not version 3.0.x")
+  except ImportError:
+    sys.stderr.write("Could not import {}\n"
+                     "Please install selenium >= 3.0.0b2.\n"
+                     "Commonly: \"sudo pip install 'selenium>=3.0.0b2'\"\n"
+                     .format(module_path))
+    sys.exit(1)
+  return module
+
+
+class TimeoutException(Exception):
+  pass
+
+
+class CobaltRunner(object):
+  """Runs a Cobalt browser w/ a WebDriver client attached."""
+  test_script_started = 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")
+
+    script_path = os.path.realpath(inspect.getsourcefile(lambda: 0))
+    app_launcher_path = os.path.realpath(
+        os.path.join(
+            os.path.dirname(script_path), "..", "..", "tools", "lbshell"))
+    sys.path.append(app_launcher_path)
+    app_launcher = importlib.import_module("app_launcher")
+    self.launcher = app_launcher.CreateLauncher(
+        platform, executable, devkit_name=devkit_name, close_output_file=False)
+
+    self.launcher.SetArgs(["--enable_webdriver"])
+    self.launcher.SetOutputCallback(self._HandleLine)
+    self.log_file_path = log_file_path
+    self.log_file = None
+
+  def __enter__(self):
+    self.thread = threading.Thread(target=self.Run)
+    self.thread.start()
+    try:
+      self.WaitForStart()
+    except KeyboardInterrupt:
+      # potentially from thread.interrupt_main(). We will treat as
+      # a timeout regardless
+      raise TimeoutException
+    return self
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    # 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."""
+
+    # Wait for WebDriver port here then connect
+    if self.test_script_started.is_set():
+      return
+
+    match = RE_WEBDRIVER_LISTEN.search(line)
+    if not match:
+      return
+
+    port = match.group(1)
+    print("WebDriver port opened:" + port + "\n", file=self.log_file)
+    self._StartWebdriver(port)
+
+  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()
+
+  def _StartWebdriver(self, port):
+    global _webdriver
+    url = "http://{}:{}/".format(self._GetIPAddress(), port)
+    self.webdriver = self.selenium_webdriver_module.Remote(
+        url, COBALT_WEBDRIVER_CAPABILITIES)
+    self.webdriver.command_executor.set_timeout(WEBDRIVER_HTTP_TIMEOUT_SECS)
+    print("Selenium Connected\n", file=self.log_file)
+    _webdriver = self.webdriver
+    self.test_script_started.set()
+
+  def WaitForStart(self):
+    """Waits for the webdriver client to attach to cobalt."""
+    if not self.test_script_started.wait(STARTUP_TIMEOUT_SECONDS):
+      self.SetShouldExit(failed=True)
+      raise TimeoutException
+    print("Cobalt started", file=self.log_file)
+
+  def Run(self):
+    """Thread run routine."""
+
+    # Use stdout if log_file_path is unspecified
+    # If log_file_path is specified, make sure to close it
+    to_close = None
+    try:
+      if self.log_file_path:
+        self.log_file = open(self.log_file_path, "w")
+        to_close = self.log_file
+      else:
+        self.log_file = sys.stdout
+
+      self.launcher.SetOutputFile(self.log_file)
+      print("Running launcher", file=self.log_file)
+      self.launcher.Run()
+      print(
+          "Cobalt terminated. failed: " + str(self.failed), file=self.log_file)
+      # This is watched for in webdriver_benchmark_test.py
+      if not self.failed:
+        sys.stdout.write("partial_layout_benchmark TEST COMPLETE\n")
+    # pylint: disable=broad-except
+    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
+
+
+def GetCobaltExecutablePath(platform, config):
+  """Auto-derives a path to a cobalt executable."""
+  if config is None:
+    try:
+      config = os.environ["BUILD_TYPE"]
+    except KeyError:
+      sys.stderr.write("Must specify --config or --executable\n")
+      sys.exit(1)
+  script_path = os.path.realpath(inspect.getsourcefile(lambda: 0))
+  script_dir = os.path.dirname(script_path)
+  out_dir = os.path.join(script_dir, "..", "..", "out")
+  executable_directory = os.path.join(out_dir, "{}_{}".format(platform, config))
+  return os.path.join(executable_directory, "cobalt")
+
+
+def main():
+  args = arg_parser.parse_args()
+  # Keep unittest module from seeing these args
+  sys.argv = sys.argv[:1]
+
+  platform = args.platform
+  if platform is None:
+    try:
+      platform = os.environ["BUILD_PLATFORM"]
+    except KeyError:
+      sys.stderr.write("Must specify --platform\n")
+      sys.exit(1)
+
+  executable = args.executable
+  if executable is None:
+    executable = GetCobaltExecutablePath(platform, args.config)
+
+  try:
+    with CobaltRunner(platform, executable, args.devkit_name,
+                      args.log_file) as runner:
+      unittest.main(testRunner=unittest.TextTestRunner(
+          verbosity=0, stream=runner.log_file))
+  except TimeoutException:
+    print("Timeout waiting for Cobalt to start", file=sys.stderr)
+    sys.exit(1)
+
+
+if __name__ == "__main__":
+  main()
diff --git a/src/cobalt/webdriver_benchmarks/test_tv_testcase.py b/src/cobalt/webdriver_benchmarks/test_tv_testcase.py
new file mode 100755
index 0000000..742fa66
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/test_tv_testcase.py
@@ -0,0 +1,116 @@
+#!/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.
+# ==============================================================================
+"""Unit tests for tv_testcase.py."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+import unittest
+
+# This directory is a package
+sys.path.insert(0, os.path.abspath('.'))
+# pylint: disable=C6203,C6203
+# pylint: disable=g-import-not-at-top
+import tv_testcase
+
+
+class MedianPercentileTest(unittest.TestCase):
+
+  def test_empty_case(self):
+    self.assertEqual(tv_testcase._percentile([], 50), None)
+
+  def test_one_item(self):
+    self.assertEqual(tv_testcase._percentile([1], 50), 1)
+
+  def test_two_items(self):
+    self.assertAlmostEqual(tv_testcase._percentile([4, 1], 50), 2.5)
+
+  def test_three_items(self):
+    self.assertAlmostEqual(tv_testcase._percentile([4, -4, -2], 50), -2)
+
+  def test_four_items(self):
+    self.assertAlmostEqual(tv_testcase._percentile([4, 0, 1, -2], 50), 0.5)
+
+
+class PercentileTest(unittest.TestCase):
+
+  def test_one_item(self):
+    self.assertEqual(tv_testcase._percentile([1], 10), 1)
+    self.assertEqual(tv_testcase._percentile([2], 50), 2)
+    self.assertEqual(tv_testcase._percentile([3], 90), 3)
+
+  def test_two_items(self):
+    self.assertEqual(tv_testcase._percentile([2, 1], 10), 1.1)
+    self.assertEqual(tv_testcase._percentile([2, 3], 50), 2.5)
+    self.assertEqual(tv_testcase._percentile([3, 4], 90), 3.9)
+    self.assertEqual(tv_testcase._percentile([3, 4], 100), 4)
+
+  def test_five_items(self):
+    self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 10), 1.4)
+    self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 50), 3)
+    self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 90), 4.6)
+    self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 100), 5)
+
+
+class MergeDictTest(unittest.TestCase):
+
+  def test_empty_merge(self):
+    a = {'x': 4}
+    b = {}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': 4})
+
+  def test_merge_into_empty(self):
+    a = {}
+    b = {'x': 4}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': 4})
+
+  def test_merge_non_overlapping_item(self):
+    a = {'x': 4}
+    b = {'y': 5}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': 4, 'y': 5})
+
+  def test_overlapping_item_with_item(self):
+    a = {'x': 4}
+    b = {'x': 5}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': [4, 5]})
+
+  def test_overlapping_list_with_item(self):
+    a = {'x': [4]}
+    b = {'x': 5}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': [4, 5]})
+
+  def test_overlapping_list_with_list(self):
+    a = {'x': [4]}
+    b = {'x': [5]}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': [4, 5]})
+
+  def test_overlapping_item_with_list(self):
+    a = {'x': 4}
+    b = {'x': [5]}
+    tv_testcase._merge_dict(a, b)
+    self.assertEqual(a, {'x': [4, 5]})
+
+
+if __name__ == '__main__':
+  sys.exit(unittest.main())
diff --git a/src/cobalt/webdriver_benchmarks/tests/README.md b/src/cobalt/webdriver_benchmarks/tests/README.md
new file mode 100644
index 0000000..a8e30ad
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/README.md
@@ -0,0 +1,29 @@
+Cobalt Webdriver-driven Benchmarks
+---------------------
+
+This directory contains a set of webdriver-driven benchmarks
+for Cobalt.
+
+Each file should contain a set of tests in Python "unittest" format.
+
+All tests in all of the files in this directory will be run on the build system.
+Results can be recorded in the build results database.
+
+To run an individual test, simply execute a script directly (or run
+all of them via "all.py"). Platform configuration will be inferred from
+the environment if set. Otherwise, it must be specified via commandline
+parameters.
+
+To make a new test:
+
+ 1. If appropriate, create a new file borrowing the boilerplate from
+    an existing simple file, such as "shelf.py"
+
+ 2. If this file contains internal names or details, consider adding it
+    to the "EXCLUDE.FILES" list.
+
+ 3. Use the `record_result*` methods in the `tv_testcase.TvTestCase` base
+    class where appropriate.
+
+ 4. Results must be added to the build results database schema. See
+    the internal "README-Updating-Result-Schema.md" file
diff --git a/src/cobalt/webdriver_benchmarks/tests/__init__.py b/src/cobalt/webdriver_benchmarks/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/tests/all.py b/src/cobalt/webdriver_benchmarks/tests/all.py
new file mode 100755
index 0000000..78924c0
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/all.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python2
+"""Target for running all tests cases."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import importlib
+import os
+import sys
+import unittest
+
+# 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
+
+
+# pylint: disable=unused-argument
+def load_tests(loader, tests, pattern):
+  """This is a Python unittest "load_tests protocol method."""
+  s = unittest.TestSuite()
+
+  for f in os.listdir(os.path.dirname(__file__)):
+    if not f.endswith(".py"):
+      continue
+    if f.startswith("_"):
+      continue
+    if f == "all.py":
+      continue
+    s.addTest(unittest.TestLoader().loadTestsFromModule(
+        importlib.import_module("tests." + f[:-3])))
+  return s
+
+if __name__ == "__main__":
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/guide.py b/src/cobalt/webdriver_benchmarks/tests/guide.py
new file mode 100755
index 0000000..5c0a011
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/guide.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python2
+"""Simple guide navigation test."""
+
+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
+import tv_testcase
+import partial_layout_benchmark
+
+# selenium imports
+keys = partial_layout_benchmark.ImportSeleniumModule("webdriver.common.keys")
+
+REPEAT_COUNT = 10
+
+
+class GuideTest(tv_testcase.TvTestCase):
+
+  def test_simple(self):
+    layout_times_us = []
+
+    self.load_tv()
+    self.assert_displayed(tv.FOCUSED_SHELF)
+
+    for _ in xrange(REPEAT_COUNT):
+      self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_LEFT)
+      self.assert_displayed(tv.FOCUSED_GUIDE)
+      self.wait_for_layout_complete()
+      self.send_keys(tv.FOCUSED_GUIDE, keys.Keys.ARROW_RIGHT)
+      self.wait_for_layout_complete_after_focused_shelf()
+      layout_times_us.append(self.get_keyup_layout_duration_us())
+
+    self.record_result_percentile("guideTestLayout95thUs", layout_times_us, 95)
+
+
+if __name__ == "__main__":
+  tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/shelf.py b/src/cobalt/webdriver_benchmarks/tests/shelf.py
new file mode 100755
index 0000000..c73255b
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/shelf.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python2
+"""Simple shelf navigation test."""
+
+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
+import tv_testcase
+import partial_layout_benchmark
+
+# selenium imports
+keys = partial_layout_benchmark.ImportSeleniumModule("webdriver.common.keys")
+
+DEFAULT_SHELVES_COUNT = 10
+SHELF_ITEMS_COUNT = 10
+
+
+class ShelfTest(tv_testcase.TvTestCase):
+
+  def test_simple(self):
+    layout_times_us = []
+    self.load_tv()
+    self.assert_displayed(tv.FOCUSED_SHELF)
+
+    for _ in xrange(DEFAULT_SHELVES_COUNT):
+      self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_DOWN)
+      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.wait_for_layout_complete_after_focused_shelf()
+      layout_times_us.append(self.get_keyup_layout_duration_us())
+
+    self.record_result_percentile("webdriverBenchmarkShelfLayout95thUsec",
+                                  layout_times_us, 95)
+
+
+if __name__ == "__main__":
+  tv_testcase.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..e180362
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py
@@ -0,0 +1,61 @@
+#!/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_result_median('timeToShelfTestTimeShelfDisplayMedianUs',
+                              metrics_array)
+    self.record_result('timeToShelfBlankStartupTimeUs',
+                       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.py b/src/cobalt/webdriver_benchmarks/tv.py
new file mode 100644
index 0000000..f9c0411
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tv.py
@@ -0,0 +1,10 @@
+"""CSS Constants."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+FOCUSED_GUIDE = '.focused.guide'
+FOCUSED_SHELF = '.focused.selected.shelf'
+FOCUSED_SHELF_TITLE = '.focused.selected.shelf .shelf-header-title .main'
+FOCUSED_TILE = '.focused.tile'
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py
new file mode 100644
index 0000000..c9a16ca
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -0,0 +1,353 @@
+"""Base class for WebDriver tests."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import json
+import logging
+import math
+import os
+import sys
+import time
+import unittest
+
+# 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("."))
+# pylint: disable=C6204,C6203
+import partial_layout_benchmark
+import tv
+
+# selenium imports
+# pylint: disable=C0103
+WebDriverWait = partial_layout_benchmark.ImportSeleniumModule(
+    submodule="webdriver.support.ui").WebDriverWait
+
+ElementNotVisibleException = (partial_layout_benchmark.ImportSeleniumModule(
+    submodule="common.exceptions").ElementNotVisibleException)
+
+BASE_URL = "https://www.youtube.com/"
+TV_APP_PATH = "/tv"
+BASE_PARAMS = {"env_forcedOffAllExperiments": True}
+PAGE_LOAD_WAIT_SECONDS = 30
+LAYOUT_TIMEOUT_SECONDS = 5
+# Today, Cobalt's WebDriver has a race that
+# can leave the former page's DOM visible after a navigate.
+# As a workaround, sleep for a bit
+# b/33275371
+COBALT_POST_NAVIGATE_SLEEP_SECONDS = 5
+
+
+def _percentile(results, percentile):
+  """Returns the percentile of an array.
+
+  This method interpolates between two numbers if the percentile lands between
+  two data points.
+
+  Args:
+      results: Sortable results array.
+      percentile: A number ranging from 0-100.
+  Returns:
+      Appropriate value.
+  Raises:
+    RuntimeError: Raised on invalid args.
+  """
+  if not results:
+    return None
+  if percentile > 100 or percentile < 0:
+    raise RuntimeError("percentile must be 0-100")
+  sorted_results = sorted(results)
+
+  if percentile == 100:
+    return sorted_results[-1]
+  fractional, index = math.modf((len(sorted_results) - 1) * (percentile * 0.01))
+  index = int(index)
+
+  if len(sorted_results) == index + 1:
+    return sorted_results[index]
+
+  return sorted_results[index] * (1 - fractional
+                                 ) + sorted_results[index + 1] * fractional
+
+
+def _merge_dict(merge_into, merge_from):
+  """Merges the second dict into the first dict.
+
+  Merge into differs from update in that it will not override values.  If the
+  values already exist, the resulting value will be a list with a union of
+  existing and new items.
+
+  Args:
+    merge_into: An output dict to merge values into.
+    merge_from: An input dict to iterate over and insert values from.
+
+  Returns:
+    None
+  """
+  if not merge_from:
+    return
+  for k, v in merge_from.items():
+    try:
+      existing_value = merge_into[k]
+    except KeyError:
+      merge_into[k] = v
+      continue
+
+    if not isinstance(v, list):
+      v = [v]
+    if isinstance(existing_value, list):
+      existing_value.extend(v)
+    else:
+      new_value = [existing_value]
+      new_value.extend(v)
+      merge_into[k] = new_value
+
+
+class TvTestCase(unittest.TestCase):
+  """Base class for WebDriver tests.
+
+  Style note: snake_case is used for function names here so as to match
+  with an internal class with the same name.
+  """
+
+  class LayoutTimeoutException(BaseException):
+    """Exception thrown when layout did not complete in time."""
+
+  @classmethod
+  def setUpClass(cls):
+    print("Running " + cls.__name__)
+
+  @classmethod
+  def tearDownClass(cls):
+    print("Done " + cls.__name__)
+
+  def get_webdriver(self):
+    return partial_layout_benchmark.GetWebDriver()
+
+  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 without the hostname.
+      query_params: Dictionary of parameter names and values.
+    Raises:
+      Underlying WebDriver exceptions
+    """
+    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]))
+      _merge_dict(query_dict, query_params)
+    parsed_url[4] = urlencode(query_dict, doseq=True)
+    final_url = urlparse.urlunparse(parsed_url)
+    self.get_webdriver().get(final_url)
+    time.sleep(COBALT_POST_NAVIGATE_SLEEP_SECONDS)
+
+  def load_tv(self, label=None, additional_query_params=None):
+    """Loads the main TV page and waits for it to display.
+
+    Args:
+      label: A value for the label query parameter.
+      additional_query_params: A dict containing additional query parameters.
+    Raises:
+      Underlying WebDriver exceptions
+    """
+    query_params = {}
+    if label is not None:
+      query_params = {"label": label}
+    if additional_query_params is not None:
+      query_params.update(additional_query_params)
+    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.
+    #
+    # We'll skip that sophistication here.
+    self.poll_until_found(tv.FOCUSED_SHELF)
+
+  def poll_until_found(self, css_selector):
+    """Polls until an element is found.
+
+    Args:
+      css_selector: A CSS selector
+    Raises:
+      Underlying WebDriver exceptions
+    """
+    start_time = time.time()
+    while ((not self.find_elements(css_selector)) and
+           (time.time() - start_time < PAGE_LOAD_WAIT_SECONDS)):
+      time.sleep(1)
+    self.assert_displayed(css_selector)
+
+  def unique_find(self, unique_selector):
+    """Finds and returns a uniquely selected element.
+
+    Args:
+      unique_selector: A CSS selector that will select only one element
+    Raises:
+      Underlying WebDriver exceptions
+      AssertError: the element isn't unique
+    Returns:
+      Element
+    """
+    return self.find_elements(unique_selector, expected_num=1)[0]
+
+  def assert_displayed(self, css_selector):
+    """Asserts that an element is displayed.
+
+    Args:
+      css_selector: A CSS selector
+    Raises:
+      Underlying WebDriver exceptions
+      AssertError: the element isn't found
+    """
+    # TODO does not actually assert that it's visible, like webdriver.py
+    # probably does.
+    self.assertTrue(self.unique_find(css_selector))
+
+  def find_elements(self, css_selector, expected_num=None):
+    """Finds elements based on a selector.
+
+    Args:
+      css_selector: A CSS selector
+      expected_num: Expected number of matching elements
+    Raises:
+      Underlying WebDriver exceptions
+      AssertError: expected_num isn't met
+    Returns:
+      Array of selected elements
+    """
+    elements = self.get_webdriver().find_elements_by_css_selector(css_selector)
+    if expected_num is not None:
+      self.assertEqual(len(elements), expected_num)
+    return elements
+
+  def send_keys(self, css_selector, keys):
+    """Sends keys to an element uniquely identified by a selector.
+
+    This method retries for a timeout period if the selected element
+    could not be immediately found. If the retries do not succeed,
+    the underlying exception is passed through.
+
+    Args:
+      css_selector: A CSS selector
+      keys: key events
+
+    Raises:
+      Underlying WebDriver exceptions
+    """
+    start_time = time.time()
+    while True:
+      try:
+        element = self.unique_find(css_selector)
+        element.send_keys(keys)
+        return
+      except ElementNotVisibleException:
+        # TODO ElementNotVisibleException seems to be considered
+        # a "falsy" exception in the internal tests, which seems to mean
+        # it would not be retried. But here, it seems essential.
+        if time.time() - start_time >= PAGE_LOAD_WAIT_SECONDS:
+          raise
+        time.sleep(1)
+
+  def wait_for_layout_complete(self):
+    """Waits for Cobalt to complete pending layouts."""
+    start_time = time.time()
+    while self.get_int_cval("Event.MainWebModule.IsProcessing"):
+
+      if time.time() - start_time > LAYOUT_TIMEOUT_SECONDS:
+        raise TvTestCase.LayoutTimeoutException()
+
+      time.sleep(0.1)
+
+  def get_keyup_layout_duration_us(self):
+    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_result(self, name, result):
+    """Records an individual scalar result of a benchmark.
+
+    Args:
+      name: name of test case
+      result: Test result. Must be JSON encodable scalar.
+    """
+    value_to_record = result
+
+    string_value_to_record = json.JSONEncoder().encode(value_to_record)
+    print("tv_testcase RESULT: {} {}".format(name, string_value_to_record))
+
+  def record_result_median(self, name, results):
+    """Records the median of an array of results.
+
+    Args:
+      name: name of test case
+      results: Test results array. Must be array of JSON encodable scalar.
+    """
+    value_to_record = _percentile(results, 50)
+
+    string_value_to_record = json.JSONEncoder().encode(value_to_record)
+    print("tv_testcase RESULT: {} {}".format(name, string_value_to_record))
+
+  def record_result_percentile(self, name, results, percentile):
+    """Records the percentile of an array of results.
+
+    Args:
+      name: The (string) name of test case.
+      results: Test results array. Must be array of JSON encodable scalars.
+      percentile: A number ranging from 0-100.
+    Raises:
+      RuntimeError: Raised on invalid args.
+    """
+    value_to_record = _percentile(results, percentile)
+    string_value_to_record = json.JSONEncoder().encode(value_to_record)
+    print("tv_testcase RESULT: {} {}".format(name, string_value_to_record))
+
+
+def main():
+  logging.basicConfig(level=logging.DEBUG)
+  partial_layout_benchmark.main()
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index d1877a9..e514ed2 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -21,6 +21,7 @@
 #include "base/compiler_specific.h"
 #include "base/string_number_conversions.h"
 #include "base/string_util.h"
+#include "base/time.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/base/tokens.h"
@@ -361,13 +362,13 @@
   StartRequest(request_body_text);
 
   // Start the timeout timer running, if applicable.
-  send_start_time_ = base::Time::Now();
+  send_start_time_ = base::TimeTicks::Now();
   if (timeout_ms_) {
     StartTimer(base::TimeDelta());
   }
   // Timer for throttling progress events.
-  upload_last_progress_time_ = base::Time();
-  last_progress_time_ = base::Time();
+  upload_last_progress_time_ = base::TimeTicks();
+  last_progress_time_ = base::TimeTicks();
 }
 
 base::optional<std::string> XMLHttpRequest::GetResponseHeader(
@@ -531,7 +532,7 @@
   } else if (sent_) {
     // Timeout was set while request was in flight. Timeout is relative to
     // the start of the request.
-    StartTimer(base::Time::Now() - send_start_time_);
+    StartTimer(base::TimeTicks::Now() - send_start_time_);
   }
 }
 
@@ -614,7 +615,7 @@
   ChangeState(kLoading);
 
   // Send a progress notification if at least 50ms have elapsed.
-  const base::Time now = base::Time::Now();
+  const base::TimeTicks now = base::TimeTicks::Now();
   const base::TimeDelta elapsed(now - last_progress_time_);
   if (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs)) {
     last_progress_time_ = now;
@@ -657,7 +658,7 @@
 
   // Fire a progress event if either the upload just completed, or if enough
   // time has elapsed since we sent the last one.
-  const base::Time now = base::Time::Now();
+  const base::TimeTicks now = base::TimeTicks::Now();
   const base::TimeDelta elapsed(now - upload_last_progress_time_);
   if (upload_complete_ ||
       (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs))) {
@@ -702,6 +703,7 @@
       << ") " << *this << std::endl
       << script::StackTraceToString(
              settings_->global_environment()->GetStackTrace());
+  stop_timeout_ = true;
   // Step 1
   TerminateRequest();
   // Steps 2-4
@@ -735,9 +737,13 @@
   // Subtract any time that has already elapsed from the timeout.
   // This is in case the user has set a timeout after send() was already in
   // flight.
-  timer_.Start(FROM_HERE,
-               base::TimeDelta::FromMilliseconds(timeout_ms_) - time_since_send,
-               this, &XMLHttpRequest::OnTimeout);
+  base::TimeDelta delay = std::max(
+      base::TimeDelta(),
+      base::TimeDelta::FromMilliseconds(timeout_ms_) - time_since_send);
+
+  // Queue the callback even if delay ends up being zero, to preserve the
+  // previous semantics.
+  timer_.Start(FROM_HERE, delay, this, &XMLHttpRequest::OnTimeout);
 }
 
 void XMLHttpRequest::ChangeState(XMLHttpRequest::State new_state) {
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h
index d3f5aa6..83d2d7f 100644
--- a/src/cobalt/xhr/xml_http_request.h
+++ b/src/cobalt/xhr/xml_http_request.h
@@ -241,11 +241,11 @@
 
   // For handling send() timeout.
   base::OneShotTimer<XMLHttpRequest> timer_;
-  base::Time send_start_time_;
+  base::TimeTicks send_start_time_;
 
   // Time to throttle progress notifications.
-  base::Time last_progress_time_;
-  base::Time upload_last_progress_time_;
+  base::TimeTicks last_progress_time_;
+  base::TimeTicks upload_last_progress_time_;
 
   // All members requiring initialization are grouped below.
   dom::DOMSettings* settings_;
diff --git a/src/cobalt/xhr/xml_http_request_event_target.cc b/src/cobalt/xhr/xml_http_request_event_target.cc
index ef9eedb..20b9eeb 100644
--- a/src/cobalt/xhr/xml_http_request_event_target.cc
+++ b/src/cobalt/xhr/xml_http_request_event_target.cc
@@ -64,37 +64,65 @@
 
 void XMLHttpRequestEventTarget::set_onabort(
     const EventListenerScriptObject& listener) {
-  onabort_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onabort_listener_ = base::nullopt;
+  } else {
+    onabort_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::abort(), listener);
 }
 void XMLHttpRequestEventTarget::set_onerror(
     const EventListenerScriptObject& listener) {
-  onerror_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onerror_listener_ = base::nullopt;
+  } else {
+    onerror_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::error(), listener);
 }
 void XMLHttpRequestEventTarget::set_onload(
     const EventListenerScriptObject& listener) {
-  onload_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onload_listener_ = base::nullopt;
+  } else {
+    onload_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::load(), listener);
 }
 void XMLHttpRequestEventTarget::set_onloadend(
     const EventListenerScriptObject& listener) {
-  onloadend_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onloadend_listener_ = base::nullopt;
+  } else {
+    onloadend_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::loadend(), listener);
 }
 void XMLHttpRequestEventTarget::set_onloadstart(
     const EventListenerScriptObject& listener) {
-  onloadstart_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onloadstart_listener_ = base::nullopt;
+  } else {
+    onloadstart_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::loadstart(), listener);
 }
 void XMLHttpRequestEventTarget::set_onprogress(
     const EventListenerScriptObject& listener) {
-  onprogress_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    onprogress_listener_ = base::nullopt;
+  } else {
+    onprogress_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::progress(), listener);
 }
 void XMLHttpRequestEventTarget::set_ontimeout(
     const EventListenerScriptObject& listener) {
-  ontimeout_listener_.emplace(this, listener);
+  if (listener.IsNull()) {
+    ontimeout_listener_ = base::nullopt;
+  } else {
+    ontimeout_listener_.emplace(this, listener);
+  }
   SetAttributeEventListener(base::Tokens::timeout(), listener);
 }
 }  // namespace xhr
diff --git a/src/cobalt/xhr/xml_http_request_test.cc b/src/cobalt/xhr/xml_http_request_test.cc
index ac3b5c1..ae2f187 100644
--- a/src/cobalt/xhr/xml_http_request_test.cc
+++ b/src/cobalt/xhr/xml_http_request_test.cc
@@ -92,7 +92,8 @@
 
 class FakeSettings : public dom::DOMSettings {
  public:
-  FakeSettings() : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL) {}
+  FakeSettings()
+      : 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..8ac9c92
--- /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/basictypes.h"
+#include "base/logging.h"
+
+namespace {
+
+static const size_t kHeaderLength = sizeof(uint32_t);
+
+COMPILE_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();
+}
+
+scoped_ptr<std::vector<uint8_t> > ChunkedByteBuffer::PopChunk() {
+  if (chunks_.empty())
+    return scoped_ptr<std::vector<uint8_t> >().Pass();
+  scoped_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 chunk->content.Pass();
+}
+
+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..483ddb9
--- /dev/null
+++ b/src/content/browser/speech/chunked_byte_buffer.h
@@ -0,0 +1,74 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.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 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.
+  scoped_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;
+    scoped_ptr<std::vector<uint8_t> > content;
+    size_t ExpectedContentLength() const;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Chunk);
+  };
+
+  ScopedVector<Chunk> chunks_;
+  scoped_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..9b7be0f
--- /dev/null
+++ b/src/content/browser/speech/chunked_byte_buffer_unittest.cc
@@ -0,0 +1,74 @@
+// 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 <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.
+  scoped_ptr<ByteVector> chunk;
+  chunk = buffer.PopChunk().Pass();
+  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().Pass();
+  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().Pass();
+  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..e6d9941
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer.cc
@@ -0,0 +1,187 @@
+// 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.h"
+
+using base::Time;
+
+namespace {
+// 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 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_rate_(sample_rate / kFrameSize) {
+  Reset();
+
+  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>(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);
+  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 ShellAudioBus& audio_bus, float* rms_out) {
+  DCHECK_EQ(audio_bus.channels(), 1);
+
+  const size_t num_samples = audio_bus.frames();
+  const int16_t* audio_data = NULL;
+
+  ShellAudioBus int16_audio_bus(1, num_samples, ShellAudioBus::kInt16,
+                                ShellAudioBus::kInterleaved);
+
+  if (audio_bus.sample_type() == ShellAudioBus::kFloat32) {
+    int16_audio_bus.Assign(audio_bus);
+    DCHECK_EQ(int16_audio_bus.sample_type(), ShellAudioBus::kInt16);
+    audio_data =
+        reinterpret_cast<const int16_t*>(int16_audio_bus.interleaved_data());
+  } else {
+    DCHECK_EQ(audio_bus.sample_type(), ShellAudioBus::kInt16);
+    audio_data =
+        reinterpret_cast<const int16_t*>(audio_bus.interleaved_data());
+  }
+
+  EpStatus ep_status = EP_PRE_SPEECH;
+
+  // 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 + kFrameSize) <= num_samples) {
+    // Have the endpointer process the frame.
+    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;
+    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..986ed0a
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer.h
@@ -0,0 +1,157 @@
+// 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 "media/base/shell_audio_bus.h"
+
+class EpStatus;
+
+namespace content {
+
+// 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 Endpointer {
+ public:
+  typedef ::media::ShellAudioBus ShellAudioBus;
+
+  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 ShellAudioBus& audio_bus, 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_;
+  }
+
+  int sample_rate() const { return sample_rate_; }
+
+  // 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_;
+  // 1 frame = (1 / frame_rate_) second of audio.
+  int frame_rate_;
+};
+
+}  // 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..929b8ad
--- /dev/null
+++ b/src/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -0,0 +1,159 @@
+// 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/endpointer/endpointer.h"
+#include "media/base/shell_audio_bus.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.
+
+#if defined(OS_STARBOARD)
+SB_COMPILE_ASSERT(kFrameSize == 160, invalid_frame_size);
+#else
+COMPILE_ASSERT(kFrameSize == 160, invalid_frame_size);
+#endif  // defined(OS_STARBOARD)
+}  // namespace
+
+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:
+  typedef ::media::ShellAudioBus ShellAudioBus;
+  explicit EndpointerFrameProcessor(Endpointer* endpointer)
+      : endpointer_(endpointer) {}
+
+  EpStatus ProcessFrame(int64_t /*time*/,
+                        int16_t* samples,
+                        int /*frame_size*/) override {
+    scoped_ptr<ShellAudioBus> frame(new ShellAudioBus(1, kFrameSize, samples));
+    endpointer_->ProcessAudio(*frame, NULL);
+    int64_t ep_time;
+    return endpointer_->Status(&ep_time);
+  }
+
+ private:
+  Endpointer* endpointer_;
+};
+
+TEST(EndpointerTest, TestEmbeddedEndpointerEvents) {
+  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..b8a01d5
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer.cc
@@ -0,0 +1,381 @@
+// 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"
+
+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 static_cast<float>(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(static_cast<size_t>(size), init);
+}
+
+void EnergyEndpointer::HistoryRing::Insert(int64_t time_us, bool decision) {
+  decision_points_[static_cast<size_t>(insertion_index_)].time_us = time_us;
+  decision_points_[static_cast<size_t>(insertion_index_)].decision = decision;
+  insertion_index_ =
+      static_cast<int>((insertion_index_ + 1) % decision_points_.size());
+}
+
+int64_t EnergyEndpointer::HistoryRing::EndTime() const {
+  int ind = insertion_index_ - 1;
+  if (ind < 0)
+    ind = static_cast<int>(decision_points_.size() - 1);
+  return decision_points_[static_cast<size_t>(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 = static_cast<int>(decision_points_.size() - 1);
+  int64_t end_us = decision_points_[static_cast<size_t>(ind)].time_us;
+  bool is_on = decision_points_[static_cast<size_t>(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_[static_cast<size_t>(ind)].time_us > start_us) &&
+         (n_summed < decision_points_.size())) {
+    --ind;
+    if (ind < 0)
+      ind = static_cast<int>(decision_points_.size() - 1);
+    if (is_on)
+      sum_us += end_us - decision_points_[static_cast<size_t>(ind)].time_us;
+    is_on = decision_points_[static_cast<size_t>(ind)].decision;
+    end_us = decision_points_[static_cast<size_t>(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;
+
+      case EP_POST_SPEECH:
+        // fall-through
+      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..2d379b9
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer.h
@@ -0,0 +1,160 @@
+// 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/memory/scoped_ptr.h"
+#include "content/browser/speech/endpointer/energy_endpointer_params.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 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.
+  scoped_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..303b3b0
--- /dev/null
+++ b/src/content/browser/speech/endpointer/energy_endpointer_params.h
@@ -0,0 +1,135 @@
+// 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_
+
+namespace content {
+
+// Input parameters for the EnergyEndpointer class.
+class 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/content/browser/speech/speech.gyp b/src/content/browser/speech/speech.gyp
new file mode 100644
index 0000000..2db9fdf
--- /dev/null
+++ b/src/content/browser/speech/speech.gyp
@@ -0,0 +1,60 @@
+# 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': 'speech',
+      'type': 'static_library',
+      'sources': [
+        'chunked_byte_buffer.cc',
+        'chunked_byte_buffer.h',
+        'endpointer/endpointer.cc',
+        'endpointer/endpointer.h',
+        'endpointer/energy_endpointer.cc',
+        'endpointer/energy_endpointer.h',
+        'endpointer/energy_endpointer_params.cc',
+        'endpointer/energy_endpointer_params.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/media/media.gyp:media',
+      ],
+    },
+    {
+      'target_name': 'speech_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'chunked_byte_buffer_unittest.cc',
+        'endpointer/endpointer_unittest.cc',
+      ],
+      'dependencies': [
+        'speech',
+        '<(DEPTH)/base/base.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+      ],
+    },
+
+    {
+      'target_name': 'speech_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'speech_test',
+      ],
+      'variables': {
+        'executable_name': 'speech_test',
+      },
+      'includes': [ '../../../starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/glimp/egl/surface_impl.h b/src/glimp/egl/surface_impl.h
index ead72c4..fc026c3 100644
--- a/src/glimp/egl/surface_impl.h
+++ b/src/glimp/egl/surface_impl.h
@@ -32,6 +32,10 @@
   virtual int GetWidth() const = 0;
   virtual int GetHeight() const = 0;
 
+  // Returns true if the surface is a window surface, false if the surface is a
+  // pixel buffer or a pixmap.
+  virtual bool IsWindowSurface() const = 0;
+
  private:
 };
 
diff --git a/src/glimp/entry_points/gles_2_0.cc b/src/glimp/entry_points/gles_2_0.cc
index 10391ca..884318a 100644
--- a/src/glimp/entry_points/gles_2_0.cc
+++ b/src/glimp/entry_points/gles_2_0.cc
@@ -281,7 +281,12 @@
 }
 
 void GL_APIENTRY glCullFace(GLenum mode) {
-  SB_NOTIMPLEMENTED();
+  gles::Context* context = GetCurrentContext();
+  if (!context) {
+    return;
+  }
+
+  return context->CullFace(mode);
 }
 
 void GL_APIENTRY glDeleteBuffers(GLsizei n, const GLuint* buffers) {
diff --git a/src/glimp/gles/buffer.cc b/src/glimp/gles/buffer.cc
index c0975cd..5e03c06 100644
--- a/src/glimp/gles/buffer.cc
+++ b/src/glimp/gles/buffer.cc
@@ -38,12 +38,7 @@
 }  // namespace
 
 Buffer::Buffer(nb::scoped_ptr<BufferImpl> impl)
-    : impl_(impl.Pass()), target_valid_(false), size_in_bytes_(0) {}
-
-void Buffer::SetTarget(GLenum target) {
-  target_ = target;
-  target_valid_ = true;
-}
+    : impl_(impl.Pass()), size_in_bytes_(0) {}
 
 void Buffer::Allocate(GLenum usage, size_t size) {
   size_in_bytes_ = size;
@@ -51,7 +46,6 @@
 }
 
 void Buffer::SetData(GLintptr offset, GLsizeiptr size, const GLvoid* data) {
-  SB_DCHECK(target_valid());
   SB_DCHECK(size_in_bytes_ >= offset + size);
   SB_DCHECK(offset >= 0);
   SB_DCHECK(size >= 0);
diff --git a/src/glimp/gles/buffer.h b/src/glimp/gles/buffer.h
index 71b18ce..85cb462 100644
--- a/src/glimp/gles/buffer.h
+++ b/src/glimp/gles/buffer.h
@@ -30,9 +30,6 @@
  public:
   explicit Buffer(nb::scoped_ptr<BufferImpl> impl);
 
-  // Called when glBindBuffer() is called.
-  void SetTarget(GLenum target);
-
   // Allocates memory within this Buffer object.
   void Allocate(GLenum usage, size_t size);
 
@@ -47,16 +44,6 @@
   // Returns true if the buffer is currently mapped to the CPU address space.
   bool is_mapped() const { return is_mapped_; }
 
-  // Returns true if the target has been set (e.g. via glBindBuffer()).
-  bool target_valid() const { return target_valid_; }
-
-  // Returns the target (set via glBindBuffer()).  Must be called only if
-  // target_valid() is true.
-  GLenum target() const {
-    SB_DCHECK(target_valid_);
-    return target_;
-  }
-
   GLsizeiptr size_in_bytes() const { return size_in_bytes_; }
 
   BufferImpl* impl() const { return impl_.get(); }
@@ -67,13 +54,6 @@
 
   nb::scoped_ptr<BufferImpl> impl_;
 
-  // The target type this buffer was last bound as, set through a call to
-  // glBindBuffer().
-  GLenum target_;
-
-  // Represents whether or not target_ as been initialized yet.
-  bool target_valid_;
-
   // The size of the allocated memory used by this buffer.
   GLsizeiptr size_in_bytes_;
 
diff --git a/src/glimp/gles/context.cc b/src/glimp/gles/context.cc
index a104230..65b7f47 100644
--- a/src/glimp/gles/context.cc
+++ b/src/glimp/gles/context.cc
@@ -21,6 +21,7 @@
 #include "glimp/egl/error.h"
 #include "glimp/egl/surface.h"
 #include "glimp/gles/blend_state.h"
+#include "glimp/gles/cull_face_state.h"
 #include "glimp/gles/draw_mode.h"
 #include "glimp/gles/index_data_type.h"
 #include "glimp/gles/pixel_format.h"
@@ -299,9 +300,13 @@
       draw_state_.scissor.enabled = true;
       draw_state_dirty_flags_.scissor_dirty = true;
       break;
+    case GL_CULL_FACE:
+      draw_state_.cull_face_state.enabled = true;
+      draw_state_.cull_face_state.mode = CullFaceState::kBack;
+      draw_state_dirty_flags_.cull_face_dirty = true;
+      break;
     case GL_DEPTH_TEST:
     case GL_DITHER:
-    case GL_CULL_FACE:
     case GL_STENCIL_TEST:
     case GL_POLYGON_OFFSET_FILL:
     case GL_SAMPLE_ALPHA_TO_COVERAGE:
@@ -323,9 +328,12 @@
       draw_state_.scissor.enabled = false;
       draw_state_dirty_flags_.scissor_dirty = true;
       break;
+    case GL_CULL_FACE:
+      draw_state_.cull_face_state.enabled = false;
+      draw_state_dirty_flags_.cull_face_dirty = true;
+      break;
     case GL_DEPTH_TEST:
     case GL_DITHER:
-    case GL_CULL_FACE:
     case GL_STENCIL_TEST:
     case GL_POLYGON_OFFSET_FILL:
     case GL_SAMPLE_ALPHA_TO_COVERAGE:
@@ -426,6 +434,31 @@
   draw_state_dirty_flags_.blend_state_dirty = true;
 }
 
+namespace {
+CullFaceState::Mode CullFaceModeFromEnum(GLenum mode) {
+  switch (mode) {
+    case GL_FRONT:
+      return CullFaceState::kFront;
+    case GL_BACK:
+      return CullFaceState::kBack;
+    case GL_FRONT_AND_BACK:
+      return CullFaceState::kFrontAndBack;
+    default:
+      return CullFaceState::kModeInvalid;
+  }
+}
+}  // namespace
+
+void Context::CullFace(GLenum mode) {
+  CullFaceState::Mode cull_face_mode = CullFaceModeFromEnum(mode);
+  if (cull_face_mode == CullFaceState::kModeInvalid) {
+    SetError(GL_INVALID_ENUM);
+    return;
+  }
+  draw_state_.cull_face_state.mode = cull_face_mode;
+  draw_state_dirty_flags_.cull_face_dirty = true;
+}
+
 GLuint Context::CreateProgram() {
   GLIMP_TRACE_EVENT0(__FUNCTION__);
   nb::scoped_ptr<ProgramImpl> program_impl = impl_->CreateProgram();
@@ -661,10 +694,18 @@
       buffer_object->Unmap();
     }
 
-    // If a bound buffer is deleted, set the bound buffer to NULL.
-    if (buffer_object->target_valid()) {
-      *GetBoundBufferForTarget(buffer_object->target()) =
-          nb::scoped_refptr<Buffer>();
+    // If a bound buffer is deleted, set the bound buffer to NULL. The buffer
+    // may be bound to any target, therefore we must scan them all.
+    const GLenum buffer_targets[3] = {GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
+                                      GL_PIXEL_UNPACK_BUFFER};
+    for (int target_index = 0; target_index < SB_ARRAY_SIZE(buffer_targets);
+         ++target_index) {
+      GLenum target = buffer_targets[target_index];
+      nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target);
+      SB_DCHECK(bound_buffer);
+      if ((*bound_buffer).get() == buffer_object.get()) {
+        *bound_buffer = NULL;
+      }
     }
   }
 }
@@ -697,25 +738,20 @@
     return;
   }
 
-  if (buffer == 0) {
-    // Unbind the current buffer if 0 is passed in for buffer.
-    *GetBoundBufferForTarget(target) = NULL;
-    return;
+  nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target);
+  SB_DCHECK(bound_buffer);
+  nb::scoped_refptr<Buffer> buffer_object;
+  if (buffer != 0) {
+    buffer_object = resource_manager_->GetBuffer(buffer);
+    if (!buffer_object) {
+      // The buffer to be bound is invalid.
+      SB_NOTIMPLEMENTED()
+          << "Creating buffers with glBindBuffer () not supported";
+      return;
+    }
   }
 
-  nb::scoped_refptr<Buffer> buffer_object =
-      resource_manager_->GetBuffer(buffer);
-
-  if (!buffer_object) {
-    // According to the specification, no error is generated if the buffer is
-    // invalid.
-    //   https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml
-    SB_DLOG(WARNING) << "Could not glBindBuffer() to invalid buffer.";
-    return;
-  }
-
-  buffer_object->SetTarget(target);
-  *GetBoundBufferForTarget(target) = buffer_object;
+  *bound_buffer = buffer_object;
 }
 
 void Context::BufferData(GLenum target,
@@ -961,11 +997,12 @@
   return NULL;
 }
 
-nb::scoped_refptr<Texture>* Context::GetBoundTextureForTarget(GLenum target) {
+nb::scoped_refptr<Texture>* Context::GetBoundTextureForTarget(GLenum target,
+                                                              GLenum texture) {
   GLIMP_TRACE_EVENT0(__FUNCTION__);
   switch (target) {
     case GL_TEXTURE_2D:
-      return &(texture_units_[active_texture_ - GL_TEXTURE0]);
+      return &(texture_units_[texture - GL_TEXTURE0]);
     case GL_TEXTURE_CUBE_MAP:
       SB_NOTREACHED() << "Currently unimplemented in glimp.";
       return NULL;
@@ -1025,7 +1062,6 @@
       // Silently ignore 0 textures.
       continue;
     }
-
     nb::scoped_refptr<Texture> texture_object =
         resource_manager_->DeregisterTexture(textures[i]);
 
@@ -1036,10 +1072,18 @@
       return;
     }
 
-    // If a bound texture is deleted, set the bound texture to NULL.
-    if (texture_object->target_valid()) {
-      *GetBoundTextureForTarget(texture_object->target()) = NULL;
-      enabled_textures_dirty_ = true;
+    // If a bound texture is deleted, set the bound texture to NULL. The texture
+    // may be bound to multiple texture units, including texture units that are
+    // not active, therefore we must scan them all.
+    for (int texture_index = 0;
+         texture_index < impl_->GetMaxFragmentTextureUnits(); ++texture_index) {
+      GLenum texture_unit = texture_index + GL_TEXTURE0;
+      nb::scoped_refptr<Texture>* bound_texture =
+          GetBoundTextureForTarget(GL_TEXTURE_2D, texture_unit);
+      if ((*bound_texture).get() == texture_object.get()) {
+        enabled_textures_dirty_ = true;
+        *bound_texture = NULL;
+      }
     }
   }
 }
@@ -1062,25 +1106,26 @@
     return;
   }
 
-  if (texture == 0) {
-    // Unbind the current texture if 0 is passed in for texture.
-    *GetBoundTextureForTarget(target) = NULL;
-    enabled_textures_dirty_ = true;
-    return;
+  nb::scoped_refptr<Texture>* bound_texture =
+      GetBoundTextureForTarget(target, active_texture_);
+  SB_DCHECK(bound_texture);
+  nb::scoped_refptr<Texture> texture_object;
+  if (texture != 0) {
+    texture_object = resource_manager_->GetTexture(texture);
+    if (!texture_object) {
+      // The texture to be bound is invalid.
+      SB_NOTIMPLEMENTED()
+          << "Creating textures with glBindTexture() not supported";
+      return;
+    }
   }
 
-  nb::scoped_refptr<Texture> texture_object =
-      resource_manager_->GetTexture(texture);
-
-  if (!texture_object) {
-    // According to the specification, no error is generated if the texture is
-    // invalid.
-    //   https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindTexture.xml
+  if ((*bound_texture).get() == texture_object.get()) {
+    // The new texture being bound is the same as the already the bound
+    // texture.
     return;
   }
-
-  texture_object->SetTarget(target);
-  *GetBoundTextureForTarget(target) = texture_object;
+  *bound_texture = texture_object;
   enabled_textures_dirty_ = true;
 }
 
@@ -1131,8 +1176,8 @@
 
 void Context::TexParameteri(GLenum target, GLenum pname, GLint param) {
   GLIMP_TRACE_EVENT0(__FUNCTION__);
-  Sampler* active_sampler =
-      (*GetBoundTextureForTarget(target))->sampler_parameters();
+  Sampler* active_sampler = (*GetBoundTextureForTarget(target, active_texture_))
+                                ->sampler_parameters();
 
   switch (pname) {
     case GL_TEXTURE_MAG_FILTER: {
@@ -1264,7 +1309,8 @@
   SB_DCHECK(pixel_format != kPixelFormatInvalid)
       << "Pixel format not supported by glimp.";
 
-  nb::scoped_refptr<Texture> texture_object = *GetBoundTextureForTarget(target);
+  nb::scoped_refptr<Texture> texture_object =
+      *GetBoundTextureForTarget(target, active_texture_);
   if (!texture_object) {
     // According to the specification, no error is generated if no texture
     // is bound.
@@ -1332,7 +1378,8 @@
   SB_DCHECK(pixel_format != kPixelFormatInvalid)
       << "Pixel format not supported by glimp.";
 
-  nb::scoped_refptr<Texture> texture_object = *GetBoundTextureForTarget(target);
+  nb::scoped_refptr<Texture> texture_object =
+      *GetBoundTextureForTarget(target, active_texture_);
   if (!texture_object) {
     // According to the specification, no error is generated if no texture
     // is bound.
@@ -2083,8 +2130,14 @@
 
 void Context::SwapBuffers() {
   GLIMP_TRACE_EVENT0(__FUNCTION__);
-  Flush();
-  impl_->SwapBuffers(default_draw_framebuffer_->color_attachment_surface());
+  egl::Surface* surface = default_draw_framebuffer_->color_attachment_surface();
+  // If surface is a pixel buffer or a pixmap, eglSwapBuffers has no effect, and
+  // no error is generated.
+  //   https://www.khronos.org/registry/egl/sdk/docs/man/html/eglSwapBuffers.xhtml
+  if (surface->impl()->IsWindowSurface()) {
+    Flush();
+    impl_->SwapBuffers(surface);
+  }
 }
 
 bool Context::BindTextureToEGLSurface(egl::Surface* surface) {
@@ -2092,7 +2145,7 @@
   SB_DCHECK(surface->GetTextureTarget() == EGL_TEXTURE_2D);
 
   const nb::scoped_refptr<Texture>& current_texture =
-      *GetBoundTextureForTarget(GL_TEXTURE_2D);
+      *GetBoundTextureForTarget(GL_TEXTURE_2D, active_texture_);
 
   if (!current_texture) {
     SB_DLOG(WARNING) << "No texture is currently bound during call to "
diff --git a/src/glimp/gles/context.h b/src/glimp/gles/context.h
index e028ed5..018a2f2 100644
--- a/src/glimp/gles/context.h
+++ b/src/glimp/gles/context.h
@@ -95,6 +95,8 @@
 
   void BlendFunc(GLenum sfactor, GLenum dfactor);
 
+  void CullFace(GLenum mode);
+
   GLuint CreateProgram();
   void DeleteProgram(GLuint program);
   void AttachShader(GLuint program, GLuint shader);
@@ -232,15 +234,16 @@
   void ReleaseContext();
   void SetError(GLenum error) { error_ = error; }
 
-  // Returns the bound buffer slot for the specific specified target.
+  // Returns the bound buffer for the specific specified target.
   // This returns a pointer because it is used by glBindBuffer() which modifies
   // what the returned scoped_refptr points to.
   nb::scoped_refptr<Buffer>* GetBoundBufferForTarget(GLenum target);
 
-  // Returns the bound texture slot for the specific specified target.
+  // Returns the bound texture for the specific specified target and slot.
   // This returns a pointer because it is used by glBindTexture() which modifies
   // what the returned scoped_refptr points to.
-  nb::scoped_refptr<Texture>* GetBoundTextureForTarget(GLenum target);
+  nb::scoped_refptr<Texture>* GetBoundTextureForTarget(GLenum target,
+                                                       GLenum texture);
 
   void SetupExtensionsString();
 
diff --git a/src/glimp/gles/cull_face_state.h b/src/glimp/gles/cull_face_state.h
new file mode 100644
index 0000000..b68d6b0
--- /dev/null
+++ b/src/glimp/gles/cull_face_state.h
@@ -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.
+ */
+
+#ifndef GLIMP_GLES_CULL_FACE_STATE_H_
+#define GLIMP_GLES_CULL_FACE_STATE_H_
+
+namespace glimp {
+namespace gles {
+
+// Describes GL cull face state, which can be modified via commands like
+// glCullFace().
+//   https://www.khronos.org/opengles/sdk/docs/man/xhtml/glCullFace.xml
+struct CullFaceState {
+  // When enabled, the initial face culling value should be kBack.
+  enum Mode {
+    kFront,
+    kBack,
+    kFrontAndBack,
+    kModeInvalid,
+  };
+
+  // Default state is face culling off.
+  CullFaceState() : enabled(false), mode(kBack) {}
+
+  Mode mode;
+  bool enabled;
+};
+
+}  // namespace gles
+}  // namespace glimp
+
+#endif  // GLIMP_GLES_CULL_FACE_STATE_H_
diff --git a/src/glimp/gles/draw_state.h b/src/glimp/gles/draw_state.h
index 96f5ec1..db9c2d9 100644
--- a/src/glimp/gles/draw_state.h
+++ b/src/glimp/gles/draw_state.h
@@ -24,6 +24,7 @@
 #include "glimp/egl/surface.h"
 #include "glimp/gles/blend_state.h"
 #include "glimp/gles/buffer.h"
+#include "glimp/gles/cull_face_state.h"
 #include "glimp/gles/framebuffer.h"
 #include "glimp/gles/program.h"
 #include "glimp/gles/sampler.h"
@@ -156,6 +157,9 @@
   // existing pixels in the output framebuffer.
   BlendState blend_state;
 
+  // Defines whether face culling is enabled, and upon which face if so.
+  CullFaceState cull_face_state;
+
   // The currently bound array buffer, set by calling
   // glBindBuffer(GL_ARRAY_BUFFER).
   nb::scoped_refptr<Buffer> array_buffer;
@@ -190,6 +194,7 @@
     scissor_dirty = true;
     viewport_dirty = true;
     blend_state_dirty = true;
+    cull_face_dirty = true;
     array_buffer_dirty = true;
     element_array_buffer_dirty = true;
     used_program_dirty = true;
@@ -205,6 +210,7 @@
   bool scissor_dirty;
   bool viewport_dirty;
   bool blend_state_dirty;
+  bool cull_face_dirty;
   bool array_buffer_dirty;
   bool element_array_buffer_dirty;
   bool used_program_dirty;
diff --git a/src/glimp/gles/program.cc b/src/glimp/gles/program.cc
index ff02fbe..0309920 100644
--- a/src/glimp/gles/program.cc
+++ b/src/glimp/gles/program.cc
@@ -163,7 +163,7 @@
 // Clear all stored uniform information and values.
 void Program::ClearUniforms() {
   for (size_t i = 0; i < uniforms_.size(); ++i) {
-    SbMemoryFree(uniforms_[i].data);
+    SbMemoryDeallocate(uniforms_[i].data);
   }
   uniforms_.clear();
   active_uniform_locations_.clear();
@@ -206,7 +206,7 @@
     // We need to reallocate data if the information has changed.
     uniform->info = new_info;
 
-    SbMemoryFree(uniform->data);
+    SbMemoryDeallocate(uniform->data);
     uniform->data = SbMemoryAllocate(DataSizeForType(count, elem_size, type));
   }
   SbMemoryCopy(uniform->data, v, DataSizeForType(count, elem_size, type));
diff --git a/src/glimp/gles/texture.cc b/src/glimp/gles/texture.cc
index a057f74..0e1ea8c 100644
--- a/src/glimp/gles/texture.cc
+++ b/src/glimp/gles/texture.cc
@@ -24,15 +24,9 @@
 
 Texture::Texture(nb::scoped_ptr<TextureImpl> impl)
     : impl_(impl.Pass()),
-      target_valid_(false),
       texture_allocated_(false),
       bound_to_surface_(NULL) {}
 
-void Texture::SetTarget(GLenum target) {
-  target_ = target;
-  target_valid_ = true;
-}
-
 void Texture::Initialize(GLint level,
                          PixelFormat pixel_format,
                          GLsizei width,
diff --git a/src/glimp/gles/texture.h b/src/glimp/gles/texture.h
index 1d4bcc5..9c530b4 100644
--- a/src/glimp/gles/texture.h
+++ b/src/glimp/gles/texture.h
@@ -34,9 +34,6 @@
  public:
   explicit Texture(nb::scoped_ptr<TextureImpl> impl);
 
-  // Called when glBindTexture() is called.
-  void SetTarget(GLenum target);
-
   void Initialize(GLint level,
                   PixelFormat pixel_format,
                   GLsizei width,
@@ -82,16 +79,6 @@
   // or not.
   bool CanBeAttachedToFramebuffer() const;
 
-  // Returns true if the target has been set (e.g. via glBindTexture()).
-  bool target_valid() const { return target_valid_; }
-
-  // Returns the target (set via glBindTexture()).  Must be called only if
-  // target_valid() is true.
-  GLenum target() const {
-    SB_DCHECK(target_valid_);
-    return target_;
-  }
-
   TextureImpl* impl() const { return impl_.get(); }
 
   // Returns whether the data has been set yet or not.
@@ -121,13 +108,6 @@
 
   nb::scoped_ptr<TextureImpl> impl_;
 
-  // The target type this texture was last bound as, set through a call to
-  // glBindTexture().
-  GLenum target_;
-
-  // Represents whether or not target_ as been initialized yet.
-  bool target_valid_;
-
   // True if underlying texture data has been allocated yet or not (e.g.
   // will be true after glTexImage2D() is called.)
   bool texture_allocated_;
diff --git a/src/glimp/glimp_common.gypi b/src/glimp/glimp_common.gypi
index 4f00bfe..ef1a68c 100644
--- a/src/glimp/glimp_common.gypi
+++ b/src/glimp/glimp_common.gypi
@@ -45,6 +45,7 @@
     'gles/context_impl.h',
     'gles/convert_pixel_data.cc',
     'gles/convert_pixel_data.h',
+    'gles/cull_face_state.h',
     'gles/draw_mode.h',
     'gles/draw_state.cc',
     'gles/draw_state.h',
diff --git a/src/media/audio/shell_audio_streamer_starboard.cc b/src/media/audio/shell_audio_streamer_starboard.cc
index ddcc139..22a143f 100644
--- a/src/media/audio/shell_audio_streamer_starboard.cc
+++ b/src/media/audio/shell_audio_streamer_starboard.cc
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-#include "media/audio/null_audio_streamer.h"
-
-#include "base/logging.h"
+#include "media/audio/shell_audio_streamer.h"
 
 namespace media {
 
@@ -25,7 +23,7 @@
 void ShellAudioStreamer::Terminate() {}
 
 ShellAudioStreamer* ShellAudioStreamer::Instance() {
-  return NullAudioStreamer::GetInstance();
+  return NULL;
 }
 
 }  // namespace media
diff --git a/src/media/base/sbplayer_pipeline.cc b/src/media/base/sbplayer_pipeline.cc
index 64497b8..ff21883 100644
--- a/src/media/base/sbplayer_pipeline.cc
+++ b/src/media/base/sbplayer_pipeline.cc
@@ -521,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);
   }
 }
 
@@ -595,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;
   }
 
@@ -757,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 6c43f8c..5beaf2c 100644
--- a/src/media/base/shell_audio_bus.h
+++ b/src/media/base/shell_audio_bus.h
@@ -59,6 +59,8 @@
 
   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;
@@ -101,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);
@@ -109,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 2708468..b62db8a 100644
--- a/src/media/base/starboard_player.cc
+++ b/src/media/base/starboard_player.cc
@@ -290,11 +290,15 @@
   SbMediaVideoCodec video_codec =
       MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
 
-  player_ = SbPlayerCreate(window_, video_codec, audio_codec,
-                           SB_PLAYER_NO_DURATION, drm_system_, &audio_header,
-                           &StarboardPlayer::DeallocateSampleCB,
-                           &StarboardPlayer::DecoderStatusCB,
-                           &StarboardPlayer::PlayerStatusCB, this);
+  player_ = SbPlayerCreate(
+      window_, video_codec, audio_codec, SB_PLAYER_NO_DURATION, drm_system_,
+      &audio_header, &StarboardPlayer::DeallocateSampleCB,
+      &StarboardPlayer::DecoderStatusCB, &StarboardPlayer::PlayerStatusCB, this
+#if SB_VERSION(3)
+      ,
+      NULL  // provider
+#endif
+      );
   set_bounds_helper_->SetPlayer(this);
 }
 
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/media/media.gyp b/src/media/media.gyp
index 12ebe12..4f6d3d0 100644
--- a/src/media/media.gyp
+++ b/src/media/media.gyp
@@ -431,6 +431,14 @@
           'dependencies': [
             '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
             'shared_memory_support',
+          ],
+        }],
+        ['OS != "ios" and cobalt != 1', {
+          'dependencies': [
+            # This is only used in [x11|gl|skcanvas]_video_renderer.cc, but not
+            # when compiling Cobalt. When linking as a shared library, the
+            # unused optimized YUV functions are left as undefined symbols
+            # after they get dead-stripped.
             'yuv_convert',
           ],
         }],
diff --git a/src/nb/allocator.h b/src/nb/allocator.h
index c5ddf7c..75ac4ed 100644
--- a/src/nb/allocator.h
+++ b/src/nb/allocator.h
@@ -42,14 +42,21 @@
   // Will return NULL if the allocation fails.
   virtual void* Allocate(std::size_t size, std::size_t alignment) = 0;
 
-  // Allocates a range of memory of the given size for the given alignment.
-  // Returns a pointer that may not be aligned but points to a memory area that
-  // is still large enough when the pointer is subsequently aligned to the given
-  // alignment. It can allocate up to size + alignment - 1 bytes. This allows a
-  // reuse allocator to use the padding area as a free block. Will return NULL
-  // if the allocation fails.
-  virtual void* AllocateForAlignment(std::size_t size,
-                                     std::size_t alignment) = 0;
+  // When supported, allocates a range of memory of the given size for the given
+  // alignment. Returns a pointer that may not be aligned but points to a memory
+  // area that is still large enough when the pointer is subsequently aligned to
+  // the given alignment. This allows a reuse allocator to use the padding area
+  // as a free block. |size| will be set to the actual size allocated on
+  // successful allocations. Will return NULL if the allocation fails. In the
+  // case that the underlying block size is inconvenient or impossible to be
+  // retreived, the |size| can remain unchanged for a successful allocation. The
+  // user may lose the ability to combine two adjacent allocations in this case.
+  // Note that the coding style recommends that in/out parameters to be placed
+  // after input parameters but |size| is kept in the left for consistency.
+  virtual void* AllocateForAlignment(std::size_t* /*size*/,
+                                     std::size_t /*alignment*/) {
+    return 0;
+  }
 
   // Frees memory previously allocated via any call to Allocate().
   virtual void Free(void* memory) = 0;
diff --git a/src/nb/allocator_decorator.cc b/src/nb/allocator_decorator.cc
index daa5320..18578b1 100644
--- a/src/nb/allocator_decorator.cc
+++ b/src/nb/allocator_decorator.cc
@@ -68,7 +68,7 @@
   return impl_->Allocate(size, alignment);
 }
 
-void* AllocatorDecorator::AllocateForAlignment(std::size_t size,
+void* AllocatorDecorator::AllocateForAlignment(std::size_t* size,
                                                std::size_t alignment) {
   ScopedLock scoped_lock(lock_);
   return impl_->AllocateForAlignment(size, alignment);
diff --git a/src/nb/allocator_decorator.h b/src/nb/allocator_decorator.h
index 13969ad..78a8407 100644
--- a/src/nb/allocator_decorator.h
+++ b/src/nb/allocator_decorator.h
@@ -34,7 +34,7 @@
 
   void* Allocate(std::size_t size);
   void* Allocate(std::size_t size, std::size_t alignment);
-  void* AllocateForAlignment(std::size_t size, std::size_t alignment);
+  void* AllocateForAlignment(std::size_t* size, std::size_t alignment);
   void Free(void* memory);
   std::size_t GetCapacity() const;
   std::size_t GetAllocated() const;
diff --git a/src/nb/analytics/memory_tracker.cc b/src/nb/analytics/memory_tracker.cc
new file mode 100644
index 0000000..56471dd
--- /dev/null
+++ b/src/nb/analytics/memory_tracker.cc
@@ -0,0 +1,67 @@
+/*
+ * 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/analytics/memory_tracker.h"
+
+#include "nb/analytics/memory_tracker_impl.h"
+#include "nb/scoped_ptr.h"
+#include "starboard/once.h"
+
+namespace nb {
+namespace analytics {
+namespace {
+SB_ONCE_INITIALIZE_FUNCTION(MemoryTrackerImpl, GetMemoryTrackerImplSingleton);
+}  // namespace
+
+MemoryTracker* MemoryTracker::Get() {
+  MemoryTracker* t = GetMemoryTrackerImplSingleton();
+  return t;
+}
+
+
+MemoryStats GetProcessMemoryStats() {
+  MemoryStats memory_stats;
+
+  memory_stats.total_cpu_memory = SbSystemGetTotalCPUMemory();
+  memory_stats.used_cpu_memory = SbSystemGetUsedCPUMemory();
+
+  if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
+    int64_t used_gpu_memory = SbSystemGetUsedGPUMemory();
+    memory_stats.total_gpu_memory = SbSystemGetTotalGPUMemory();
+    memory_stats.used_gpu_memory = SbSystemGetUsedGPUMemory();
+  }
+  return memory_stats;
+}
+
+nb::scoped_ptr<MemoryTrackerPrintThread>
+CreateDebugPrintThread(MemoryTracker* memory_tracker) {
+  return nb::scoped_ptr<MemoryTrackerPrintThread>(
+     new MemoryTrackerPrintThread(memory_tracker));
+}
+
+nb::scoped_ptr<MemoryTrackerPrintCSVThread>
+CreateDebugPrintCSVThread(MemoryTracker* memory_tracker,
+                          int sample_interval_ms,
+                          int total_sampling_time_ms) {
+  return nb::scoped_ptr<MemoryTrackerPrintCSVThread>(
+      new MemoryTrackerPrintCSVThread(
+          memory_tracker,
+          sample_interval_ms,
+          total_sampling_time_ms));
+}
+
+}  // namespace analytics
+}  // namespace nb
diff --git a/src/nb/analytics/memory_tracker.h b/src/nb/analytics/memory_tracker.h
new file mode 100644
index 0000000..7d1c332
--- /dev/null
+++ b/src/nb/analytics/memory_tracker.h
@@ -0,0 +1,169 @@
+/*
+ * 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_MEMORY_TRACKER_H_
+#define NB_MEMORY_TRACKER_H_
+
+#include <vector>
+#include "starboard/configuration.h"
+#include "starboard/types.h"
+#include "nb/scoped_ptr.h"
+
+namespace nb {
+
+namespace analytics {
+
+class MemoryTracker;
+class MemoryTrackerPrintThread;
+class MemoryTrackerPrintCSVThread;
+class AllocationVisitor;
+class AllocationGroup;
+class AllocationRecord;
+
+struct MemoryStats {
+  MemoryStats() : total_cpu_memory(0), used_cpu_memory(0),
+                  total_gpu_memory(0), used_gpu_memory(0) {}
+  int64_t total_cpu_memory;
+  int64_t used_cpu_memory;
+  int64_t total_gpu_memory;
+  int64_t used_gpu_memory;
+};
+
+MemoryStats GetProcessMemoryStats();
+
+// Creates a MemoryTracker instance that implements the
+//  MemoryTracker. Once the instance is created it can begin tracking
+//  system allocations by calling InstallGlobalTrackingHooks().
+//  Deleting the MemoryTracker is forbidden.
+//
+// Example, Creation and Hooking:
+//   static MemoryTracker* s_global_tracker =
+//       GetOrCreateMemoryTracker();
+//   s_global_tracker->InstallGlobalTrackingHooks();  // now tracking memory.
+//
+// Data about the allocations are aggregated under AllocationGroups and it's
+//  recommended that GetAllocationGroups(...) is used to get simple allocation
+//  statistics.
+//
+// Deeper analytics are possible by creating an AllocationVisitor subclass and
+//  traversing through the internal allocations of the tracker. In this way all
+//  known information about allocation state of the program is made accessible.
+//  The visitor does not need to perform any locking as this is guaranteed by
+//  the MemoryTracker.
+//
+// Example (AllocationVisitor):
+//  MyAllocation visitor = ...;
+//  s_global_tracker->Accept(&visitor);
+//  visitor.PrintAllocations();
+//
+// Performance:
+//  1) Gold builds disallow memory tracking and therefore have zero-cost
+//     for this feature.
+//  2) All other builds that allow memory tracking have minimal cost as long
+//     as memory tracking has not been activated. This is facilitated by NOT
+//     using locks, at the expense of thread safety during teardown (hence the
+//     reason why you should NOT delete a memory tracker with hooks installed).
+//  3) When the memory tracking has been activated then there is a non-trivial
+//     performance cost in terms of CPU and memory for the feature.
+class MemoryTracker {
+ public:
+  // Gets the singleton instance of the default MemoryTracker. This
+  // is created the first time it is used.
+  static MemoryTracker* Get();
+
+  MemoryTracker() {}
+  virtual bool InstallGlobalTrackingHooks() = 0;
+
+  // It's recommended the MemoryTracker is never removed or deleted during the
+  // runtime.
+  virtual void RemoveGlobalTrackingHooks() = 0;
+
+  // Returns the total amount of bytes that are tracked.
+  virtual int64_t GetTotalAllocationBytes() = 0;
+  virtual int64_t GetTotalNumberOfAllocations() = 0;
+
+  // Allows probing of all memory allocations. The visitor does not need to
+  // perform any locking and can allocate memory during it's operation.
+  virtual void Accept(AllocationVisitor* visitor) = 0;
+
+  // Collects all memory groups that exist. The AllocationGroups lifetime
+  // exists for as long as the MemoryTracker instance is alive.
+  virtual void GetAllocationGroups(
+      std::vector<const AllocationGroup*>* output) = 0;
+
+  // Enabled/disables memory tracking in the current thread.
+  virtual void SetMemoryTrackingEnabled(bool on) = 0;
+  // Returns the memory tracking state in the current thread.
+  virtual bool IsMemoryTrackingEnabled() const = 0;
+
+  // Returns true if the memory was successfully tracked.
+  virtual bool AddMemoryTracking(const void* memory, size_t size) = 0;
+  // Returns a non-zero size if the memory was successfully removed.
+  virtual size_t RemoveMemoryTracking(const void* memory) = 0;
+  // Returns true if the memory has tracking. When true is returned then the
+  // supplied AllocRecord is written.
+  virtual bool GetMemoryTracking(const void* memory,
+                                 AllocationRecord* record) const = 0;
+
+ protected:
+  virtual ~MemoryTracker() {}
+
+  SB_DISALLOW_COPY_AND_ASSIGN(MemoryTracker);
+};
+
+// A visitor class which is useful for inspecting data.
+class AllocationVisitor {
+ public:
+  // Returns true to keep visiting, otherwise abort.
+  virtual bool Visit(const void* memory,
+                     const AllocationRecord& alloc_record) = 0;
+  virtual ~AllocationVisitor() {}
+};
+
+// Contains an allocation record for a pointer including it's size and what
+// AllocationGroup it was constructed under.
+class AllocationRecord {
+ public:
+  AllocationRecord() : size(0), allocation_group(NULL) {}
+  AllocationRecord(size_t sz, AllocationGroup* group)
+      : size(sz), allocation_group(group) {}
+
+  static AllocationRecord Empty() { return AllocationRecord(); }
+  bool IsEmpty() const { return !size && !allocation_group; }
+  size_t size;
+  AllocationGroup* allocation_group;
+};
+
+// Creates a SimpleThread that will output the state of the memory
+// periodically. Start()/Cancel()/Join() are called AUTOMATICALLY with
+// this object. Start() is on the returned thread before it is returned.
+// Join() is automatically called on destruction.
+scoped_ptr<MemoryTrackerPrintThread>
+    CreateDebugPrintThread(MemoryTracker* memory_tracker);
+
+// Creates a SimpleThread that will output the state of the memory
+// periodically. Start()/Cancel()/Join() are called AUTOMATICALLY with
+// this object. Start() is on the returned thread before it is returned.
+// Join() is automatically called on destruction.
+scoped_ptr<MemoryTrackerPrintCSVThread>
+    CreateDebugPrintCSVThread(MemoryTracker* memory_tracker,
+                              int sample_interval_ms,
+                              int total_sampling_time_ms);
+
+}  // namespace analytics
+}  // namespace nb
+
+#endif  // NB_MEMORY_TRACKER_H_
diff --git a/src/nb/analytics/memory_tracker_helpers.cc b/src/nb/analytics/memory_tracker_helpers.cc
new file mode 100644
index 0000000..a1eedc8
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_helpers.cc
@@ -0,0 +1,280 @@
+/*
+ * 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/analytics/memory_tracker_helpers.h"
+
+#include <stdint.h>
+#include <vector>
+
+#include "nb/hash.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+
+namespace nb {
+namespace analytics {
+
+AllocationGroup::AllocationGroup(const std::string& name)
+    : name_(name), allocation_bytes_(0), num_allocations_(0) {
+}
+
+AllocationGroup::~AllocationGroup() {}
+
+void AllocationGroup::AddAllocation(int64_t num_bytes) {
+  if (num_bytes == 0)
+    return;
+  int num_alloc_diff = num_bytes > 0 ? 1 : -1;
+  allocation_bytes_.fetch_add(num_bytes);
+  num_allocations_.fetch_add(num_alloc_diff);
+}
+
+void AllocationGroup::GetAggregateStats(int32_t* num_allocs,
+                                        int64_t* allocation_bytes) const {
+  *num_allocs = num_allocations_.load();
+  *allocation_bytes = allocation_bytes_.load();
+}
+
+int64_t AllocationGroup::allocation_bytes() const {
+  return allocation_bytes_.load();
+}
+
+int32_t AllocationGroup::num_allocations() const {
+  return num_allocations_.load();
+}
+
+AtomicStringAllocationGroupMap::AtomicStringAllocationGroupMap() {
+  unaccounted_group_ = Ensure("Unaccounted");
+}
+
+AtomicStringAllocationGroupMap::~AtomicStringAllocationGroupMap() {
+  unaccounted_group_ = NULL;
+  while (!group_map_.empty()) {
+    Map::iterator it = group_map_.begin();
+    delete it->second;
+    group_map_.erase(it);
+  }
+}
+
+AllocationGroup* AtomicStringAllocationGroupMap::Ensure(
+    const std::string& name) {
+  starboard::ScopedLock lock(mutex_);
+  Map::const_iterator found_it = group_map_.find(name);
+  if (found_it != group_map_.end()) {
+    return found_it->second;
+  }
+  AllocationGroup* group = new AllocationGroup(name);
+  group_map_[name] = group;
+  return group;
+}
+
+AllocationGroup* AtomicStringAllocationGroupMap::GetDefaultUnaccounted() {
+  return unaccounted_group_;
+}
+
+bool AtomicStringAllocationGroupMap::Erase(const std::string& name) {
+  starboard::ScopedLock lock(mutex_);
+  Map::iterator found_it = group_map_.find(name);
+  if (found_it == group_map_.end()) {
+    // Didn't find it.
+    return false;
+  }
+  group_map_.erase(found_it);
+  return true;
+}
+
+void AtomicStringAllocationGroupMap::GetAll(
+    std::vector<const AllocationGroup*>* output) const {
+  starboard::ScopedLock lock(mutex_);
+  for (Map::const_iterator it = group_map_.begin(); it != group_map_.end();
+       ++it) {
+    output->push_back(it->second);
+  }
+}
+
+void AllocationGroupStack::Push(AllocationGroup* group) {
+  alloc_group_stack_.push_back(group);
+}
+
+void AllocationGroupStack::Pop() {
+  alloc_group_stack_.pop_back();
+}
+
+AllocationGroup* AllocationGroupStack::Peek() {
+  if (alloc_group_stack_.empty()) {
+    return NULL;
+  }
+  return alloc_group_stack_.back();
+}
+
+AtomicAllocationMap::AtomicAllocationMap() {}
+
+AtomicAllocationMap::~AtomicAllocationMap() {}
+
+bool AtomicAllocationMap::Add(const void* memory,
+                              const AllocationRecord& alloc_record) {
+  starboard::ScopedLock lock(mutex_);
+  const bool inserted =
+      pointer_map_.insert(std::make_pair(memory, alloc_record)).second;
+  return inserted;
+}
+
+bool AtomicAllocationMap::Get(const void* memory,
+                              AllocationRecord* alloc_record) const {
+  starboard::ScopedLock lock(mutex_);
+  PointerMap::const_iterator found_it = pointer_map_.find(memory);
+  if (found_it == pointer_map_.end()) {
+    if (alloc_record) {
+      *alloc_record = AllocationRecord::Empty();
+    }
+    return false;
+  }
+  if (alloc_record) {
+    *alloc_record = found_it->second;
+  }
+  return true;
+}
+
+bool AtomicAllocationMap::Remove(const void* memory,
+                                 AllocationRecord* alloc_record) {
+  starboard::ScopedLock lock(mutex_);
+  PointerMap::iterator found_it = pointer_map_.find(memory);
+
+  if (found_it == pointer_map_.end()) {
+    if (alloc_record) {
+      *alloc_record = AllocationRecord::Empty();
+    }
+    return false;
+  }
+  if (alloc_record) {
+    *alloc_record = found_it->second;
+  }
+  pointer_map_.erase(found_it);
+  return true;
+}
+
+bool AtomicAllocationMap::Accept(AllocationVisitor* visitor) const {
+  starboard::ScopedLock lock(mutex_);
+  for (PointerMap::const_iterator it = pointer_map_.begin();
+       it != pointer_map_.end(); ++it) {
+    const void* memory = it->first;
+    const AllocationRecord& alloc_rec = it->second;
+    if (!visitor->Visit(memory, alloc_rec)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+size_t AtomicAllocationMap::Size() const {
+  starboard::ScopedLock lock(mutex_);
+  return pointer_map_.size();
+}
+
+bool AtomicAllocationMap::Empty() const {
+  starboard::ScopedLock lock(mutex_);
+  return pointer_map_.empty();
+}
+
+void AtomicAllocationMap::Clear() {
+  starboard::ScopedLock lock(mutex_);
+  for (PointerMap::iterator it = pointer_map_.begin();
+       it != pointer_map_.end(); ++it) {
+    const AllocationRecord& rec = it->second;
+    AllocationGroup* group = rec.allocation_group;
+    group->AddAllocation(-rec.size);
+  }
+  return pointer_map_.clear();
+}
+
+ConcurrentAllocationMap::ConcurrentAllocationMap() : pointer_map_array_() {}
+
+ConcurrentAllocationMap::~ConcurrentAllocationMap() {
+  Clear();
+}
+
+bool ConcurrentAllocationMap::Add(const void* memory,
+                                  const AllocationRecord& alloc_record) {
+  AtomicAllocationMap& map = GetMapForPointer(memory);
+  return map.Add(memory, alloc_record);
+}
+
+bool ConcurrentAllocationMap::Get(const void* memory,
+                                  AllocationRecord* alloc_record) const {
+  const AtomicAllocationMap& map = GetMapForPointer(memory);
+  return map.Get(memory, alloc_record);
+}
+
+bool ConcurrentAllocationMap::Remove(const void* memory,
+                                     AllocationRecord* alloc_record) {
+  AtomicAllocationMap& map = GetMapForPointer(memory);
+  bool output = map.Remove(memory, alloc_record);
+  return output;
+}
+
+size_t ConcurrentAllocationMap::Size() const {
+  size_t size = 0;
+  for (int i = 0; i < kNumElements; ++i) {
+    const AtomicAllocationMap& map = pointer_map_array_[i];
+    size += map.Size();
+  }
+  return size;
+}
+
+bool ConcurrentAllocationMap::Empty() const {
+  return 0 == Size();
+}
+
+void ConcurrentAllocationMap::Clear() {
+  for (int i = 0; i < kNumElements; ++i) {
+    AtomicAllocationMap& map = pointer_map_array_[i];
+    map.Clear();
+  }
+}
+
+bool ConcurrentAllocationMap::Accept(AllocationVisitor* visitor) const {
+  for (int i = 0; i < kNumElements; ++i) {
+    const AtomicAllocationMap& map = pointer_map_array_[i];
+    if (!map.Accept(visitor)) {
+      return false;  // Early out.
+    }
+  }
+  return true;
+}
+
+size_t ConcurrentAllocationMap::hash_ptr(const void* ptr) {
+  uintptr_t val = reinterpret_cast<uintptr_t>(ptr);
+
+  return RuntimeHash32(reinterpret_cast<const char*>(&val), sizeof(val));
+}
+
+int ConcurrentAllocationMap::ToIndex(const void* ptr) const {
+  SB_DCHECK(0 != kNumElements);
+  uint32_t hash_val = hash_ptr(ptr);
+  int index = hash_val % kNumElements;
+  return index;
+}
+
+AtomicAllocationMap& ConcurrentAllocationMap::GetMapForPointer(
+    const void* ptr) {
+  return pointer_map_array_[ToIndex(ptr)];
+}
+
+const AtomicAllocationMap& ConcurrentAllocationMap::GetMapForPointer(
+    const void* ptr) const {
+  return pointer_map_array_[ToIndex(ptr)];
+}
+
+}  // namespace analytics
+}  // namespace nb
diff --git a/src/nb/analytics/memory_tracker_helpers.h b/src/nb/analytics/memory_tracker_helpers.h
new file mode 100644
index 0000000..d004620
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_helpers.h
@@ -0,0 +1,233 @@
+/*
+ * 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_MEMORY_TRACKER_HELPERS_H_
+#define NB_MEMORY_TRACKER_HELPERS_H_
+
+#include <map>
+#include <vector>
+
+#include "nb/analytics/memory_tracker.h"
+#include "nb/atomic.h"
+#include "nb/simple_thread.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+#include "starboard/types.h"
+#include "starboard/log.h"
+
+namespace nb {
+namespace analytics {
+
+class AllocationGroup;
+class AllocationRecord;
+
+template <typename Type>
+class ThreadLocalPointer {
+ public:
+  ThreadLocalPointer() {
+    slot_ = SbThreadCreateLocalKey(NULL);  // No destructor for pointer.
+    SB_DCHECK(kSbThreadLocalKeyInvalid != slot_);
+  }
+
+  ~ThreadLocalPointer() { SbThreadDestroyLocalKey(slot_); }
+
+  Type* Get() const {
+    void* ptr = SbThreadGetLocalValue(slot_);
+    Type* type_ptr = static_cast<Type*>(ptr);
+    return type_ptr;
+  }
+
+  void Set(Type* ptr) {
+    void* void_ptr = static_cast<void*>(ptr);
+    SbThreadSetLocalValue(slot_, void_ptr);
+  }
+
+ private:
+  SbThreadLocalKey slot_;
+  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer<Type>);
+};
+
+class ThreadLocalBoolean {
+ public:
+  ThreadLocalBoolean() : default_value_(false) {}
+  explicit ThreadLocalBoolean(bool default_value)
+      : default_value_(default_value) {}
+  ~ThreadLocalBoolean() {}
+
+  bool Get() const {
+    bool val = tlp_.Get() != NULL;
+    return val ^ default_value_;
+  }
+
+  void Set(bool val) {
+    val = val ^ default_value_;
+    tlp_.Set(val ? TruePointer() : FalsePointer());
+  }
+
+ private:
+  static void* TruePointer() { return reinterpret_cast<void*>(0x1); }
+  static void* FalsePointer() { return NULL; }
+  ThreadLocalPointer<void> tlp_;
+  const bool default_value_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean);
+};
+
+// An AllocationGroup is a collection of allocations that are logically lumped
+// together, such as "Javascript" or "Graphics".
+class AllocationGroup {
+ public:
+  explicit AllocationGroup(const std::string& name);
+  ~AllocationGroup();
+  const std::string& name() const { return name_; }
+
+  void AddAllocation(int64_t num_bytes);
+  void GetAggregateStats(int32_t* num_allocs, int64_t* allocation_bytes) const;
+
+  int64_t allocation_bytes() const;
+  int32_t num_allocations() const;
+
+ private:
+  const std::string name_;
+  nb::atomic_int64_t allocation_bytes_;
+  nb::atomic_int32_t num_allocations_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroup);
+};
+
+// A self locking data structure that maps strings -> AllocationGroups. This is
+// used to resolve MemoryGroup names (e.g. "Javascript") to an AllocationGroup
+// which can be used to group allocations together.
+class AtomicStringAllocationGroupMap {
+ public:
+  AtomicStringAllocationGroupMap();
+  ~AtomicStringAllocationGroupMap();
+
+  AllocationGroup* Ensure(const std::string& name);
+  AllocationGroup* GetDefaultUnaccounted();
+
+  bool Erase(const std::string& key);
+  void GetAll(std::vector<const AllocationGroup*>* output) const;
+
+ private:
+  typedef std::map<std::string, AllocationGroup*> Map;
+  Map group_map_;
+  AllocationGroup* unaccounted_group_;
+  mutable starboard::Mutex mutex_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(AtomicStringAllocationGroupMap);
+};
+
+class AllocationGroupStack {
+ public:
+  AllocationGroupStack() { Push_DebugBreak(NULL); }
+  ~AllocationGroupStack() {}
+
+  void Push(AllocationGroup* group);
+  void Pop();
+  AllocationGroup* Peek();
+
+  void Push_DebugBreak(AllocationGroup* ag) { debug_stack_.push_back(ag); }
+  void Pop_DebugBreak() { debug_stack_.pop_back(); }
+  AllocationGroup* Peek_DebugBreak() {
+    if (debug_stack_.empty()) {
+      return NULL;
+    }
+    return debug_stack_.back();
+  }
+
+ private:
+  SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroupStack);
+  typedef std::vector<AllocationGroup*> AllocationGroupPtrVec;
+  AllocationGroupPtrVec alloc_group_stack_, debug_stack_;
+};
+
+// A per-pointer map of allocations to AllocRecords. This map is thread safe.
+class AtomicAllocationMap {
+ public:
+  AtomicAllocationMap();
+  ~AtomicAllocationMap();
+
+  // Returns true if Added. Otherwise false means that the pointer
+  // already existed.
+  bool Add(const void* memory, const AllocationRecord& alloc_record);
+
+  // Returns true if the memory exists in this set.
+  bool Get(const void* memory, AllocationRecord* alloc_record) const;
+
+  // Return true if the memory existed in this set. If true
+  // then output alloc_record is written with record that was found.
+  // otherwise the record is written as 0 bytes and null key.
+  bool Remove(const void* memory, AllocationRecord* alloc_record);
+
+  bool Accept(AllocationVisitor* visitor) const;
+
+  size_t Size() const;
+  bool Empty() const;
+  void Clear();
+
+ private:
+  SB_DISALLOW_COPY_AND_ASSIGN(AtomicAllocationMap);
+  typedef std::map<const void*, AllocationRecord> PointerMap;
+
+  PointerMap pointer_map_;
+  mutable starboard::Mutex mutex_;
+};
+
+// A per-pointer map of allocations to AllocRecords. This is a hybrid data
+// structure consisting of a hashtable of maps. Each pointer that is
+// stored or retrieved is hashed to a random bucket. Each bucket has it's own
+// lock. This distributed pattern increases performance significantly by
+// reducing contention. The top-level hashtable is of constant size and does
+// not resize. Each bucket is implemented as it's own map of elements.
+class ConcurrentAllocationMap {
+ public:
+  static const int kNumElements = 511;
+  ConcurrentAllocationMap();
+  ~ConcurrentAllocationMap();
+
+  // Returns true if Added. Otherwise false means that the pointer
+  // already existed.
+  bool Add(const void* memory, const AllocationRecord& alloc_record);
+  // Returns true if the memory exists in this set.
+  bool Get(const void* memory, AllocationRecord* alloc_record) const;
+  // Return true if the memory existed in this set. If true
+  // then output alloc_record is written with record that was found.
+  // otherwise the record is written as 0 bytes and null key.
+  bool Remove(const void* memory, AllocationRecord* alloc_record);
+  size_t Size() const;
+  bool Empty() const;
+  void Clear();
+
+  // Provides access to all the allocations within in a thread safe manner.
+  bool Accept(AllocationVisitor* visitor) const;
+
+  AtomicAllocationMap& GetMapForPointer(const void* ptr);
+  const AtomicAllocationMap& GetMapForPointer(const void* ptr) const;
+
+ private:
+  SB_DISALLOW_COPY_AND_ASSIGN(ConcurrentAllocationMap);
+  // Takes a pointer and generates a hash.
+  static size_t hash_ptr(const void* ptr);
+
+  int ToIndex(const void* ptr) const;
+  AtomicAllocationMap pointer_map_array_[kNumElements];
+};
+
+}  // namespace analytics
+}  // namespace nb
+
+#endif  // NB_MEMORY_TRACKER_HELPERS_H_
diff --git a/src/nb/analytics/memory_tracker_helpers_test.cc b/src/nb/analytics/memory_tracker_helpers_test.cc
new file mode 100644
index 0000000..ebef340
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_helpers_test.cc
@@ -0,0 +1,124 @@
+/*
+ * 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/analytics/memory_tracker_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace nb {
+namespace analytics {
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+TEST(AtomicStringAllocationGroupMap, Use) {
+  AtomicStringAllocationGroupMap map;
+  AllocationGroup* tag = map.Ensure("MemoryRegion");
+  EXPECT_TRUE(tag != NULL);
+  EXPECT_EQ(std::string("MemoryRegion"), tag->name());
+  AllocationGroup* tag2 = map.Ensure("MemoryRegion");
+  EXPECT_EQ(tag, tag2);
+}
+
+TEST(AtomicAllocationMap, AddHasRemove) {
+  AtomicAllocationMap atomic_pointer_map;
+  int32_t int_a = 0;
+  int32_t int_b = 1;
+
+  // Initially empty.
+  EXPECT_TRUE(atomic_pointer_map.Empty());
+
+  const AllocationRecord int32_alloc_record =
+      AllocationRecord(sizeof(int32_t), NULL);
+  AllocationRecord alloc_output;
+
+  EXPECT_TRUE(atomic_pointer_map.Add(&int_a, int32_alloc_record));
+  EXPECT_FALSE(atomic_pointer_map.Add(&int_a, int32_alloc_record));
+  EXPECT_TRUE(atomic_pointer_map.Get(&int_a, &alloc_output));
+  EXPECT_EQ(alloc_output.size, sizeof(int32_t));
+
+  EXPECT_FALSE(atomic_pointer_map.Get(&int_b, &alloc_output));
+  EXPECT_EQ(0, alloc_output.size);
+  // Adding pointer to int_a increases set to 1 element.
+  EXPECT_EQ(atomic_pointer_map.Size(), 1);
+  EXPECT_FALSE(atomic_pointer_map.Empty());
+
+  // Adds pointer to int_b.
+  EXPECT_TRUE(atomic_pointer_map.Add(&int_b, int32_alloc_record));
+  EXPECT_TRUE(atomic_pointer_map.Get(&int_b, &alloc_output));
+  EXPECT_EQ(sizeof(int32_t), alloc_output.size);
+  // Expect that the second pointer added will increase the number of elements.
+  EXPECT_EQ(atomic_pointer_map.Size(), 2);
+  EXPECT_FALSE(atomic_pointer_map.Empty());
+
+  // Now remove the elements and ensure that they no longer found and that
+  // the size of the table shrinks to empty.
+  EXPECT_TRUE(atomic_pointer_map.Remove(&int_a, &alloc_output));
+  EXPECT_EQ(sizeof(int32_t), alloc_output.size);
+  EXPECT_EQ(atomic_pointer_map.Size(), 1);
+  EXPECT_FALSE(atomic_pointer_map.Remove(&int_a, &alloc_output));
+  EXPECT_EQ(0, alloc_output.size);
+  EXPECT_TRUE(atomic_pointer_map.Remove(&int_b, &alloc_output));
+  EXPECT_EQ(atomic_pointer_map.Size(), 0);
+
+  EXPECT_TRUE(atomic_pointer_map.Empty());
+}
+
+TEST(ConcurrentAllocationMap, AddHasRemove) {
+  ConcurrentAllocationMap alloc_map;
+  int32_t int_a = 0;
+  int32_t int_b = 1;
+
+  // Initially empty.
+  EXPECT_TRUE(alloc_map.Empty());
+
+  const AllocationRecord int32_alloc_record =
+      AllocationRecord(sizeof(int32_t), NULL);
+  AllocationRecord alloc_output;
+
+  EXPECT_TRUE(alloc_map.Add(&int_a, int32_alloc_record));
+  EXPECT_FALSE(alloc_map.Add(&int_a, int32_alloc_record));
+  EXPECT_TRUE(alloc_map.Get(&int_a, &alloc_output));
+  EXPECT_EQ(alloc_output.size, sizeof(int32_t));
+
+  EXPECT_FALSE(alloc_map.Get(&int_b, &alloc_output));
+  EXPECT_EQ(0, alloc_output.size);
+  // Adding pointer to int_a increases set to 1 element.
+  EXPECT_EQ(alloc_map.Size(), 1);
+  EXPECT_FALSE(alloc_map.Empty());
+
+  // Adds pointer to int_b.
+  EXPECT_TRUE(alloc_map.Add(&int_b, int32_alloc_record));
+  EXPECT_TRUE(alloc_map.Get(&int_b, &alloc_output));
+  EXPECT_EQ(sizeof(int32_t), alloc_output.size);
+  // Expect that the second pointer added will increase the number of elements.
+  EXPECT_EQ(alloc_map.Size(), 2);
+  EXPECT_FALSE(alloc_map.Empty());
+
+  // Now remove the elements and ensure that they no longer found and that
+  // the size of the table shrinks to empty.
+  EXPECT_TRUE(alloc_map.Remove(&int_a, &alloc_output));
+  EXPECT_EQ(sizeof(int32_t), alloc_output.size);
+  EXPECT_EQ(alloc_map.Size(), 1);
+  EXPECT_FALSE(alloc_map.Remove(&int_a, &alloc_output));
+  EXPECT_EQ(0, alloc_output.size);
+  EXPECT_TRUE(alloc_map.Remove(&int_b, &alloc_output));
+  EXPECT_EQ(alloc_map.Size(), 0);
+
+  EXPECT_TRUE(alloc_map.Empty());
+}
+
+}  // namespace
+}  // namespace analytics
+}  // namespace nb
diff --git a/src/nb/analytics/memory_tracker_impl.cc b/src/nb/analytics/memory_tracker_impl.cc
new file mode 100644
index 0000000..a4a8c89
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_impl.cc
@@ -0,0 +1,836 @@
+/*
+ * 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/analytics/memory_tracker_impl.h"
+
+#include <cstring>
+#include <iomanip>
+#include <iterator>
+#include <sstream>
+
+#include "nb/atomic.h"
+#include "starboard/atomic.h"
+#include "starboard/log.h"
+#include "starboard/time.h"
+
+namespace nb {
+namespace analytics {
+namespace {
+// NoMemoryTracking will disable memory tracking while in the current scope of
+// execution. When the object is destroyed it will reset the previous state
+// of allocation tracking.
+// Example:
+//   void Foo() {
+//     NoMemoryTracking no_memory_tracking_in_scope;
+//     int* ptr = new int();  // ptr is not tracked.
+//     delete ptr;
+//     return;    // Previous memory tracking state is restored.
+//   }
+class NoMemoryTracking {
+ public:
+  NoMemoryTracking(MemoryTracker* owner) : owner_(owner) {
+    prev_val_ = owner_->IsMemoryTrackingEnabled();
+    owner_->SetMemoryTrackingEnabled(false);
+  }
+  ~NoMemoryTracking() { owner_->SetMemoryTrackingEnabled(prev_val_); }
+
+ private:
+  bool prev_val_;
+  MemoryTracker* owner_;
+};
+
+// This is a simple algorithm to remove the "needle" from the haystack. Note
+// that this function is simple and not well optimized.
+std::string RemoveString(const std::string& haystack, const char* needle) {

+  const size_t NOT_FOUND = std::string::npos;

+

+  // Base case. No modification needed.

+  size_t pos = haystack.find(needle);

+  if (pos == NOT_FOUND) {

+    return haystack;

+  }

+  const size_t n = strlen(needle);

+  std::string output;

+  output.reserve(haystack.size());

+

+  // Copy string, omitting the portion containing the "needle".

+  std::copy(haystack.begin(), haystack.begin() + pos,

+            std::back_inserter(output));

+  std::copy(haystack.begin() + pos + n, haystack.end(),

+            std::back_inserter(output));

+

+  // Recursively remove same needle in haystack.

+  return RemoveString(output, needle);

+}
+}  // namespace
+
+SbMemoryReporter* MemoryTrackerImpl::GetMemoryReporter() {
+  return &sb_memory_tracker_;
+}
+
+NbMemoryScopeReporter* MemoryTrackerImpl::GetMemoryScopeReporter() {
+  return &nb_memory_scope_reporter_;
+}
+
+int64_t MemoryTrackerImpl::GetTotalAllocationBytes() {
+  return total_bytes_allocated_.load();
+}
+
+AllocationGroup* MemoryTrackerImpl::GetAllocationGroup(const char* name) {
+  DisableMemoryTrackingInScope no_tracking(this);
+  AllocationGroup* alloc_group = alloc_group_map_.Ensure(name);
+  return alloc_group;
+}
+
+void MemoryTrackerImpl::PushAllocationGroupByName(const char* group_name) {
+  AllocationGroup* group = GetAllocationGroup(group_name);
+  PushAllocationGroup(group);
+}
+
+void MemoryTrackerImpl::PushAllocationGroup(AllocationGroup* alloc_group) {
+  if (alloc_group == NULL) {
+    alloc_group = alloc_group_map_.GetDefaultUnaccounted();
+  }
+  DisableMemoryTrackingInScope no_tracking(this);
+  allocation_group_stack_tls_.GetOrCreate()->Push(alloc_group);
+}
+
+AllocationGroup* MemoryTrackerImpl::PeekAllocationGroup() {
+  DisableMemoryTrackingInScope no_tracking(this);
+  AllocationGroup* out =
+      allocation_group_stack_tls_.GetOrCreate()->Peek();
+  if (out == NULL) {
+    out = alloc_group_map_.GetDefaultUnaccounted();
+  }
+  return out;
+}
+
+void MemoryTrackerImpl::PopAllocationGroup() {
+  DisableMemoryTrackingInScope no_tracking(this);
+  AllocationGroupStack* alloc_tls = allocation_group_stack_tls_.GetOrCreate();
+  alloc_tls->Pop();
+  AllocationGroup* group = alloc_tls->Peek();
+  // We don't allow null, so if this is encountered then push the
+  // "default unaccounted" alloc group.
+  if (group == NULL) {
+    alloc_tls->Push(alloc_group_map_.GetDefaultUnaccounted());
+  }
+}
+
+void MemoryTrackerImpl::GetAllocationGroups(
+    std::vector<const AllocationGroup*>* output) {
+  DisableMemoryTrackingInScope no_allocation_tracking(this);
+  output->reserve(100);
+  alloc_group_map_.GetAll(output);
+}
+
+void MemoryTrackerImpl::GetAllocationGroups(
+    std::map<std::string, const AllocationGroup*>* output) {
+  output->clear();
+  DisableMemoryTrackingInScope no_tracking(this);
+  std::vector<const AllocationGroup*> tmp;
+  GetAllocationGroups(&tmp);
+  for (size_t i = 0; i < tmp.size(); ++i) {
+    output->insert(std::make_pair(tmp[i]->name(), tmp[i]));
+  }
+}
+
+void MemoryTrackerImpl::Accept(AllocationVisitor* visitor) {
+  DisableMemoryTrackingInScope no_mem_tracking(this);
+  atomic_allocation_map_.Accept(visitor);
+}
+
+void MemoryTrackerImpl::Clear() {
+  // Prevent clearing of the tree from triggering a re-entrant
+  // memory deallocation.
+  atomic_allocation_map_.Clear();
+  total_bytes_allocated_.store(0);
+}
+
+void MemoryTrackerImpl::Debug_PushAllocationGroupBreakPointByName(
+    const char* group_name) {
+  DisableMemoryTrackingInScope no_tracking(this);
+  SB_DCHECK(group_name != NULL);
+  AllocationGroup* group = alloc_group_map_.Ensure(group_name);
+  Debug_PushAllocationGroupBreakPoint(group);
+}
+
+void MemoryTrackerImpl::Debug_PushAllocationGroupBreakPoint(
+    AllocationGroup* alloc_group) {
+  DisableMemoryTrackingInScope no_tracking(this);
+  allocation_group_stack_tls_.GetOrCreate()->Push_DebugBreak(alloc_group);
+}
+
+void MemoryTrackerImpl::Debug_PopAllocationGroupBreakPoint() {
+  DisableMemoryTrackingInScope no_tracking(this);
+  allocation_group_stack_tls_.GetOrCreate()->Pop_DebugBreak();
+}
+
+// Converts "2345.54" => "2,345.54".
+std::string InsertCommasIntoNumberString(const std::string& input) {
+  typedef std::vector<char> CharVector;
+  typedef CharVector::iterator CharIt;
+
+  CharVector chars(input.begin(), input.end());
+  std::reverse(chars.begin(), chars.end());
+
+  CharIt curr_it = chars.begin();
+  CharIt mid = std::find(chars.begin(), chars.end(), '.');
+  if (mid == chars.end()) {
+    mid = curr_it;
+  }
+
+  CharVector out(curr_it, mid);
+
+  int counter = 0;
+  for (CharIt it = mid; it != chars.end(); ++it) {
+    if (counter != 0 && (counter % 3 == 0)) {
+      out.push_back(',');
+    }
+    if (*it != '.') {
+      counter++;
+    }
+    out.push_back(*it);
+  }
+
+  std::reverse(out.begin(), out.end());
+  std::stringstream ss;
+  for (int i = 0; i < out.size(); ++i) {
+    ss << out[i];
+  }
+  return ss.str();
+}
+
+template <typename T>
+std::string NumberFormatWithCommas(T val) {
+  // Convert value to string.
+  std::stringstream ss;
+  ss << val;
+  std::string s = InsertCommasIntoNumberString(ss.str());
+  return s;
+}
+
+void MemoryTrackerImpl::OnMalloc(void* context,
+                                 const void* memory,
+                                 size_t size) {
+  MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+  t->AddMemoryTracking(memory, size);
+}
+
+void MemoryTrackerImpl::OnDealloc(void* context, const void* memory) {
+  MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+  t->RemoveMemoryTracking(memory);
+}
+
+void MemoryTrackerImpl::OnMapMem(void* context,
+                                 const void* memory,
+                                 size_t size) {
+  // We might do something more interesting with MapMemory calls later.
+  OnMalloc(context, memory, size);
+}
+
+void MemoryTrackerImpl::OnUnMapMem(void* context,
+                                   const void* memory,
+                                   size_t size) {
+  // We might do something more interesting with UnMapMemory calls later.
+  OnDealloc(context, memory);
+}
+
+void MemoryTrackerImpl::OnPushAllocationGroup(
+    void* context,
+    NbMemoryScopeInfo* memory_scope_info) {
+  MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+  void** cached_handle = &(memory_scope_info->cached_handle_);
+  const bool allows_caching = memory_scope_info->allows_caching_;
+  const char* group_name = memory_scope_info->memory_scope_name_;
+
+  AllocationGroup* group = NULL;
+  if (allows_caching && *cached_handle != NULL) {
+    group = static_cast<AllocationGroup*>(*cached_handle);
+  } else {
+    group = t->GetAllocationGroup(group_name);
+    if (allows_caching) {
+      // Flush all pending writes so that the the pointee is well formed
+      // by the time the pointer becomes visible to other threads.
+      SbAtomicMemoryBarrier();
+      *cached_handle = static_cast<void*>(group);
+    }
+  }
+
+  t->PushAllocationGroup(group);
+}
+
+void MemoryTrackerImpl::OnPopAllocationGroup(void* context) {
+  MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+  t->PopAllocationGroup();
+}
+
+void MemoryTrackerImpl::Initialize(
+    SbMemoryReporter* sb_memory_reporter,
+    NbMemoryScopeReporter* memory_scope_reporter) {
+  SbMemoryReporter mem_reporter = {
+      MemoryTrackerImpl::OnMalloc, MemoryTrackerImpl::OnDealloc,
+
+      MemoryTrackerImpl::OnMapMem, MemoryTrackerImpl::OnUnMapMem,
+
+      this};
+
+  NbMemoryScopeReporter mem_scope_reporter = {
+      MemoryTrackerImpl::OnPushAllocationGroup,
+      MemoryTrackerImpl::OnPopAllocationGroup,
+
+      this,
+  };
+
+  *sb_memory_reporter = mem_reporter;
+  *memory_scope_reporter = mem_scope_reporter;
+}
+
+void MemoryTrackerImpl::SetThreadFilter(SbThreadId tid) {
+  thread_filter_id_ = tid;
+}
+
+bool MemoryTrackerImpl::IsCurrentThreadAllowedToReport() const {
+  if (thread_filter_id_ == kSbThreadInvalidId) {
+    return true;
+  }
+  return SbThreadGetId() == thread_filter_id_;
+}
+
+MemoryTrackerImpl::DisableDeletionInScope::DisableDeletionInScope(
+    MemoryTrackerImpl* owner)
+    : owner_(owner) {
+  prev_state_ = owner->MemoryDeletionEnabled();
+  owner_->SetMemoryDeletionEnabled(false);
+}
+
+MemoryTrackerImpl::DisableDeletionInScope::~DisableDeletionInScope() {
+  owner_->SetMemoryDeletionEnabled(prev_state_);
+}
+
+MemoryTrackerImpl::MemoryTrackerImpl()
+    : thread_filter_id_(kSbThreadInvalidId) {
+  total_bytes_allocated_.store(0);
+  global_hooks_installed_ = false;
+  Initialize(&sb_memory_tracker_, &nb_memory_scope_reporter_);
+  // Push the default region so that stats can be accounted for.
+  PushAllocationGroup(alloc_group_map_.GetDefaultUnaccounted());
+}
+
+MemoryTrackerImpl::~MemoryTrackerImpl() {
+  // If we are currently hooked into allocation tracking...
+  if (global_hooks_installed_) {
+    RemoveGlobalTrackingHooks();
+    // For performance reasons no locking is used on the tracker.
+    // Therefore give enough time for other threads to exit this tracker
+    // before fully destroying this object.
+    SbThreadSleep(250 * kSbTimeMillisecond);  // 250 millisecond wait.
+  }
+}
+
+bool MemoryTrackerImpl::AddMemoryTracking(const void* memory, size_t size) {
+  // Vars are stored to assist in debugging.
+  const bool thread_allowed_to_report = IsCurrentThreadAllowedToReport();
+  const bool valid_memory_request = (memory != NULL) && (size != 0);
+  const bool mem_track_enabled = IsMemoryTrackingEnabled();
+
+  const bool tracking_enabled =
+      mem_track_enabled && valid_memory_request && thread_allowed_to_report;
+
+  if (!tracking_enabled) {
+    return false;
+  }
+
+  // End all memory tracking in subsequent data structures.
+  DisableMemoryTrackingInScope no_memory_tracking(this);
+  AllocationGroupStack* alloc_stack =
+      allocation_group_stack_tls_.GetOrCreate();
+  AllocationGroup* group = alloc_stack->Peek();
+  if (!group) {
+    group = alloc_group_map_.GetDefaultUnaccounted();
+  }
+
+#ifndef NDEBUG
+  // This section of the code is designed to allow a developer to break
+  // execution whenever the debug allocation stack is in scope, so that the
+  // allocations can be stepped through.
+  // Example:
+  //   Debug_PushAllocationGroupBreakPointByName("Javascript");
+  //  ...now set a break point below at "static int i = 0"
+  if (group && (group == alloc_stack->Peek_DebugBreak()) &&
+      alloc_stack->Peek_DebugBreak()) {
+    static int i = 0;  // This static is here to allow an
+    ++i;               // easy breakpoint in the debugger
+  }
+#endif
+
+  AllocationRecord alloc_record(size, group);
+  bool added = atomic_allocation_map_.Add(memory, alloc_record);
+  if (added) {
+    AddAllocationBytes(size);
+    group->AddAllocation(size);
+  } else {
+    // Handles the case where the memory hasn't been properly been reported
+    // released. This is less serious than you would think because the memory
+    // allocator in the system will recycle the memory and it will come back.
+    AllocationRecord unexpected_alloc;
+    atomic_allocation_map_.Get(memory, &unexpected_alloc);
+    AllocationGroup* prev_group = unexpected_alloc.allocation_group;
+
+    std::string prev_group_name;
+    if (prev_group) {
+      prev_group_name = unexpected_alloc.allocation_group->name();
+    } else {
+      prev_group_name = "none";
+    }
+
+    if (!added) {
+      std::stringstream ss;
+      ss << "\nUnexpected condition, previous allocation was not removed:\n"
+         << "\tprevious alloc group: " << prev_group_name << "\n"
+         << "\tnew alloc group: " << group->name() << "\n"
+         << "\tprevious size: " << unexpected_alloc.size << "\n"
+         << "\tnew size: " << size << "\n";
+
+      SbLogRaw(ss.str().c_str());
+    }
+  }
+  return added;
+}
+
+size_t MemoryTrackerImpl::RemoveMemoryTracking(const void* memory) {
+  const bool do_remove = memory && MemoryDeletionEnabled();
+  if (!do_remove) {
+    return 0;
+  }
+
+  AllocationRecord alloc_record;
+  bool removed = false;
+
+  // Prevent a map::erase() from causing an endless stack overflow by
+  // disabling memory deletion for the very limited scope.
+  {
+    // Not a correct name. TODO - change.
+    DisableDeletionInScope no_memory_deletion(this);
+    removed = atomic_allocation_map_.Remove(memory, &alloc_record);
+  }
+
+  if (!removed) {
+    return 0;
+  } else {
+    const int64_t alloc_size = (static_cast<int64_t>(alloc_record.size));
+    AllocationGroup* group = alloc_record.allocation_group;
+    if (group) {
+      group->AddAllocation(-alloc_size);
+    }
+    AddAllocationBytes(-alloc_size);
+    return alloc_record.size;
+  }
+}
+
+bool MemoryTrackerImpl::GetMemoryTracking(const void* memory,
+                                          AllocationRecord* record) const {
+  const bool exists = atomic_allocation_map_.Get(memory, record);
+  return exists;
+}
+
+void MemoryTrackerImpl::SetMemoryTrackingEnabled(bool on) {
+  memory_tracking_disabled_tls_.Set(!on);
+}
+
+bool MemoryTrackerImpl::IsMemoryTrackingEnabled() const {
+  const bool enabled = !memory_tracking_disabled_tls_.Get();
+  return enabled;
+}
+
+void MemoryTrackerImpl::AddAllocationBytes(int64_t val) {
+  total_bytes_allocated_.fetch_add(val);
+}
+
+bool MemoryTrackerImpl::MemoryDeletionEnabled() const {
+  return !memory_deletion_enabled_tls_.Get();
+}
+
+void MemoryTrackerImpl::SetMemoryDeletionEnabled(bool on) {
+  memory_deletion_enabled_tls_.Set(!on);
+}
+
+MemoryTrackerImpl::DisableMemoryTrackingInScope::DisableMemoryTrackingInScope(
+    MemoryTrackerImpl* t)
+    : owner_(t) {
+  prev_value_ = owner_->IsMemoryTrackingEnabled();
+  owner_->SetMemoryTrackingEnabled(false);
+}
+
+MemoryTrackerImpl::DisableMemoryTrackingInScope::
+    ~DisableMemoryTrackingInScope() {
+  owner_->SetMemoryTrackingEnabled(prev_value_);
+}
+
+
+MemoryTrackerPrintThread::MemoryTrackerPrintThread(
+    MemoryTracker* memory_tracker)
+    : SimpleThread("MemoryTrackerPrintThread"),
+    finished_(false),
+    memory_tracker_(memory_tracker) {
+  Start();
+}
+
+MemoryTrackerPrintThread::~MemoryTrackerPrintThread() {
+  Cancel();
+  Join();
+}
+
+void MemoryTrackerPrintThread::Cancel() {
+  finished_.store(true);
+}
+
+void MemoryTrackerPrintThread::Run() {
+  while (!finished_.load()) {
+    NoMemoryTracking no_mem_tracking_in_this_scope(memory_tracker_);
+
+    // std::map<std::string, const AllocationGroup*> output;
+    // typedef std::map<std::string, const AllocationGroup*>::const_iterator
+    // MapIt;
+    std::vector<const AllocationGroup*> vector_output;
+    memory_tracker_->GetAllocationGroups(&vector_output);
+
+    typedef std::map<std::string, const AllocationGroup*> Map;
+    typedef Map::const_iterator MapIt;
+
+    Map output;
+    for (int i = 0; i < vector_output.size(); ++i) {
+      const AllocationGroup* group = vector_output[i];
+      output[group->name()] = group;
+    }
+
+    int32_t num_allocs = 0;
+    int64_t total_bytes = 0;
+
+    struct F {
+      static void PrintRow(std::stringstream* ss,
+          const std::string& v1,
+          const std::string& v2,
+          const std::string& v3) {
+        ss->width(20);
+        *ss << std::left << v1;
+        ss->width(13);
+        *ss << std::right << v2 << "  ";
+        ss->width(10);
+        *ss << std::right << v3 << "\n";
+      }
+    };
+
+    if (memory_tracker_->IsMemoryTrackingEnabled()) {
+      // If this isn't true then it would cause an infinite loop. The
+      // following will likely crash.
+      SB_DCHECK(false) << "Unexpected, memory tracking should be disabled.";
+    }
+
+    std::stringstream ss;
+
+    F::PrintRow(&ss, "NAME", "BYTES", "NUM ALLOCS");
+    ss << "---------------------------------------------\n";
+    for (MapIt it = output.begin(); it != output.end(); ++it) {
+      const AllocationGroup* group = it->second;
+      if (!group) {
+        continue;
+      }
+
+      int32_t num_group_allocs = -1;
+      int64_t total_group_bytes = -1;
+
+      group->GetAggregateStats(&num_group_allocs, &total_group_bytes);
+      SB_DCHECK(-1 != num_group_allocs);
+      SB_DCHECK(-1 != total_group_bytes);
+      num_allocs += num_group_allocs;
+      total_bytes += total_group_bytes;
+
+      F::PrintRow(&ss,
+                  it->first,
+                  NumberFormatWithCommas(total_group_bytes),
+                  NumberFormatWithCommas(num_group_allocs));
+    }
+    ss << "---------------------------------------------\n";
+
+    std::stringstream final_ss;
+    final_ss << "\n"
+      << "Total Bytes Allocated: "
+      << NumberFormatWithCommas(total_bytes) << "\n"
+      << "Total allocations: "
+      << NumberFormatWithCommas(num_allocs) << "\n\n" << ss.str();
+
+    SbLogRaw(final_ss.str().c_str());
+
+    SbThreadSleep(1000 * 1000);
+  }
+}
+
+MemoryTrackerPrintCSVThread::MemoryTrackerPrintCSVThread(
+    MemoryTracker* memory_tracker,
+    int sampling_interval_ms,
+    int sampling_time_ms)
+    : SimpleThread("MemoryTrackerPrintCSVThread"),
+      memory_tracker_(memory_tracker),
+      sample_interval_ms_(sampling_interval_ms),
+      sampling_time_ms_(sampling_time_ms),
+      start_time_(SbTimeGetNow()) {
+  Start();
+}
+
+
+MemoryTrackerPrintCSVThread::~MemoryTrackerPrintCSVThread() {
+  Cancel();
+  Join();
+}
+
+void MemoryTrackerPrintCSVThread::Cancel() {
+  canceled_.store(true);
+}
+
+std::string MemoryTrackerPrintCSVThread::ToCsvString(
+    const MapAllocationSamples& samples_in) {
+  typedef MapAllocationSamples Map;
+  typedef Map::const_iterator MapIt;
+
+  const char QUOTE[] = "\"";
+  const char DELIM[] = ",";
+  const char NEW_LINE[] = "\n";
+
+  size_t largest_sample_size = 0;
+  size_t smallest_sample_size = INT_MAX;
+
+  // Sanitize samples_in and store as samples.
+  MapAllocationSamples samples;
+  for (MapIt it = samples_in.begin(); it != samples_in.end(); ++it) {
+    std::string name = it->first;
+    const AllocationSamples& value = it->second;
+
+    if (value.allocated_bytes_.size() != value.number_allocations_.size()) {
+      SB_NOTREACHED() << "Error at " << __FILE__ << ":" << __LINE__;
+      return "ERROR";
+    }
+
+    const size_t n = value.allocated_bytes_.size();
+    if (n > largest_sample_size) {
+      largest_sample_size = n;
+    }
+    if (n < smallest_sample_size) {
+      smallest_sample_size = n;
+    }
+
+    // Strip out any characters that could make parsing the csv difficult.
+    name = RemoveString(name, QUOTE);
+    name = RemoveString(name, DELIM);
+    name = RemoveString(name, NEW_LINE);
+
+    const bool duplicate_found = (samples.end() != samples.find(name));
+    if (duplicate_found) {
+      SB_NOTREACHED() << "Error, duplicate found for entry: "
+                      << name << NEW_LINE;
+    }
+    // Store value as a sanitized sample.
+    samples[name] = value;
+  }
+
+  SB_DCHECK(largest_sample_size == smallest_sample_size);
+
+  std::stringstream ss;
+
+  // Begin output to CSV.
+  // Sometimes we need to skip the CPU memory entry.
+  const MapIt total_cpu_memory_it = samples.find(UntrackedMemoryKey());
+
+  // Preamble
+  ss << NEW_LINE << "//////////////////////////////////////////////";
+  ss << NEW_LINE << "// CSV of bytes / allocation" << NEW_LINE;
+  // HEADER.
+  ss << "Name" << DELIM << QUOTE << "Bytes/Alloc" << QUOTE << NEW_LINE;
+  // DATA.
+  for (MapIt it = samples.begin(); it != samples.end(); ++it) {
+    if (total_cpu_memory_it == it) {
+      continue;
+    }
+
+    const AllocationSamples& samples = it->second;
+    if (samples.allocated_bytes_.empty() ||
+        samples.number_allocations_.empty()) {
+      SB_NOTREACHED() << "Should not be here";
+      return "ERROR";
+    }
+    const int32_t n_allocs = samples.number_allocations_.back();
+    const int64_t n_bytes = samples.allocated_bytes_.back();
+    int bytes_per_alloc = 0;
+    if (n_allocs > 0) {
+      bytes_per_alloc = n_bytes / n_allocs;
+    }
+    const std::string& name = it->first;
+    ss << QUOTE << name << QUOTE << DELIM << bytes_per_alloc << NEW_LINE;
+  }
+  ss << NEW_LINE;
+
+  // Preamble
+  ss << NEW_LINE << "//////////////////////////////////////////////"
+     << NEW_LINE << "// CSV of bytes allocated per region (MB's)."
+     << NEW_LINE << "// Units are in Megabytes. This is designed"
+     << NEW_LINE << "// to be used in a stacked graph." << NEW_LINE;
+
+  // HEADER.
+  for (MapIt it = samples.begin(); it != samples.end(); ++it) {
+    if (total_cpu_memory_it == it) {
+      continue;
+    }
+    const std::string& name = it->first;
+    ss << QUOTE << name << QUOTE << DELIM;
+  }
+  // Save the total for last.
+  if (total_cpu_memory_it != samples.end()) {
+    const std::string& name = total_cpu_memory_it->first;
+    ss << QUOTE << name << QUOTE << DELIM;
+  }
+  ss << NEW_LINE;
+
+  // Print out the values of each of the samples.
+  for (int i = 0; i < smallest_sample_size; ++i) {
+    for (MapIt it = samples.begin(); it != samples.end(); ++it) {
+      if (total_cpu_memory_it == it) {
+        continue;
+      }
+      const int64_t alloc_bytes = it->second.allocated_bytes_[i];
+      // Convert to float megabytes with decimals of precision.
+      double n = alloc_bytes / (1000 * 10);
+      n = n / (100.);
+      ss << n << DELIM;
+    }
+    if (total_cpu_memory_it != samples.end()) {
+      const int64_t alloc_bytes =
+         total_cpu_memory_it->second.allocated_bytes_[i];
+      // Convert to float megabytes with decimals of precision.
+      double n = alloc_bytes / (1000 * 10);
+      n = n / (100.);
+      ss << n << DELIM;
+    }
+    ss << NEW_LINE;
+  }
+
+  ss << NEW_LINE;
+  // Preamble
+  ss << NEW_LINE << "//////////////////////////////////////////////";
+  ss << NEW_LINE << "// CSV of number of allocations per region." << NEW_LINE;
+
+  // HEADER
+  for (MapIt it = samples.begin(); it != samples.end(); ++it) {
+    if (total_cpu_memory_it == it) {
+      continue;
+    }
+    const std::string& name = it->first;
+    ss << QUOTE << name << QUOTE << DELIM;
+  }
+  ss << NEW_LINE;
+  for (int i = 0; i < smallest_sample_size; ++i) {
+    for (MapIt it = samples.begin(); it != samples.end(); ++it) {
+      if (total_cpu_memory_it == it) {
+        continue;
+      }
+      const int64_t n_allocs = it->second.number_allocations_[i];
+      ss << n_allocs << DELIM;
+    }
+    ss << NEW_LINE;
+  }
+  std::string output = ss.str();
+  return output;
+}
+
+const char* MemoryTrackerPrintCSVThread::UntrackedMemoryKey() {
+  return "Untracked Memory";
+}
+
+void MemoryTrackerPrintCSVThread::Run() {
+  NoMemoryTracking no_mem_tracking_in_this_scope(memory_tracker_);
+
+  SbLogRaw("\nMemoryTrackerPrintCSVThread is sampling...\n");
+  int sample_count = 0;
+  MapAllocationSamples map_samples;
+
+  while (!TimeExpiredYet() && !canceled_.load()) {
+    // Sample total memory used by the system.
+    MemoryStats mem_stats = GetProcessMemoryStats();
+    int64_t untracked_used_memory = mem_stats.used_cpu_memory +
+                                    mem_stats.used_gpu_memory;
+
+    std::vector<const AllocationGroup*> vector_output;
+    memory_tracker_->GetAllocationGroups(&vector_output);
+
+    // Sample all known memory scopes.
+    for (int i = 0; i < vector_output.size(); ++i) {
+      const AllocationGroup* group = vector_output[i];
+      const std::string& name = group->name();
+
+      const bool first_creation = map_samples.find(group->name()) ==
+                                  map_samples.end();
+
+      AllocationSamples* new_entry  = &(map_samples[name]);
+
+      // Didn't see it before so create new entry.
+      if (first_creation) {
+        // Make up for lost samples...
+        new_entry->allocated_bytes_.resize(sample_count, 0);
+        new_entry->number_allocations_.resize(sample_count, 0);
+      }
+
+      int32_t num_allocs = - 1;
+      int64_t allocation_bytes = -1;
+      group->GetAggregateStats(&num_allocs, &allocation_bytes);
+
+      new_entry->allocated_bytes_.push_back(allocation_bytes);
+      new_entry->number_allocations_.push_back(num_allocs);
+
+      untracked_used_memory -= allocation_bytes;
+    }
+
+    // Now push in remaining total.
+    AllocationSamples* process_sample = &(map_samples[UntrackedMemoryKey()]);
+    if (untracked_used_memory < 0) {
+      // On some platforms, total GPU memory may not be correctly reported.
+      // However the allocations from the GPU memory may be reported. In this
+      // case untracked_used_memory will go negative. To protect the memory
+      // reporting the untracked_used_memory is set to 0 so that it doesn't
+      // cause an error in reporting.
+      untracked_used_memory = 0;
+    }
+    process_sample->allocated_bytes_.push_back(untracked_used_memory);
+    process_sample->number_allocations_.push_back(-1);
+
+    ++sample_count;
+    SbThreadSleep(kSbTimeMillisecond * sample_interval_ms_);
+  }
+
+  std::string output = ToCsvString(map_samples);
+  SbLogRaw(output.c_str());
+  SbLogFlush();
+  // Prevents the "thread exited code 0" from being interleaved into the
+  // output. This happens if SbLogFlush() is not implemented for the platform.
+  SbThreadSleep(1000 * kSbTimeMillisecond);
+}
+
+bool MemoryTrackerPrintCSVThread::TimeExpiredYet() {
+  const SbTime diff_us = SbTimeGetNow() - start_time_;
+  const bool expired_time = diff_us > (sampling_time_ms_ * kSbTimeMillisecond);
+  return expired_time;
+}
+
+}  // namespace analytics
+}  // namespace nb
diff --git a/src/nb/analytics/memory_tracker_impl.h b/src/nb/analytics/memory_tracker_impl.h
new file mode 100644
index 0000000..1af04aa
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_impl.h
@@ -0,0 +1,233 @@
+/*
+ * 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_MEMORY_TRACKER_IMPL_H_
+#define NB_MEMORY_TRACKER_IMPL_H_
+
+#include "nb/analytics/memory_tracker_helpers.h"
+#include "nb/analytics/memory_tracker.h"
+#include "nb/memory_scope.h"
+#include "nb/scoped_ptr.h"
+#include "nb/thread_local_object.h"
+#include "starboard/configuration.h"
+#include "starboard/memory_reporter.h"
+#include "starboard/memory.h"
+#include "starboard/mutex.h"
+#include "starboard/time.h"
+
+namespace nb {
+namespace analytics {
+
+class MemoryTrackerImpl : public MemoryTracker {
+ public:
+  typedef ConcurrentAllocationMap AllocationMapType;
+
+  MemoryTrackerImpl();
+  virtual ~MemoryTrackerImpl();
+
+  // MemoryTracker adapter which is compatible with the SbMemoryReporter
+  // interface.
+  SbMemoryReporter* GetMemoryReporter();
+  NbMemoryScopeReporter* GetMemoryScopeReporter();
+
+  AllocationGroup* GetAllocationGroup(const char* name);
+  // Declares the start of a memory region. After this call, all
+  // memory regions will be tagged with this allocation group.
+  // Note that AllocationGroup is a tracking characteristic and
+  // does not imply any sort of special allocation pool.
+  void PushAllocationGroupByName(const char* group_name);
+  void PushAllocationGroup(AllocationGroup* alloc_group);
+  AllocationGroup* PeekAllocationGroup();
+  // Ends the current memory region and the previous memory region
+  // is restored.
+  void PopAllocationGroup();
+
+  // CONTROL
+  //
+  // Adds tracking to the supplied memory pointer. An AllocationRecord is
+  // generated for the supplied allocation which can be queried immediately
+  // with GetMemoryTracking(...).
+  bool InstallGlobalTrackingHooks() SB_OVERRIDE {
+    if (global_hooks_installed_)
+      return true;
+    global_hooks_installed_ = true;
+    bool ok = SbMemorySetReporter(GetMemoryReporter());
+    ok |= NbSetMemoryScopeReporter(GetMemoryScopeReporter());
+    return ok;
+  }
+  void RemoveGlobalTrackingHooks() SB_OVERRIDE {
+    SbMemorySetReporter(NULL);
+    NbSetMemoryScopeReporter(NULL);
+  }
+
+  bool AddMemoryTracking(const void* memory, size_t size) SB_OVERRIDE;
+  size_t RemoveMemoryTracking(const void* memory) SB_OVERRIDE;
+  // Returns true if the allocation record was successfully found.
+  // If true then the output will be written to with the values.
+  // Otherwise the output is reset to the empty AllocationRecord.
+  bool GetMemoryTracking(const void* memory,
+                         AllocationRecord* record) const SB_OVERRIDE;
+  // Thread local function to get and set the memory tracking state. When set
+  // to disabled then memory allocations are not recorded. However memory
+  // deletions are still recorded.
+  void SetMemoryTrackingEnabled(bool on) SB_OVERRIDE;
+  bool IsMemoryTrackingEnabled() const SB_OVERRIDE;
+
+  // REPORTING
+  //
+  // Total allocation bytes that have been allocated by this
+  // MemoryTrackerImpl.
+  int64_t GetTotalAllocationBytes() SB_OVERRIDE;
+  // Retrieves a collection of all known allocation groups. Locking is done
+  // internally.
+  void GetAllocationGroups(std::vector<const AllocationGroup*>* output)
+      SB_OVERRIDE;
+  // Retrieves a collection of all known allocation groups. Locking is done
+  // internally. The output is a map of names to AllocationGroups.
+  void GetAllocationGroups(
+      std::map<std::string, const AllocationGroup*>* output);
+
+  // Provides access to the internal allocations in a thread safe way.
+  // Allocation tracking is disabled in the current thread for the duration
+  // of the visitation.
+  void Accept(AllocationVisitor* visitor) SB_OVERRIDE;
+
+  int64_t GetTotalNumberOfAllocations() SB_OVERRIDE {
+    return pointer_map()->Size();
+  }
+
+  // TESTING.
+  AllocationMapType* pointer_map() { return &atomic_allocation_map_; }
+  void Clear();
+
+  // This is useful for debugging. Allows the developer to set a breakpoint
+  // and see only allocations that are in the defined allocation group. This
+  // is only active in the current thread.
+  void Debug_PushAllocationGroupBreakPointByName(const char* group_name);
+  void Debug_PushAllocationGroupBreakPoint(AllocationGroup* alloc_group);
+  void Debug_PopAllocationGroupBreakPoint();
+
+  // This is useful for testing, setting this to a thread will allow ONLY
+  // those allocations from the set thread.
+  // Setting this to kSbThreadInvalidId (default) allows all threads to report
+  // allocations.
+  void SetThreadFilter(SbThreadId tid);
+  bool IsCurrentThreadAllowedToReport() const;
+
+ private:
+  struct DisableMemoryTrackingInScope {
+    DisableMemoryTrackingInScope(MemoryTrackerImpl* t);
+    ~DisableMemoryTrackingInScope();
+    MemoryTrackerImpl* owner_;
+    bool prev_value_;
+  };
+
+  // Disables all memory deletion in the current scope. This is used in one
+  // location.
+  struct DisableDeletionInScope {
+    DisableDeletionInScope(MemoryTrackerImpl* owner);
+    ~DisableDeletionInScope();
+    MemoryTrackerImpl* owner_;
+    bool prev_state_;
+  };
+
+  // These are functions that are used specifically SbMemoryReporter.
+  static void OnMalloc(void* context, const void* memory, size_t size);
+  static void OnDealloc(void* context, const void* memory);
+  static void OnMapMem(void* context, const void* memory, size_t size);
+  static void OnUnMapMem(void* context, const void* memory, size_t size);
+  static void OnPushAllocationGroup(void* context,
+                                    NbMemoryScopeInfo* memory_scope_info);
+  static void OnPopAllocationGroup(void* context);
+
+  void Initialize(SbMemoryReporter* memory_reporter,
+                  NbMemoryScopeReporter* nb_memory_scope_reporter);
+  void AddAllocationBytes(int64_t val);
+  bool MemoryDeletionEnabled() const;
+
+  void SetMemoryDeletionEnabled(bool on);
+
+  SbMemoryReporter sb_memory_tracker_;
+  NbMemoryScopeReporter nb_memory_scope_reporter_;
+  SbThreadId thread_filter_id_;
+
+  AllocationMapType atomic_allocation_map_;
+  AtomicStringAllocationGroupMap alloc_group_map_;
+
+  atomic_int64_t total_bytes_allocated_;
+
+  // THREAD LOCAL SECTION.
+  ThreadLocalBoolean memory_deletion_enabled_tls_;
+  ThreadLocalBoolean memory_tracking_disabled_tls_;
+  ThreadLocalObject<AllocationGroupStack> allocation_group_stack_tls_;
+  bool global_hooks_installed_;
+};
+
+// Start() is called when this object is created, and Cancel() & Join() are
+// called during destruction.
+class MemoryTrackerPrintThread : public SimpleThread {
+ public:
+  MemoryTrackerPrintThread(MemoryTracker* memory_tracker);
+  virtual ~MemoryTrackerPrintThread() SB_OVERRIDE;
+
+  // Overridden so that the thread can exit gracefully.
+  virtual void Cancel() SB_OVERRIDE;
+  virtual void Run() SB_OVERRIDE;
+
+ private:
+  atomic_bool finished_;
+  MemoryTracker* memory_tracker_;
+};
+
+// Generates CSV values of the engine.
+// There are three sections of data including:
+//   1. average bytes / alloc
+//   2. # Bytes allocated per memory scope.
+//   3. # Allocations per memory scope.
+// This data can be pasted directly into a Google spreadsheet and visualized.
+// Note that this thread will implicitly call Start() is called during
+// construction and Cancel() & Join() during destruction.
+class MemoryTrackerPrintCSVThread : public SimpleThread {
+ public:
+  MemoryTrackerPrintCSVThread(MemoryTracker* memory_tracker,
+                              int sampling_interval_ms,
+                              int sampling_time_ms);
+  virtual ~MemoryTrackerPrintCSVThread() SB_OVERRIDE;
+
+  // Overridden so that the thread can exit gracefully.
+  virtual void Cancel() SB_OVERRIDE;
+  virtual void Run() SB_OVERRIDE;
+ private:
+  struct AllocationSamples {
+    std::vector<int32_t> number_allocations_;
+    std::vector<int64_t> allocated_bytes_;
+  };
+  typedef std::map<std::string, AllocationSamples> MapAllocationSamples;
+  static std::string ToCsvString(const MapAllocationSamples& samples);
+  static const char* UntrackedMemoryKey();
+  bool TimeExpiredYet();
+
+  MemoryTracker* memory_tracker_;
+  const int sample_interval_ms_;
+  const int sampling_time_ms_;
+  SbTime start_time_;
+  atomic_bool canceled_;
+};
+
+}  // namespace analytics
+}  // namespace nb
+
+#endif  // NB_MEMORY_TRACKER_IMPL_H_
diff --git a/src/nb/analytics/memory_tracker_impl_test.cc b/src/nb/analytics/memory_tracker_impl_test.cc
new file mode 100644
index 0000000..309d771
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_impl_test.cc
@@ -0,0 +1,802 @@
+/*
+ * 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/analytics/memory_tracker_impl.h"
+#include "nb/memory_scope.h"
+#include "nb/scoped_ptr.h"
+#include "nb/test_thread.h"
+#include "starboard/system.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define STRESS_TEST_DURATION_SECONDS 1
+#define NUM_STRESS_TEST_THREADS 3
+
+namespace nb {
+namespace analytics {
+namespace {
+
+MemoryTrackerImpl* s_memory_tracker_ = NULL;
+
+struct NoMemTracking {
+  bool prev_val;
+  NoMemTracking() : prev_val(false) {
+    if (s_memory_tracker_) {
+      prev_val = s_memory_tracker_->IsMemoryTrackingEnabled();
+      s_memory_tracker_->SetMemoryTrackingEnabled(false);
+    }
+  }
+  ~NoMemTracking() {
+    if (s_memory_tracker_) {
+      s_memory_tracker_->SetMemoryTrackingEnabled(prev_val);
+    }
+  }
+};
+
+// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when
+// for unit testing allocations. These overrides disable memory
+// tracking for the duration of the EXPECT and ASSERT operations.
+#define EXPECT_EQ_NO_TRACKING(A, B)                 \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_EQ(A, B);                                \
+  }
+
+#define EXPECT_TRUE_NO_TRACKING(A)                  \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_TRUE(A);                                 \
+  }
+
+#define EXPECT_FALSE_NO_TRACKING(A)                 \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_FALSE(A);                                \
+  }
+
+#define ASSERT_EQ_NO_TRACKING(A, B)                 \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    ASSERT_EQ(A, B);                                \
+  }
+
+#define ASSERT_TRUE_NO_TRACKING(A)                  \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    ASSERT_TRUE(A);                                 \
+  }
+
+// !! converts int -> bool.
+bool FlipCoin() {
+  return !!(SbSystemGetRandomUInt64() & 0x1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stress testing the Allocation Tracker.
+class MemoryScopeThread : public nb::TestThread {
+ public:
+  typedef nb::TestThread Super;
+
+  explicit MemoryScopeThread(MemoryTrackerImpl* memory_tracker)
+      : memory_tracker_(memory_tracker) {
+    static int s_counter = 0;
+
+    std::stringstream ss;
+    ss << "MemoryScopeThread_" << s_counter++;
+    unique_name_ = ss.str();
+  }
+  virtual ~MemoryScopeThread() {}
+
+  // Overridden so that the thread can exit gracefully.
+  virtual void Join() {
+    finished_ = true;
+    Super::Join();
+  }
+  virtual void Run() {
+    while (!finished_) {
+      TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str());
+      AllocationGroup* group = memory_tracker_->PeekAllocationGroup();
+
+      const int cmp_result = group->name().compare(unique_name_);
+      if (cmp_result != 0) {
+        GTEST_FAIL() << "unique name mismatch";
+        return;
+      }
+    }
+  }
+
+ private:
+  MemoryTrackerImpl* memory_tracker_;
+  bool finished_;
+  std::string unique_name_;
+  int do_delete_counter_;
+  int do_malloc_counter_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Stress testing the Allocation Tracker.
+class AllocationStressThread : public nb::TestThread {
+ public:
+  explicit AllocationStressThread(MemoryTrackerImpl* memory_tracker);
+  virtual ~AllocationStressThread();
+
+  // Overridden so that the thread can exit gracefully.
+  virtual void Join();
+  virtual void Run();
+
+ private:
+  typedef std::map<const void*, AllocationRecord> AllocMap;
+
+  void CheckPointers();
+  bool RemoveRandomAllocation(std::pair<const void*, AllocationRecord>* output);
+  bool DoDelete();
+  void DoMalloc();
+
+  MemoryTrackerImpl* memory_tracker_;
+  bool finished_;
+  std::map<const void*, AllocationRecord> allocated_pts_;
+  std::string unique_name_;
+  int do_delete_counter_;
+  int do_malloc_counter_;
+};
+
+class AddAllocationStressThread : public nb::TestThread {
+ public:
+  typedef std::map<const void*, AllocationRecord> AllocMap;
+
+  AddAllocationStressThread(MemoryTracker* memory_tracker,
+                            int num_elements_add,
+                            AllocMap* destination_map,
+                            starboard::Mutex* destination_map_mutex)
+      : memory_tracker_(memory_tracker),
+        num_elements_to_add_(num_elements_add),
+        destination_map_(destination_map),
+        destination_map_mutex_(destination_map_mutex) {}
+
+  virtual void Run() {
+    for (int i = 0; i < num_elements_to_add_; ++i) {
+      const int alloc_size = std::rand() % 100 + 8;
+      void* ptr = SbMemoryAllocate(alloc_size);
+
+      AllocationRecord record;
+      if (memory_tracker_->GetMemoryTracking(ptr, &record)) {
+        NoMemTracking no_mem_tracking;  // simplifies test.
+
+        starboard::ScopedLock lock(*destination_map_mutex_);
+        destination_map_->insert(std::make_pair(ptr, record));
+      } else {
+        ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not add pointer.";
+      }
+      if (FlipCoin()) {
+        SbThreadYield();  // Give other threads a chance to run.
+      }
+    }
+  }
+
+ private:
+  MemoryTracker* memory_tracker_;
+  AllocMap* destination_map_;
+  starboard::Mutex* destination_map_mutex_;
+  int num_elements_to_add_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Framework which initializes the MemoryTracker once and installs it
+// for the first test and the removes the MemoryTracker after the
+// the last test finishes.
+class MemoryTrackerImplTest : public ::testing::Test {
+ public:
+  typedef MemoryTrackerImpl::AllocationMapType AllocationMapType;
+  MemoryTrackerImplTest() {}
+
+  MemoryTrackerImpl* memory_tracker() { return s_memory_tracker_; }
+
+  bool GetAllocRecord(void* alloc_memory, AllocationRecord* output) {
+    return memory_tracker()->GetMemoryTracking(alloc_memory, output);
+  }
+
+  AllocationMapType* pointer_map() { return memory_tracker()->pointer_map(); }
+
+  size_t NumberOfAllocations() {
+    AllocationMapType* map = pointer_map();
+    return map->Size();
+  }
+
+  int64_t TotalAllocationBytes() {
+    return memory_tracker()->GetTotalAllocationBytes();
+  }
+
+  bool MemoryTrackerEnabled() const { return s_memory_tracker_enabled_; }
+
+ protected:
+  static void SetUpTestCase() {
+    if (!s_memory_tracker_) {
+      s_memory_tracker_ = new MemoryTrackerImpl;
+      s_memory_tracker_enabled_ =
+          s_memory_tracker_->InstallGlobalTrackingHooks();
+    }
+  }
+  static void TearDownTestCase() { SbMemorySetReporter(NULL); }
+
+  virtual void SetUp() {
+    memory_tracker()->Clear();
+    memory_tracker()->SetThreadFilter(SbThreadGetId());
+  }
+
+  virtual void TearDown() { memory_tracker()->Clear(); }
+  static bool s_memory_tracker_enabled_;
+};
+bool MemoryTrackerImplTest::s_memory_tracker_enabled_ = false;
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryTrackerImplTest
+TEST_F(MemoryTrackerImplTest, NoMemTracking) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  ASSERT_EQ_NO_TRACKING(0, NumberOfAllocations());
+  scoped_ptr<int> dummy(new int());
+  EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+  {
+    // Now that memory allocation is disabled, no more allocations should
+    // be recorded.
+    NoMemTracking no_memory_tracking_in_this_scope;
+    int* dummy2 = new int();
+    EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+    delete dummy2;
+    EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+  }
+  scoped_ptr<int> dummy2(new int());
+  EXPECT_EQ_NO_TRACKING(2, NumberOfAllocations());
+  dummy.reset(NULL);
+  EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+  dummy2.reset(NULL);
+  EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations());
+}
+
+TEST_F(MemoryTrackerImplTest, RemovePointerOnNoMemoryTracking) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+
+  int* int_ptr = new int();
+  {
+    NoMemTracking no_memory_tracking_in_this_scope;
+    delete int_ptr;
+  }
+  EXPECT_FALSE_NO_TRACKING(pointer_map()->Get(int_ptr, NULL));
+}
+
+TEST_F(MemoryTrackerImplTest, NewDeleteOverridenTest) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations());
+  int* int_a = new int(0);
+  EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+  delete int_a;
+  EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations());
+}
+
+TEST_F(MemoryTrackerImplTest, TotalAllocationBytes) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  int32_t* int_a = new int32_t(0);
+  EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations());
+  EXPECT_EQ_NO_TRACKING(4, TotalAllocationBytes());
+  delete int_a;
+  EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations());
+}
+
+// Tests the expectation that a lot of allocations can be executed and that
+// internal data structures won't overflow.
+TEST_F(MemoryTrackerImplTest, NoStackOverflow) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  static const int kNumAllocations = 1000;
+  std::vector<int*> allocations;
+
+  // Also it turns out that this test is great for catching
+  // background threads pushing allocations through the allocator.
+  // This is supposed to be filtered, but if it's not then this test will
+  // fail.
+  // SbThreadYield() is used to give other threads a chance to enter into our
+  // allocator and catch a test failure.
+  SbThreadSleep(1);
+  ASSERT_EQ_NO_TRACKING(0, NumberOfAllocations());
+
+  for (int i = 0; i < kNumAllocations; ++i) {
+    SbThreadYield();
+    EXPECT_EQ_NO_TRACKING(i, NumberOfAllocations());
+    int* val = new int(0);
+    NoMemTracking no_tracking_in_scope;
+    allocations.push_back(val);
+  }
+
+  EXPECT_EQ_NO_TRACKING(kNumAllocations, NumberOfAllocations());
+
+  for (int i = 0; i < kNumAllocations; ++i) {
+    SbThreadYield();
+    EXPECT_EQ_NO_TRACKING(kNumAllocations - i, NumberOfAllocations());
+    delete allocations[i];
+  }
+
+  EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations());
+}
+
+// Tests the expectation that the macros will push/pop the memory scope.
+TEST_F(MemoryTrackerImplTest, MacrosPushPop) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  scoped_ptr<int> dummy;
+  {
+    TRACK_MEMORY_SCOPE("TestAllocations");
+    dummy.reset(new int());
+  }
+
+  scoped_ptr<int> dummy2(new int());
+
+  AllocationRecord alloc_rec;
+  pointer_map()->Get(dummy.get(), &alloc_rec);
+
+  ASSERT_TRUE_NO_TRACKING(alloc_rec.allocation_group);
+  EXPECT_EQ_NO_TRACKING(std::string("TestAllocations"),
+                        alloc_rec.allocation_group->name());
+
+  pointer_map()->Get(dummy2.get(), &alloc_rec);
+
+  ASSERT_TRUE_NO_TRACKING(alloc_rec.allocation_group);
+  EXPECT_EQ_NO_TRACKING(std::string("Unaccounted"),
+                        alloc_rec.allocation_group->name());
+}
+
+// Tests the expectation that if the cached flag on the NbMemoryScopeInfo is
+// set to false that the caching of the handle is not performed.
+TEST_F(MemoryTrackerImplTest, RespectsNonCachedHandle) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  const bool kCaching = false;
+  NbMemoryScopeInfo memory_scope = {
+      NULL, "MyName", __FILE__,
+      __LINE__, __FUNCTION__, false};  // false to disallow caching.
+
+  // Pushing the memory scope should trigger the caching operation to be
+  // attempted. However, because caching was explicitly disabled this handle
+  // should retain the value of 0.
+  NbPushMemoryScope(&memory_scope);
+  EXPECT_TRUE_NO_TRACKING(memory_scope.cached_handle_ == NULL);
+
+  // ... and still assert that the group was created with the expected name.
+  AllocationGroup* group = memory_tracker()->GetAllocationGroup("MyName");
+  // Equality check.
+  EXPECT_EQ_NO_TRACKING(0, group->name().compare("MyName"));
+  NbPopMemoryScope();
+}
+
+// Tests the expectation that if the cached flag on the NbMemoryScopeInfo is
+// set to true that the caching will be applied for the cached_handle of the
+// memory scope.
+TEST_F(MemoryTrackerImplTest, PushAllocGroupCachedHandle) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  NbMemoryScopeInfo memory_scope = {
+      NULL,      // Cached handle.
+      "MyName",  // Memory scope name.
+      __FILE__, __LINE__, __FUNCTION__,
+      true  // Allows caching.
+  };
+
+  NbPushMemoryScope(&memory_scope);
+  EXPECT_TRUE_NO_TRACKING(memory_scope.cached_handle_ != NULL);
+  AllocationGroup* group = memory_tracker()->GetAllocationGroup("MyName");
+
+  EXPECT_EQ_NO_TRACKING(memory_scope.cached_handle_,
+                        static_cast<void*>(group));
+}
+
+// Tests the expectation that the macro TRACK_MEMORY_SCOPE will capture the
+// allocation in the MemoryTrackerImpl.
+TEST_F(MemoryTrackerImplTest, MacrosGroupAccounting) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  MemoryTrackerImpl* track_alloc = memory_tracker();  // Debugging.
+  track_alloc->Clear();
+
+  memory_tracker()->Clear();
+  const AllocationGroup* group_a =
+      memory_tracker()->GetAllocationGroup("MemoryTrackerTest-ScopeA");
+
+  const AllocationGroup* group_b =
+      memory_tracker()->GetAllocationGroup("MemoryTrackerTest-ScopeB");
+
+  ASSERT_TRUE_NO_TRACKING(group_a);
+  ASSERT_TRUE_NO_TRACKING(group_b);
+
+  int32_t num_allocations = -1;
+  int64_t allocation_bytes = -1;
+
+  // Expect that both groups have no allocations in them.
+  group_a->GetAggregateStats(&num_allocations, &allocation_bytes);
+  EXPECT_EQ_NO_TRACKING(0, num_allocations);
+  EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+
+  group_b->GetAggregateStats(&num_allocations, &allocation_bytes);
+  EXPECT_EQ_NO_TRACKING(0, num_allocations);
+  EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+
+  scoped_ptr<int> alloc_a, alloc_b, alloc_b2;
+  {
+    TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA");
+    alloc_a.reset(new int());
+    {
+      TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeB");
+      alloc_b.reset(new int());
+      alloc_b2.reset(new int());
+
+      group_a->GetAggregateStats(&num_allocations, &allocation_bytes);
+      EXPECT_EQ_NO_TRACKING(1, num_allocations);
+      EXPECT_EQ_NO_TRACKING(4, allocation_bytes);
+      alloc_a.reset(NULL);
+      group_a->GetAggregateStats(&num_allocations, &allocation_bytes);
+      EXPECT_EQ_NO_TRACKING(0, num_allocations);
+      EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+
+      num_allocations = allocation_bytes = -1;
+      group_b->GetAggregateStats(&num_allocations, &allocation_bytes);
+      EXPECT_EQ_NO_TRACKING(2, num_allocations);
+      EXPECT_EQ_NO_TRACKING(8, allocation_bytes);
+
+      alloc_b2.reset(NULL);
+      group_b->GetAggregateStats(&num_allocations, &allocation_bytes);
+      EXPECT_EQ_NO_TRACKING(1, num_allocations);
+      EXPECT_EQ_NO_TRACKING(4, allocation_bytes);
+
+      alloc_b.reset(NULL);
+      group_b->GetAggregateStats(&num_allocations, &allocation_bytes);
+      EXPECT_EQ_NO_TRACKING(0, num_allocations);
+      EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+    }
+  }
+}
+
+// Tests the expectation that the visitor can access the allocations.
+TEST_F(MemoryTrackerImplTest, VisitorAccess) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  class SimpleVisitor : public AllocationVisitor {
+   public:
+    SimpleVisitor() : num_memory_allocs_(0) {}
+    virtual bool Visit(const void* memory,
+                       const AllocationRecord& alloc_record) {
+      num_memory_allocs_++;
+      return true;  // Keep traversing.
+    }
+
+    size_t num_memory_allocs_;
+  };
+
+  SimpleVisitor visitor;
+  scoped_ptr<int> int_ptr(new int);
+
+  // Should see the int_ptr allocation.
+  memory_tracker()->Accept(&visitor);
+  EXPECT_EQ_NO_TRACKING(1, visitor.num_memory_allocs_);
+  visitor.num_memory_allocs_ = 0;
+
+  int_ptr.reset(NULL);
+  // Now no allocations should be available.
+  memory_tracker()->Accept(&visitor);
+  EXPECT_EQ_NO_TRACKING(0, visitor.num_memory_allocs_);
+}
+
+// A stress test that rapidly adds allocations, but saves all deletions
+// for the main thread. This test will catch concurrency errors related
+// to reporting new allocations.
+TEST_F(MemoryTrackerImplTest, MultiThreadedStressAddTest) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  // Disable allocation filtering.
+  memory_tracker()->SetThreadFilter(kSbThreadInvalidId);
+
+  std::vector<nb::TestThread*> threads;
+
+  const int kNumObjectsToAdd = 10000 / NUM_STRESS_TEST_THREADS;
+  AddAllocationStressThread::AllocMap map;
+  starboard::Mutex map_mutex;
+
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    nb::TestThread* thread = new AddAllocationStressThread(
+        memory_tracker(), kNumObjectsToAdd, &map, &map_mutex);
+
+    threads.push_back(thread);
+  }
+
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    threads[i]->Start();
+  }
+
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    threads[i]->Join();
+  }
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    delete threads[i];
+  }
+
+  while (!map.empty()) {
+    const void* ptr = map.begin()->first;
+    map.erase(map.begin());
+
+    if (!memory_tracker()->GetMemoryTracking(ptr, NULL)) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "No tracking?!";
+    }
+
+    SbMemoryDeallocate(const_cast<void*>(ptr));
+    if (memory_tracker()->GetMemoryTracking(ptr, NULL)) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Tracking?!";
+    }
+  }
+}
+
+// Tests the expectation that memory scopes are multi-threaded safe.
+TEST_F(MemoryTrackerImplTest, MultiThreadedMemoryScope) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  memory_tracker()->SetThreadFilter(kSbThreadInvalidId);
+  TRACK_MEMORY_SCOPE("MultiThreadedStressUseTest");
+
+  std::vector<MemoryScopeThread*> threads;
+
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    threads.push_back(new MemoryScopeThread(memory_tracker()));
+  }
+
+  for (int i = 0; i < threads.size(); ++i) {
+    threads[i]->Start();
+  }
+
+  SbThreadSleep(STRESS_TEST_DURATION_SECONDS * 1000 * 1000);
+
+  for (int i = 0; i < threads.size(); ++i) {
+    threads[i]->Join();
+  }
+
+  for (int i = 0; i < threads.size(); ++i) {
+    delete threads[i];
+  }
+
+  threads.clear();
+}
+
+// Tests the expectation that new/delete can be done by different threads.
+TEST_F(MemoryTrackerImplTest, MultiThreadedStressUseTest) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+  // Disable allocation filtering.
+  memory_tracker()->SetThreadFilter(kSbThreadInvalidId);
+  TRACK_MEMORY_SCOPE("MultiThreadedStressUseTest");
+
+  std::vector<AllocationStressThread*> threads;
+
+  for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) {
+    threads.push_back(new AllocationStressThread(memory_tracker()));
+  }
+
+  for (int i = 0; i < threads.size(); ++i) {
+    threads[i]->Start();
+  }
+
+  SbThreadSleep(STRESS_TEST_DURATION_SECONDS * 1000 * 1000);
+
+  for (int i = 0; i < threads.size(); ++i) {
+    threads[i]->Join();
+  }
+
+  for (int i = 0; i < threads.size(); ++i) {
+    delete threads[i];
+  }
+
+  threads.clear();
+}
+
+//////////////////////////// Implementation ///////////////////////////////////
+/// Impl of AllocationStressThread
+AllocationStressThread::AllocationStressThread(MemoryTrackerImpl* tracker)
+    : memory_tracker_(tracker), finished_(false) {
+  static int counter = 0;
+  std::stringstream ss;
+  ss << "AllocStressThread-" << counter++;
+  unique_name_ = ss.str();
+}
+
+AllocationStressThread::~AllocationStressThread() {
+  if (!allocated_pts_.empty()) {
+    ADD_FAILURE_AT(__FILE__, __LINE__) << "allocated pointers still exist";
+  }
+}
+
+void AllocationStressThread::Join() {
+  finished_ = true;
+  nb::TestThread::Join();
+}
+
+void AllocationStressThread::CheckPointers() {
+  typedef AllocMap::iterator Iter;
+
+  for (Iter it = allocated_pts_.begin(); it != allocated_pts_.end(); ++it) {
+    const void* ptr = it->first;
+    const bool found = memory_tracker_->GetMemoryTracking(ptr, NULL);
+    if (!found) {
+      NoMemTracking no_tracking_in_scope;
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Not found";
+    }
+  }
+}
+
+void AllocationStressThread::Run() {
+  while (!finished_) {
+    const bool do_delete = FlipCoin();
+    if (FlipCoin()) {
+      DoDelete();
+    } else {
+      DoMalloc();
+    }
+    CheckPointers();
+
+    // Randomly give other threads the opportunity run.
+    if (FlipCoin()) {
+      SbThreadYield();
+    }
+  }
+
+  // Clear out all memory.
+  while (DoDelete()) {
+    ;
+  }
+}
+
+bool AllocationStressThread::RemoveRandomAllocation(
+    std::pair<const void*, AllocationRecord>* output) {
+  if (allocated_pts_.empty()) {
+    return false;
+  }
+
+  // Select a random pointer to delete.
+  int idx = std::rand() % allocated_pts_.size();
+  AllocMap::iterator iter = allocated_pts_.begin();
+  while (idx > 0) {
+    idx--;
+    iter++;
+  }
+  output->first = iter->first;
+  output->second = iter->second;
+  allocated_pts_.erase(iter);
+  return true;
+}
+
+bool AllocationStressThread::DoDelete() {
+  NoMemTracking no_memory_tracking_in_this_scope;
+  ++do_delete_counter_;
+
+  std::pair<const void*, AllocationRecord> alloc;
+  if (!RemoveRandomAllocation(&alloc)) {
+    return false;
+  }
+
+  const void* ptr = alloc.first;
+  const AllocationRecord expected_alloc_record = alloc.second;
+
+  TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str());
+  AllocationGroup* current_group = memory_tracker_->PeekAllocationGroup();
+
+  // Expect that the name of the current allocation group name is the same as
+  // what we expect.
+  if (current_group->name() != unique_name_) {
+    NoMemTracking no_memory_tracking_in_this_scope;
+    ADD_FAILURE_AT(__FILE__, __LINE__) << " " << current_group->name()
+                                       << " != " << unique_name_;
+  }
+
+  MemoryTrackerImpl::AllocationMapType* internal_alloc_map =
+      memory_tracker_->pointer_map();
+
+  AllocationRecord existing_alloc_record;
+
+  const bool found_existing_record =
+      memory_tracker_->GetMemoryTracking(ptr, &existing_alloc_record);
+
+  if (!found_existing_record) {
+    ADD_FAILURE_AT(__FILE__, __LINE__)
+        << "expected to find existing record, but did not";
+  } else if (current_group != existing_alloc_record.allocation_group) {
+    ADD_FAILURE_AT(__FILE__, __LINE__)
+        << "group allocation mismatch: " << current_group->name()
+        << " != " << existing_alloc_record.allocation_group->name() << "\n";
+  }
+  SbMemoryDeallocate(const_cast<void*>(ptr));
+  return true;
+}
+
+void AllocationStressThread::DoMalloc() {
+  ++do_malloc_counter_;
+  if (allocated_pts_.size() > 10000) {
+    return;
+  }
+
+  TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str());
+  AllocationGroup* current_group = memory_tracker_->PeekAllocationGroup();
+
+  // Sanity check, make sure that the current_group name is the same as
+  // our unique name.
+  if (current_group->name() != unique_name_) {
+    NoMemTracking no_tracking_in_scope;
+    ADD_FAILURE_AT(__FILE__, __LINE__) << " " << current_group->name()
+                                       << " != " << unique_name_;
+  }
+
+  if (!memory_tracker_->IsMemoryTrackingEnabled()) {
+    NoMemTracking no_tracking_in_scope;
+    ADD_FAILURE_AT(__FILE__, __LINE__)
+        << " memory tracking state was disabled.";
+  }
+
+  const int alloc_size = std::rand() % 100 + 8;
+
+  void* memory = SbMemoryAllocate(alloc_size);
+
+  AllocationRecord record;
+  bool found = memory_tracker_->GetMemoryTracking(memory, &record);
+  if (!found) {
+    NoMemTracking no_tracking_in_scope;
+    ADD_FAILURE_AT(__FILE__, __LINE__)
+        << "Violated expectation, malloc counter: " << do_malloc_counter_;
+  }
+  AllocMap::iterator found_it = allocated_pts_.find(memory);
+
+  if (found_it != allocated_pts_.end()) {
+    NoMemTracking no_tracking_in_scope;
+    ADD_FAILURE_AT(__FILE__, __LINE__)
+        << "This pointer should not be in the map.";
+  }
+
+  NoMemTracking no_tracking_in_scope;
+  allocated_pts_[memory] = AllocationRecord(alloc_size, current_group);
+}
+
+}  // namespace
+}  // namespace analytics
+}  // namespace nb
diff --git a/src/nb/analytics/memory_tracker_test.cc b/src/nb/analytics/memory_tracker_test.cc
new file mode 100644
index 0000000..92cb8b3
--- /dev/null
+++ b/src/nb/analytics/memory_tracker_test.cc
@@ -0,0 +1,222 @@
+/*
+ * 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/analytics/memory_tracker.h"
+#include "nb/analytics/memory_tracker_helpers.h"
+#include "nb/memory_scope.h"
+#include "nb/scoped_ptr.h"
+#include "starboard/memory.h"
+#include "starboard/memory_reporter.h"
+#include "starboard/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace nb {
+namespace analytics {
+namespace {
+
+MemoryTracker* s_memory_tracker_ = NULL;
+
+struct NoMemTracking {
+  bool prev_val;
+  NoMemTracking() : prev_val(false) {
+    if (s_memory_tracker_) {
+      prev_val = s_memory_tracker_->IsMemoryTrackingEnabled();
+      s_memory_tracker_->SetMemoryTrackingEnabled(false);
+    }
+  }
+  ~NoMemTracking() {
+    if (s_memory_tracker_) {
+      s_memory_tracker_->SetMemoryTrackingEnabled(prev_val);
+    }
+  }
+};
+
+// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when
+// for unit testing allocations. These overrides disable memory
+// tracking for the duration of the EXPECT and ASSERT operations.
+#define EXPECT_EQ_NO_TRACKING(A, B)                 \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_EQ(A, B);                                \
+  }
+
+#define EXPECT_TRUE_NO_TRACKING(A)                  \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_TRUE(A);                                 \
+  }
+
+#define EXPECT_FALSE_NO_TRACKING(A)                 \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    EXPECT_FALSE(A);                                \
+  }
+
+#define ASSERT_TRUE_NO_TRACKING(A)                  \
+  {                                                 \
+    NoMemTracking no_memory_tracking_in_this_scope; \
+    ASSERT_TRUE(A);                                 \
+  }
+
+///////////////////////////////////////////////////////////////////////////////
+// Framework which initializes the MemoryTracker once and installs it
+// for the first test and the removes the MemoryTracker after the
+// the last test finishes.
+class MemoryTrackerTest : public ::testing::Test {
+ public:
+  MemoryTrackerTest() {}
+
+  MemoryTracker* memory_tracker() { return s_memory_tracker_; }
+
+  bool GetAllocRecord(void* alloc_memory, AllocationRecord* output) {
+    return memory_tracker()->GetMemoryTracking(alloc_memory, output);
+  }
+
+  int64_t TotalNumberOfAllocations() {
+    return memory_tracker()->GetTotalNumberOfAllocations();
+  }
+
+  int64_t TotalAllocationBytes() {
+    return memory_tracker()->GetTotalAllocationBytes();
+  }
+
+  bool MemoryTrackerEnabled() const { return s_memory_tracker_enabled_; }
+
+ protected:
+  static void SetUpTestCase() {
+    s_memory_tracker_ = MemoryTracker::Get();
+    s_memory_tracker_enabled_ = s_memory_tracker_->InstallGlobalTrackingHooks();
+  }
+  static void TearDownTestCase() {
+    s_memory_tracker_->RemoveGlobalTrackingHooks();
+  }
+  static bool s_memory_tracker_enabled_;
+};
+bool MemoryTrackerTest::s_memory_tracker_enabled_ = false;
+
+///////////////////////////////////////////////////////////////////////////////
+class FindAllocationVisitor : public AllocationVisitor {
+ public:
+  FindAllocationVisitor() : found_(false), memory_to_find_(NULL) {}
+
+  bool found() const { return found_; }
+  void set_found(bool val) { found_ = val; }
+  void set_memory_to_find(const void* memory) {
+    memory_to_find_ = memory;
+    found_ = false;
+  }
+
+  virtual bool Visit(const void* memory, const AllocationRecord& alloc_record) {
+    if (memory_to_find_ == memory) {
+      found_ = true;
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  bool found_;
+  const void* memory_to_find_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+TEST_F(MemoryTrackerTest, MacrosScopedObject) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+
+  scoped_ptr<int> alloc_a, alloc_b;
+  {
+    TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA");
+    alloc_a.reset(new int());
+    {
+      TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeB");
+      alloc_b.reset(new int());
+    }
+  }
+
+  // Now test that the allocations now exist in the memory tracker.
+  AllocationRecord alloc_record_a, alloc_record_b;
+  // Expect that the allocations exist and that the AllocRecords are written
+  // with the allocation information.
+  EXPECT_TRUE_NO_TRACKING(
+      memory_tracker()->GetMemoryTracking(alloc_a.get(), &alloc_record_a));
+  EXPECT_TRUE_NO_TRACKING(
+      memory_tracker()->GetMemoryTracking(alloc_b.get(), &alloc_record_b));
+
+  // Sanity test that the allocations are non-null.
+
+  const AllocationGroup* group_a = alloc_record_a.allocation_group;
+  const AllocationGroup* group_b = alloc_record_b.allocation_group;
+  ASSERT_TRUE_NO_TRACKING(group_a);
+  ASSERT_TRUE_NO_TRACKING(group_b);
+
+  EXPECT_EQ_NO_TRACKING(group_a->name(),
+                        std::string("MemoryTrackerTest-ScopeA"));
+  EXPECT_EQ_NO_TRACKING(group_b->name(),
+                        std::string("MemoryTrackerTest-ScopeB"));
+
+  // When the allocation is returned to the free store then it's expected that
+  // the memory tracker will indicate that the allocation no longer exists.
+  alloc_a.reset();
+  alloc_b.reset();
+
+  EXPECT_FALSE_NO_TRACKING(
+      memory_tracker()->GetMemoryTracking(alloc_a.get(), &alloc_record_a));
+  EXPECT_FALSE_NO_TRACKING(
+      memory_tracker()->GetMemoryTracking(alloc_b.get(), &alloc_record_b));
+
+  int32_t num_allocations = -1;
+  int64_t allocation_bytes = -1;
+
+  group_a->GetAggregateStats(&num_allocations, &allocation_bytes);
+  EXPECT_EQ_NO_TRACKING(0, num_allocations);
+  EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+
+  group_b->GetAggregateStats(&num_allocations, &allocation_bytes);
+  EXPECT_EQ_NO_TRACKING(0, num_allocations);
+  EXPECT_EQ_NO_TRACKING(0, allocation_bytes);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+TEST_F(MemoryTrackerTest, Visitor) {
+  // Memory tracker is not enabled for this build.
+  if (!MemoryTrackerEnabled()) {
+    return;
+  }
+
+  FindAllocationVisitor visitor;
+
+  scoped_ptr<int> alloc_a;
+  {
+    TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA");
+
+    alloc_a.reset(new int());
+    visitor.set_memory_to_find(alloc_a.get());
+    memory_tracker()->Accept(&visitor);
+    EXPECT_TRUE_NO_TRACKING(visitor.found());
+
+    alloc_a.reset(NULL);
+    visitor.set_found(false);
+    memory_tracker()->Accept(&visitor);
+    EXPECT_FALSE_NO_TRACKING(visitor.found());
+  }
+}
+
+}  // namespace
+}  // namespace analytics
+}  // namespace nb
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/fixed_no_free_allocator.cc b/src/nb/fixed_no_free_allocator.cc
index 3722da6..53a615f 100644
--- a/src/nb/fixed_no_free_allocator.cc
+++ b/src/nb/fixed_no_free_allocator.cc
@@ -27,28 +27,6 @@
   next_memory_ = memory_start_;
 }
 
-void* FixedNoFreeAllocator::Allocate(std::size_t size,
-                                     std::size_t alignment,
-                                     bool align_pointer) {
-  // Find the next aligned memory available.
-  uint8_t* aligned_next_memory =
-      AsPointer(AlignUp(AsInteger(next_memory_), alignment));
-
-  if (aligned_next_memory + size < aligned_next_memory) {
-    // "aligned_next_memory + size" overflows.
-    return NULL;
-  }
-
-  if (aligned_next_memory + size > memory_end_) {
-    // We don't have enough memory available to make this allocation.
-    return NULL;
-  }
-
-  void* memory_pointer = align_pointer ? aligned_next_memory : next_memory_;
-  next_memory_ = aligned_next_memory + size;
-  return memory_pointer;
-}
-
 void FixedNoFreeAllocator::Free(void* memory) {
   // Nothing to do here besides ensure that the freed memory belongs to us.
   SB_DCHECK(memory >= memory_start_);
@@ -67,4 +45,30 @@
   SB_NOTIMPLEMENTED();
 }
 
+void* FixedNoFreeAllocator::Allocate(std::size_t* size,
+                                     std::size_t alignment,
+                                     bool align_pointer) {
+  // Find the next aligned memory available.
+  uint8_t* aligned_next_memory =
+      AsPointer(AlignUp(AsInteger(next_memory_), alignment));
+
+  if (aligned_next_memory + *size < aligned_next_memory) {
+    // "aligned_next_memory + size" overflows.
+    return NULL;
+  }
+
+  if (aligned_next_memory + *size > memory_end_) {
+    // We don't have enough memory available to make this allocation.
+    return NULL;
+  }
+
+  if (!align_pointer) {
+    *size += AsInteger(aligned_next_memory) - AsInteger(next_memory_);
+  }
+
+  void* memory_pointer = align_pointer ? aligned_next_memory : next_memory_;
+  next_memory_ = aligned_next_memory + *size;
+  return memory_pointer;
+}
+
 }  // namespace nb
diff --git a/src/nb/fixed_no_free_allocator.h b/src/nb/fixed_no_free_allocator.h
index 696a7c0..31bfc60 100644
--- a/src/nb/fixed_no_free_allocator.h
+++ b/src/nb/fixed_no_free_allocator.h
@@ -37,22 +37,23 @@
 class FixedNoFreeAllocator : public Allocator {
  public:
   FixedNoFreeAllocator(void* memory_start, std::size_t memory_size);
-  void* Allocate(std::size_t size) { return Allocate(size, 1, true); }
+  void* Allocate(std::size_t size) { return Allocate(&size, 1, true); }
 
   void* Allocate(std::size_t size, std::size_t alignment) {
-    return Allocate(size, alignment, true);
+    return Allocate(&size, alignment, true);
   }
 
-  void* AllocateForAlignment(std::size_t size, std::size_t alignment) {
+  void* AllocateForAlignment(std::size_t* size, std::size_t alignment) {
     return Allocate(size, alignment, false);
   }
-  void* Allocate(std::size_t size, std::size_t alignment, bool align_pointer);
   void Free(void* memory);
   std::size_t GetCapacity() const;
   std::size_t GetAllocated() const;
   void PrintAllocations() const;
 
  private:
+  void* Allocate(std::size_t* size, std::size_t alignment, bool align_pointer);
+
   // The start of our memory range, as passed in to the constructor.
   void* const memory_start_;
 
diff --git a/src/nb/hash.cc b/src/nb/hash.cc
new file mode 100644
index 0000000..b04c7d5
--- /dev/null
+++ b/src/nb/hash.cc
@@ -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.
+ */
+
+#include "nb/hash.h"
+
+#include "starboard/types.h"
+
+namespace nb {
+
+namespace {
+// Injects the super fast hash into the nb namespace.
+#include "third_party/super_fast_hash/super_fast_hash.cc"
+}  // namespace
+
+uint32_t RuntimeHash32(const char* data, int length, uint32_t prev_hash) {
+  return SuperFastHash(data, length, prev_hash);
+}
+
+}  // namespace nb
diff --git a/src/nb/hash.h b/src/nb/hash.h
new file mode 100644
index 0000000..fe48e3d
--- /dev/null
+++ b/src/nb/hash.h
@@ -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.
+ */
+
+#ifndef NB_HASH_H_
+#define NB_HASH_H_
+
+#include "starboard/types.h"
+
+namespace nb {
+
+enum { kDefaultSeed = 0x12345789 };
+
+// RuntimeHash32 is a 32 bit hash for data. The only guarantee is that this
+// hash is persistent for the lifetime of the program. This hash function
+// is not guaranteed to be consistent across platforms. The hash value should
+// never be saved to disk.
+// It's sufficient however, to use as an in-memory hashtable.
+uint32_t RuntimeHash32(const char* data,
+                       int length,
+                       uint32_t prev_hash = kDefaultSeed);
+
+}  // namespace nb
+
+#endif  // NB_HASH_H_
diff --git a/src/nb/memory_pool.cc b/src/nb/memory_pool.cc
index 840042c..4ae9a7e 100644
--- a/src/nb/memory_pool.cc
+++ b/src/nb/memory_pool.cc
@@ -23,15 +23,21 @@
 MemoryPool::MemoryPool(void* buffer,
                        std::size_t size,
                        bool thread_safe,
-                       bool verify_full_capacity)
+                       bool verify_full_capacity,
+                       std::size_t small_allocation_threshold)
     : no_free_allocator_(buffer, size),
       reuse_allocator_(
-          scoped_ptr<Allocator>(new ReuseAllocator(&no_free_allocator_)),
+          scoped_ptr<Allocator>(new ReuseAllocator(&no_free_allocator_,
+                                                   size,
+                                                   small_allocation_threshold)),
           thread_safe) {
   SB_DCHECK(buffer);
   SB_DCHECK(size != 0U);
 
-  if (verify_full_capacity) {
+  // This is redundant if ReuseAllocator::Allcator() can allocate the difference
+  // between the requested size and the last free block from the fallback
+  // allocator and combine the blocks.
+  if (verify_full_capacity && small_allocation_threshold == 0) {
     void* p = Allocate(size);
     SB_DCHECK(p);
     Free(p);
diff --git a/src/nb/memory_pool.h b/src/nb/memory_pool.h
index cef03db..b81e893 100644
--- a/src/nb/memory_pool.h
+++ b/src/nb/memory_pool.h
@@ -29,30 +29,16 @@
 // as necessary.
 class MemoryPool : public Allocator {
  public:
-  // When |verify_full_capacity| is true, the ctor tries to allocate the whole
-  // budget and free it immediately.  This can:
-  // 1. Ensure the |size| is accurate after accounting for all implicit
-  //    alignment enforced by the underlying allocators.
-  // 2. The |reuse_allocator_| contains a free block of the whole budget.  As
-  //    the |reuse_allocator_| doesn't support extending of free block, an
-  //    allocation that is larger than the both biggest free block in the
-  //    |reuse_allocator_| and the remaining memory inside the
-  //    |no_free_allocator_| will fail even if the combination of both can
-  //    fulfill the allocation.
-  // Note that when |verify_full_capacity| is true, GetHighWaterMark() always
-  // return the budget, which makes memory usage tracking useless.
   MemoryPool(void* buffer,
              std::size_t size,
              bool thread_safe,
-             bool verify_full_capacity = false);
+             bool verify_full_capacity = false,
+             std::size_t small_allocation_threshold = 0);
 
   void* Allocate(std::size_t size) { return reuse_allocator_.Allocate(size); }
   void* Allocate(std::size_t size, std::size_t alignment) {
     return reuse_allocator_.Allocate(size, alignment);
   }
-  void* AllocateForAlignment(std::size_t size, std::size_t alignment) {
-    return reuse_allocator_.AllocateForAlignment(size, alignment);
-  }
   void Free(void* memory) { reuse_allocator_.Free(memory); }
   std::size_t GetCapacity() const { return reuse_allocator_.GetCapacity(); }
   std::size_t GetAllocated() const { return reuse_allocator_.GetAllocated(); }
diff --git a/src/nb/memory_scope.cc b/src/nb/memory_scope.cc
new file mode 100644
index 0000000..f475a16
--- /dev/null
+++ b/src/nb/memory_scope.cc
@@ -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.
+ */
+
+#include "nb/memory_scope.h"
+#include "starboard/log.h"
+#include "starboard/atomic.h"
+
+NbMemoryScopeReporter* s_memory_reporter_ = NULL;
+
+bool NbSetMemoryScopeReporter(NbMemoryScopeReporter* reporter) {
+  // Flush all the pending memory writes out to main memory so that
+  // other threads see a fully constructed reporter.
+  SbAtomicMemoryBarrier();
+  s_memory_reporter_ = reporter;
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  SbLogRaw("\nMemory Scope Reporting is disabled because this build does "
+           "not support it. Try a QA, devel or debug build.\n");
+  return false;
+#else
+  return true;
+#endif
+}
+
+void NbPushMemoryScope(NbMemoryScopeInfo* memory_scope_info) {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter_)) {
+    return;
+  }
+  s_memory_reporter_->push_memory_scope_cb(
+    s_memory_reporter_->context,
+    memory_scope_info);
+#endif
+}
+
+void NbPopMemoryScope() {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter_)) {
+    return;
+  }
+  s_memory_reporter_->pop_memory_scope_cb(s_memory_reporter_->context);
+#endif
+}
diff --git a/src/nb/memory_scope.h b/src/nb/memory_scope.h
new file mode 100644
index 0000000..72e1ddc
--- /dev/null
+++ b/src/nb/memory_scope.h
@@ -0,0 +1,143 @@
+/*
+ * 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_MEMORY_SCOPE_H_
+#define NB_MEMORY_SCOPE_H_
+
+///////////////////////////////////////////////////////////////////////////////
+// Macros to define the memory scope objects. These are objects that are used
+// to annotate sections of the code base as belonging to a particular memory
+// scope. Note that this is an annotation and does not memory allocation.
+///////////////////////////////////////////////////////////////////////////////
+
+// Macro to track the memory scope inside a function or block of code. The
+// memory scope is in effect until the end of the code block.
+// Example:
+//   void Foo() {
+//     TRACK_MEMORY_SCOPE("FooMemoryScope");
+//     // pops the memory scope at the end.
+//   }
+
+#if !defined(__cplusplus)
+// Disallow macro use for non-C++ builds.
+#define TRACK_MEMORY_SCOPE(STR) error_forbidden_in_non_c_plus_plus_code
+#define TRACK_MEMORY_SCOPE_DYNAMIC(STR) error_forbidden_in_non_c_plus_plus_code
+#elif defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+#define TRACK_MEMORY_SCOPE(STR) TRACK_MEMORY_STATIC_CACHED(STR)
+#define TRACK_MEMORY_SCOPE_DYNAMIC(STR) TRACK_MEMORY_STATIC_NOT_CACHED(STR)
+#else
+// No-op when starboard does not allow memory tracking.
+#define TRACK_MEMORY_SCOPE(STR)
+#define TRACK_MEMORY_SCOPE_DYNAMIC(STR)
+#endif
+
+// Preprocessor needs double expansion in order to __FILE__, __LINE__ and
+// __FUNCTION__ properly.
+#define TRACK_MEMORY_STATIC_CACHED(STR) \
+  TRACK_MEMORY_STATIC_CACHED_IMPL_2(STR, __FILE__, __LINE__, __FUNCTION__)
+
+#define TRACK_MEMORY_STATIC_NOT_CACHED(STR) \
+  TRACK_MEMORY_STATIC_NOT_CACHED_IMPL_2(STR, __FILE__, __LINE__, __FUNCTION__)
+
+// Only enable TRACK_MEMORY_STATIC_CACHED_IMPL_2 if starboard allows memory
+// tracking.
+#define TRACK_MEMORY_STATIC_CACHED_IMPL_2(Str, FileStr, LineNum, FuncStr) \
+  static NbMemoryScopeInfo memory_scope_handle_##LineNum =                \
+      { NULL, Str, FileStr, LineNum, FuncStr, true };                     \
+  NbPushMemoryScope(&memory_scope_handle_##LineNum);                      \
+  NbPopMemoryScopeOnScopeEnd pop_on_scope_end_##LineNum;
+
+#define TRACK_MEMORY_STATIC_NOT_CACHED_IMPL_2(Str, FileStr, LineNum, FuncStr) \
+  NbMemoryScopeInfo memory_scope_handle_##LineNum = {                         \
+      NULL, Str, FileStr, LineNum, FuncStr, false};                           \
+  NbPushMemoryScope(&memory_scope_handle_##LineNum);                          \
+  NbPopMemoryScopeOnScopeEnd pop_on_scope_end_##LineNum;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct NbMemoryScopeReporter;
+struct NbMemoryScopeInfo;
+
+// Sets the memory reporter. Returns true on success, false something
+// goes wrong.
+bool NbSetMemoryScopeReporter(NbMemoryScopeReporter* reporter);
+
+// Note that we pass by pointer because the memory scope contains a
+// variable allowing the result to be cached.
+void NbPushMemoryScope(NbMemoryScopeInfo* memory_scope);
+void NbPopMemoryScope();
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementation
+///////////////////////////////////////////////////////////////////////////////
+// Interface for handling memory scopes.
+typedef void (*NbReportPushMemoryScopeCallback)(void* context,
+                                                NbMemoryScopeInfo* info);
+typedef void (*NbReportPopMemoryScopeCallback)(void* context);
+
+struct NbMemoryScopeReporter {
+  // Callback to report pushing of memory scope.
+  NbReportPushMemoryScopeCallback push_memory_scope_cb;
+
+  // Callback to report poping of the memory scope.
+  NbReportPopMemoryScopeCallback pop_memory_scope_cb;
+
+  // Optional, is passed to the callbacks as first argument.
+  void* context;
+};
+
+// This MemoryScope must remain a POD data type so that it can be statically
+// initialized.
+struct NbMemoryScopeInfo {
+  // cached_handle_ allows a cached result of the the fields represented in
+  // this struct to be generated and the handle be placed into this field.
+  // See also allows_caching_.
+  void* cached_handle_;
+
+  // Represents the name of the memory scope. I.E. "Javascript" or "Gfx".
+  const char* memory_scope_name_;
+
+  // Represents the file name that this memory scope was created at.
+  const char* file_name_;
+
+  // Represents the line number that this memory scope was created at.
+  int line_number_;
+
+  // Represents the function name that this memory scope was created at.
+  const char* function_name_;
+
+  // When true, if cached_handle_ is 0 then an object may be created that
+  // represents the fields of this object. The handle that represents this
+  // cached object is then placed in cached_hanlde_.
+  bool allows_caching_;
+};
+
+// NbPopMemoryScopeOnScopeEnd is only allowed for C++ builds.
+#ifdef __cplusplus
+// A helper that pops the memory scope at the end of the current code block.
+struct NbPopMemoryScopeOnScopeEnd {
+  ~NbPopMemoryScopeOnScopeEnd() { NbPopMemoryScope(); }
+};
+#endif
+
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // NB_MEMORY_SCOPE_H_
diff --git a/src/nb/memory_scope_test.cc b/src/nb/memory_scope_test.cc
new file mode 100644
index 0000000..e734c2f
--- /dev/null
+++ b/src/nb/memory_scope_test.cc
@@ -0,0 +1,257 @@
+/*

+ * 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/memory_scope.h"

+#include "starboard/mutex.h"

+#include "nb/thread_local_object.h"

+

+#include "testing/gtest/include/gtest/gtest.h"

+

+namespace nb {

+namespace {

+

+bool StarboardAllowsMemoryTracking() {

+#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)

+  return true;

+#else

+  return false;

+#endif

+}

+

+// This is a memory scope reporter that is compatible

+// with the MemoryScopeReporter.

+class TestMemoryScopeReporter {

+ public:

+  typedef std::vector<NbMemoryScopeInfo*> MemoryScopeVector;

+

+  TestMemoryScopeReporter() {

+    memory_scope_reporter_ = CreateMemoryScopeReporter();

+  }

+

+  NbMemoryScopeReporter* memory_scope_reporter() {

+    return &memory_scope_reporter_;

+  }

+

+  MemoryScopeVector* stack_thread_local() { return stack_tlo_.GetOrCreate(); }

+

+  void OnPushMemoryScope(NbMemoryScopeInfo* memory_scope) {

+    stack_thread_local()->push_back(memory_scope);

+  }

+

+  void OnPopMemoryScope() {

+    MemoryScopeVector* stack = stack_thread_local();

+    if (!stack->empty()) {

+      stack->pop_back();

+    } else {

+      ADD_FAILURE_AT(__FILE__, __LINE__)

+          << " stack was empty and could not be popped.";

+    }

+  }

+

+ private:

+  static void OnPushMemoryScopeCallback(void* context,

+                                        NbMemoryScopeInfo* info) {

+    TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context);

+    t->OnPushMemoryScope(info);

+  }

+

+  static void OnPopMemoryScopeCallback(void* context) {

+    TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context);

+    t->OnPopMemoryScope();

+  }

+

+  NbMemoryScopeReporter CreateMemoryScopeReporter() {

+    NbMemoryScopeReporter reporter = {OnPushMemoryScopeCallback,

+                                      OnPopMemoryScopeCallback, this};

+    return reporter;

+  }

+

+  NbMemoryScopeReporter memory_scope_reporter_;

+  ThreadLocalObject<MemoryScopeVector> stack_tlo_;

+};

+

+// A test framework for testing the Pushing & popping memory scopes.

+// The key feature here is that reporter is setup on the first test

+// instance and torn down after the last test has run.

+class MemoryScopeReportingTest : public ::testing::Test {

+ public:

+  TestMemoryScopeReporter* test_memory_reporter() { return s_reporter_; }

+

+  bool reporting_enabled() const { return s_reporter_enabled_; }

+

+ protected:

+  static void SetUpTestCase() {

+    if (!s_reporter_) {

+      s_reporter_ = new TestMemoryScopeReporter;

+    }

+    s_reporter_enabled_ =

+        NbSetMemoryScopeReporter(s_reporter_->memory_scope_reporter());

+

+    EXPECT_EQ(StarboardAllowsMemoryTracking(), s_reporter_enabled_)

+        << "Expected the memory scope reporter to be enabled whenever "

+           "starboard memory tracking is allowed.";

+  }

+

+  static void TearDownTestCase() {

+    // The reporter itself is not deleted because other threads could

+    // be traversing through it's data structures. It's better just to leave

+    // the object alive for the purposes of this unit test and set the pointer

+    // to NULL.

+    // This is done in order to make the MemoryScopeReport object lock free.

+    // This increases performance and reduces complexity of design.

+    NbSetMemoryScopeReporter(NULL);

+  }

+

+  // Per test setup.

+  virtual void SetUp() {

+    test_memory_reporter()->stack_thread_local()->clear();

+  }

+

+  static TestMemoryScopeReporter* s_reporter_;

+  static bool s_reporter_enabled_;

+};

+TestMemoryScopeReporter* MemoryScopeReportingTest::s_reporter_ = NULL;

+bool MemoryScopeReportingTest::s_reporter_enabled_;

+

+///////////////////////////////////////////////////////////////////////////////

+// TESTS.

+// There are two sets of tests: POSITIVE and NEGATIVE.

+//  The positive tests are active when STARBOARD_ALLOWS_MEMORY_TRACKING is

+//  defined and test that memory tracking is enabled.

+//  NEGATIVE tests ensure that tracking is disabled when

+//  STARBOARD_ALLOWS_MEMORY_TRACKING is not defined.

+// When adding new tests:

+//  POSITIVE tests are named normally.

+//  NEGATIVE tests are named with "No" prefixed to the beginning.

+//  Example:

+//   TEST_F(MemoryScopeReportingTest, PushPop) <--- POSITIVE test.

+//   TEST_F(MemoryScopeReportingTest, NoPushPop) <- NEGATIVE test.

+//  All positive & negative tests are grouped together.

+///////////////////////////////////////////////////////////////////////////////

+

+#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)

+// These are POSITIVE tests, which test the expectation that when the define

+// STARBOARD_ALLOWS_MEMORY_TRACKING is active that the memory scope reporter

+// will receive memory scope notifications.

+

+// Tests the assumption that the SbMemoryAllocate and SbMemoryDeallocate

+// will report memory allocations.

+TEST_F(MemoryScopeReportingTest, PushPop) {

+  ASSERT_TRUE(reporting_enabled());

+  const int line_number = __LINE__;

+  const char* file_name = __FILE__;

+  const char* function_name = __FUNCTION__;

+  NbMemoryScopeInfo info = {0,              // Cached value (null).

+                            "Javascript",   // Name of the memory scope.

+                            file_name,      // Filename that invoked this.

+                            line_number,    // Line number.

+                            function_name,  // Function name.

+                            true};          // true allows caching.

+

+  NbPushMemoryScope(&info);

+

+  ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty());

+  NbMemoryScopeInfo* info_ptr =

+      test_memory_reporter()->stack_thread_local()->front();

+

+  EXPECT_EQ(&info, info_ptr);

+  EXPECT_STREQ(info.file_name_, file_name);

+  EXPECT_STREQ(info.function_name_, function_name);

+  EXPECT_EQ(info.line_number_, line_number);

+

+  NbPopMemoryScope();

+  EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());

+}

+

+// Tests the expectation that the memory reporting macros will

+// push/pop memory regions and will also correctly bind to the

+// file, linenumber and also the function name.

+TEST_F(MemoryScopeReportingTest, Macros) {

+  ASSERT_TRUE(reporting_enabled());

+  // There should be no leftover stack objects.

+  EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());

+  {

+    const int line_before = __LINE__;

+    TRACK_MEMORY_SCOPE("TestMemoryScope");

+    const int predicted_line = line_before + 1;

+

+    NbMemoryScopeInfo* info_ptr =

+        test_memory_reporter()->stack_thread_local()->front();

+

+    // TRACK_MEMORY_SCOPE is defined to allow caching.

+    EXPECT_EQ(true, info_ptr->allows_caching_);

+

+    // The cached_handle_ is not mutated by TestMemoryScopeReporter so

+    // therefore it should be the default value of 0.

+    EXPECT_EQ(0, info_ptr->cached_handle_);

+

+    EXPECT_STREQ("TestMemoryScope", info_ptr->memory_scope_name_);

+    EXPECT_STREQ(__FILE__, info_ptr->file_name_);

+    EXPECT_EQ(predicted_line, info_ptr->line_number_);

+    EXPECT_STREQ(__FUNCTION__, info_ptr->function_name_);

+  }

+  // Expect that the stack object is now empty again.

+  EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());

+}

+

+#else  // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)

+// These are NEGATIVE tests, which test the expectation that when the

+// STARBOARD_ALLOWS_MEMORY_TRACKING is undefined that the memory scope reprter

+// does not receive memory scope notifications.

+

+// Tests the expectation that push pop does not send notifications to the

+// reporter when disabled.

+TEST_F(MemoryScopeReportingTest, NoPushPop) {

+  ASSERT_FALSE(reporting_enabled());

+  const int line_number = __LINE__;

+  const char* file_name = __FILE__;

+  const char* function_name = __FUNCTION__;

+  NbMemoryScopeInfo info = {0,              // Cached value (null).

+                            "Javascript",   // Name of the memory scope.

+                            file_name,      // Filename that invoked this.

+                            line_number,    // Line number.

+                            function_name,  // Function name.

+                            true};          // true allows caching.

+

+  NbPushMemoryScope(&info);

+

+  ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty());

+  NbMemoryScopeInfo* info_ptr =

+      test_memory_reporter()->stack_thread_local()->front();

+

+  EXPECT_EQ(&info, info_ptr);

+  EXPECT_STREQ(info.file_name_, file_name);

+  EXPECT_STREQ(info.function_name_, function_name);

+  EXPECT_EQ(info.line_number_, line_number);

+

+  NbPopMemoryScope();

+  EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty());

+}

+

+// Tests the expectation that the memory reporting macros are disabled when

+// memory tracking is not allowed.

+TEST_F(MemoryScopeReportingTest, NoMacros) {

+  ASSERT_FALSE(reporting_enabled());

+  // Test that the macros do nothing when memory reporting has been

+  // disabled.

+  TRACK_MEMORY_SCOPE("InternalMemoryRegion");

+  ASSERT_TRUE(test_memory_reporter()->stack_thread_local()->empty())

+      << "Memory reporting received notifications when it should be disabled.";

+}

+#endif

+

+}  // namespace.

+}  // namespace nb.

diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp
index 6f56df7..360ac03 100644
--- a/src/nb/nb.gyp
+++ b/src/nb/nb.gyp
@@ -20,31 +20,45 @@
       'variables': {
         'includes_starboard': 1,
       },
-      'sources': [
-        'allocator.h',
-        'allocator_decorator.cc',
-        'allocator_decorator.h',
-        'fixed_no_free_allocator.cc',
-        'fixed_no_free_allocator.h',
-        'memory_pool.cc',
-        'memory_pool.h',
-        'move.h',
-        'pointer_arithmetic.h',
-        'rect.h',
-        'ref_counted.cc',
-        'ref_counted.h',
-        'reuse_allocator.cc',
-        'reuse_allocator.h',
-        'scoped_ptr.h',
-        'thread_collision_warner.cc',
-        'thread_collision_warner.h',
-      ],
-
-      'dependencies': [
-        '<(DEPTH)/starboard/starboard.gyp:starboard',
-      ],
-
       'conditions': [
+        ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', {
+          'sources': [
+            'allocator.h',
+            'allocator_decorator.cc',
+            'allocator_decorator.h',
+            'analytics/memory_tracker.cc',
+            'analytics/memory_tracker.h',
+            'analytics/memory_tracker_impl.cc',
+            'analytics/memory_tracker_impl.h',
+            'analytics/memory_tracker_helpers.cc',
+            'analytics/memory_tracker_helpers.h',
+            'atomic.h',
+            'fixed_no_free_allocator.cc',
+            'fixed_no_free_allocator.h',
+            'hash.cc',
+            'hash.h',
+            'memory_pool.cc',
+            'memory_pool.h',
+            'memory_scope.cc',
+            'memory_scope.h',
+            'move.h',
+            'pointer_arithmetic.h',
+            'rect.h',
+            'ref_counted.cc',
+            'ref_counted.h',
+            'reuse_allocator.cc',
+            'reuse_allocator.h',
+            'scoped_ptr.h',
+            'simple_thread.cc',
+            'simple_thread.h',
+            'thread_collision_warner.cc',
+            'thread_collision_warner.h',
+            'thread_local_object.h',
+          ],
+          'dependencies': [
+            '<(DEPTH)/starboard/starboard.gyp:starboard',
+          ],
+        }],
         ['target_arch == "ps4"', {
           'sources': [
             'kernel_contiguous_allocator_ps4.cc',
@@ -59,15 +73,26 @@
     {
       'target_name': 'nb_test',
       'type': '<(gtest_target_type)',
-      'sources': [
-        'fixed_no_free_allocator_test.cc',
-        'reuse_allocator_test.cc',
-        'run_all_unittests.cc',
-      ],
-      'dependencies': [
-        'nb',
-        '<(DEPTH)/testing/gmock.gyp:gmock',
-        '<(DEPTH)/testing/gtest.gyp:gtest',
+      'conditions': [
+        ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', {
+          'sources': [
+            'analytics/memory_tracker_helpers_test.cc',
+            'analytics/memory_tracker_impl_test.cc',
+            'analytics/memory_tracker_test.cc',
+            'atomic_test.cc',
+            'fixed_no_free_allocator_test.cc',
+            'memory_scope_test.cc',
+            'reuse_allocator_test.cc',
+            'run_all_unittests.cc',
+            'test_thread.h',
+            'thread_local_object_test.cc',
+          ],
+          'dependencies': [
+            'nb',
+            '<(DEPTH)/testing/gmock.gyp:gmock',
+            '<(DEPTH)/testing/gtest.gyp:gtest',
+          ],
+        }]
       ],
     },
     {
@@ -79,6 +104,7 @@
       'variables': {
         'executable_name': 'nb_test',
       },
+      'includes': [ '../starboard/build/deploy.gypi' ],
     },
 
     {
diff --git a/src/nb/pointer_arithmetic.h b/src/nb/pointer_arithmetic.h
index 411308c..cb1539a 100644
--- a/src/nb/pointer_arithmetic.h
+++ b/src/nb/pointer_arithmetic.h
@@ -31,7 +31,7 @@
   return reinterpret_cast<uint8_t*>(integer_value);
 }
 
-// Helper method for subclasses to align addresses up to a specified value.
+// Helper method to align addresses up to a specified value.
 // Returns the the smallest value that is greater than or equal to value, but
 // aligned to alignment.
 template <typename T>
@@ -42,9 +42,20 @@
 
 template <typename T>
 T* AlignUp(T* value, uintptr_t alignment) {
-  uintptr_t decremented_value = AsInteger(value) - 1;
-  return reinterpret_cast<T*>(decremented_value + alignment -
-                              (decremented_value % alignment));
+  return reinterpret_cast<T*>(AlignUp(AsInteger(value), alignment));
+}
+
+// Helper method to align addresses down to a specified value.
+// Returns the the largest value that is less than or equal to value, but
+// aligned to alignment.
+template <typename T>
+T AlignDown(T value, T alignment) {
+  return value / alignment * alignment;
+}
+
+template <typename T>
+T* AlignDown(T* value, uintptr_t alignment) {
+  return reinterpret_cast<T*>(AlignDown(AsInteger(value), alignment));
 }
 
 // Helper method for subclasses to determine if a given address or value
diff --git a/src/nb/reuse_allocator.cc b/src/nb/reuse_allocator.cc
index 61a40a6..c12375d 100644
--- a/src/nb/reuse_allocator.cc
+++ b/src/nb/reuse_allocator.cc
@@ -24,10 +24,104 @@
 
 namespace nb {
 
-ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator) {
-  fallback_allocator_ = fallback_allocator;
-  capacity_ = 0;
-  total_allocated_ = 0;
+// Minimum block size to avoid extremely small blocks inside the block list and
+// to ensure that a zero sized allocation will return a non-zero sized block.
+const std::size_t kMinBlockSizeBytes = 16;
+
+bool ReuseAllocator::MemoryBlock::Merge(const MemoryBlock& other) {
+  if (AsInteger(address_) + size_ == AsInteger(other.address_)) {
+    size_ += other.size_;
+    return true;
+  }
+  if (AsInteger(other.address_) + other.size_ == AsInteger(address_)) {
+    address_ = other.address_;
+    size_ += other.size_;
+    return true;
+  }
+  return false;
+}
+
+bool ReuseAllocator::MemoryBlock::CanFullfill(std::size_t request_size,
+                                              std::size_t alignment) const {
+  const std::size_t extra_bytes_for_alignment =
+      AlignUp(AsInteger(address_), alignment) - AsInteger(address_);
+  const std::size_t aligned_size = request_size + extra_bytes_for_alignment;
+  return size_ >= aligned_size;
+}
+
+void ReuseAllocator::MemoryBlock::Allocate(std::size_t request_size,
+                                           std::size_t alignment,
+                                           bool allocate_from_front,
+                                           MemoryBlock* allocated,
+                                           MemoryBlock* free) const {
+  SB_DCHECK(allocated);
+  SB_DCHECK(free);
+  SB_DCHECK(CanFullfill(request_size, alignment));
+
+  // First we assume that the block is just enough to fulfill the allocation and
+  // leaves no free block.
+  allocated->address_ = address_;
+  allocated->size_ = size_;
+  free->address_ = NULL;
+  free->size_ = 0;
+
+  if (allocate_from_front) {
+    // |address_|
+    //     |     <- allocated_size ->     | <- remaining_size -> |
+    //     -------------------------------------------------------
+    //          |   <-  request_size ->   |                      |
+    //  |aligned_address|       |end_of_allocation|      |address_ + size_|
+    std::size_t aligned_address = AlignUp(AsInteger(address_), alignment);
+    std::size_t end_of_allocation = aligned_address + request_size;
+    std::size_t allocated_size = end_of_allocation - AsInteger(address_);
+    std::size_t remaining_size = size_ - allocated_size;
+    if (remaining_size < kMinBlockSizeBytes) {
+      return;
+    }
+    allocated->size_ = allocated_size;
+    free->address_ = AsPointer(end_of_allocation);
+    free->size_ = remaining_size;
+  } else {
+    // |address_|
+    //     |   <- remaining_size ->  |   <- allocated_size ->    |
+    //     -------------------------------------------------------
+    //                               |  <-  request_size -> |    |
+    //                       |aligned_address|           |address_ + size_|
+    std::size_t aligned_address =
+        AlignDown(AsInteger(address_) + size_ - request_size, alignment);
+    std::size_t allocated_size = AsInteger(address_) + size_ - aligned_address;
+    std::size_t remaining_size = size_ - allocated_size;
+    if (remaining_size < kMinBlockSizeBytes) {
+      return;
+    }
+    allocated->address_ = AsPointer(aligned_address);
+    allocated->size_ = allocated_size;
+    free->address_ = address_;
+    free->size_ = remaining_size;
+  }
+}
+
+ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator)
+    : fallback_allocator_(fallback_allocator),
+      small_allocation_threshold_(0),
+      capacity_(0),
+      total_allocated_(0) {}
+
+ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator,
+                               std::size_t capacity,
+                               std::size_t small_allocation_threshold)
+    : fallback_allocator_(fallback_allocator),
+      small_allocation_threshold_(small_allocation_threshold),
+      capacity_(0),
+      total_allocated_(0) {
+  // If |small_allocation_threshold_| is non-zero, this class will use last-fit
+  // strategy to fulfill small allocations.  Pre-allocator full capacity so
+  // last-fit makes sense.
+  if (small_allocation_threshold_ > 0) {
+    void* p = Allocate(capacity, 1);
+    SB_DCHECK(p);
+    Free(p);
+  }
 }
 
 ReuseAllocator::~ReuseAllocator() {
@@ -44,18 +138,14 @@
   }
 }
 
-void ReuseAllocator::AddFreeBlock(void* address, std::size_t size) {
-  MemoryBlock new_block;
-  new_block.address = address;
-  new_block.size = size;
-
+ReuseAllocator::FreeBlockSet::iterator ReuseAllocator::AddFreeBlock(
+    MemoryBlock block_to_add) {
   if (free_blocks_.size() == 0) {
-    free_blocks_.insert(new_block);
-    return;
+    return free_blocks_.insert(block_to_add).first;
   }
 
   // See if we can merge this block with one on the right or left.
-  FreeBlockSet::iterator it = free_blocks_.lower_bound(new_block);
+  FreeBlockSet::iterator it = free_blocks_.lower_bound(block_to_add);
   // lower_bound will return an iterator to our neighbor on the right,
   // if one exists.
   FreeBlockSet::iterator right_to_erase = free_blocks_.end();
@@ -63,9 +153,7 @@
 
   if (it != free_blocks_.end()) {
     MemoryBlock right_block = *it;
-    if (AsInteger(new_block.address) + new_block.size ==
-        AsInteger(right_block.address)) {
-      new_block.size += right_block.size;
+    if (block_to_add.Merge(right_block)) {
       right_to_erase = it;
     }
   }
@@ -75,10 +163,7 @@
     it--;
     MemoryBlock left_block = *it;
     // Are we contiguous with the block to our left?
-    if (AsInteger(left_block.address) + left_block.size ==
-        AsInteger(new_block.address)) {
-      new_block.address = left_block.address;
-      new_block.size += left_block.size;
+    if (block_to_add.Merge(left_block)) {
       left_to_erase = it;
     }
   }
@@ -90,7 +175,7 @@
     free_blocks_.erase(left_to_erase);
   }
 
-  free_blocks_.insert(new_block);
+  return free_blocks_.insert(block_to_add).first;
 }
 
 void ReuseAllocator::RemoveFreeBlock(FreeBlockSet::iterator it) {
@@ -101,11 +186,6 @@
   return Allocate(size, 1);
 }
 
-void* ReuseAllocator::AllocateForAlignment(std::size_t size,
-                                           std::size_t alignment) {
-  return Allocate(size, alignment);
-}
-
 void* ReuseAllocator::Allocate(std::size_t size, std::size_t alignment) {
   if (alignment == 0) {
     alignment = 1;
@@ -113,15 +193,14 @@
 
   // Try to satisfy request from free list.
   // First look for a block that is appropriately aligned.
-  // If we can't, look for a block that is big enough that we can
-  // carve out an aligned block.
+  // If we can't, look for a block that is big enough that we can carve out an
+  // aligned block.
   // If there is no such block, allocate more from our fallback allocator.
   void* user_address = 0;
 
-  // Keeping things rounded and aligned will help us
-  // avoid creating tiny and/or badly misaligned free blocks.
-  // Also ensure even for a 0-byte request we return a unique block.
-  const std::size_t kMinBlockSizeBytes = 16;
+  // Keeping things rounded and aligned will help us avoid creating tiny and/or
+  // badly misaligned free blocks.  Also ensure even for a 0-byte request we
+  // return a unique block.
   const std::size_t kMinAlignment = 16;
   size = std::max(size, kMinBlockSizeBytes);
   size = AlignUp(size, kMinBlockSizeBytes);
@@ -130,87 +209,65 @@
   // Worst case how much memory we need.
   MemoryBlock allocated_block;
 
-  // Start looking through the free list.
-  // If this is slow, we can store another map sorted by size.
-  for (FreeBlockSet::iterator it = free_blocks_.begin();
-       it != free_blocks_.end(); ++it) {
-    MemoryBlock block = *it;
-    const std::size_t extra_bytes_for_alignment =
-        (alignment - AsInteger(block.address) % alignment) % alignment;
-    const std::size_t aligned_size = size + extra_bytes_for_alignment;
-    if (block.size >= aligned_size) {
-      // The block is big enough.  We may waste some space due to alignment.
-      RemoveFreeBlock(it);
-      const std::size_t remaining_bytes = block.size - aligned_size;
-      if (remaining_bytes >= kMinBlockSizeBytes) {
-        AddFreeBlock(AsPointer(AsInteger(block.address) + aligned_size),
-                     remaining_bytes);
-        allocated_block.size = aligned_size;
-      } else {
-        allocated_block.size = block.size;
+  bool scan_from_front = size > small_allocation_threshold_;
+  FreeBlockSet::iterator free_block_iter = free_blocks_.end();
+
+  if (scan_from_front) {
+    // Start looking through the free list from the front.
+    for (FreeBlockSet::iterator it = free_blocks_.begin();
+         it != free_blocks_.end(); ++it) {
+      if (it->CanFullfill(size, alignment)) {
+        free_block_iter = it;
+        break;
       }
-      user_address = AlignUp(block.address, alignment);
-      allocated_block.address = block.address;
-      SB_DCHECK(allocated_block.size <= block.size);
-      break;
+    }
+  } else {
+    // Start looking through the free list from the back.
+    for (FreeBlockSet::reverse_iterator it = free_blocks_.rbegin();
+         it != free_blocks_.rend(); ++it) {
+      if (it->CanFullfill(size, alignment)) {
+        free_block_iter = it.base();
+        --free_block_iter;
+        break;
+      }
     }
   }
 
-  if (user_address == 0) {
-    // No free blocks found.
-    // Allocate one from the fallback allocator.
-    size = AlignUp(size, alignment);
-    void* ptr = fallback_allocator_->AllocateForAlignment(size, alignment);
+  if (free_block_iter == free_blocks_.end()) {
+    // No free blocks found, allocate one from the fallback allocator.
+    std::size_t block_size = size;
+    void* ptr =
+        fallback_allocator_->AllocateForAlignment(&block_size, alignment);
     if (ptr == NULL) {
       return NULL;
     }
-    uint8_t* memory_address = reinterpret_cast<uint8_t*>(ptr);
-    user_address = AlignUp(memory_address, alignment);
-    allocated_block.size = size;
-    allocated_block.address = user_address;
-
-    if (memory_address != user_address) {
-      std::size_t alignment_padding_size =
-          AsInteger(user_address) - AsInteger(memory_address);
-      if (alignment_padding_size >= kMinBlockSizeBytes) {
-        // Register the memory range skipped for alignment as a free block for
-        // later use.
-        AddFreeBlock(memory_address, alignment_padding_size);
-        capacity_ += alignment_padding_size;
-      } else {
-        // The memory range skipped for alignment is too small for a free block.
-        // Adjust the allocated block to include the alignment padding.
-        allocated_block.size += alignment_padding_size;
-        allocated_block.address = AsPointer(AsInteger(allocated_block.address) -
-                                            alignment_padding_size);
-      }
-    }
-
-    capacity_ += allocated_block.size;
+    free_block_iter = AddFreeBlock(MemoryBlock(ptr, block_size));
+    capacity_ += block_size;
     fallback_allocations_.push_back(ptr);
   }
+
+  MemoryBlock block = *free_block_iter;
+  // The block is big enough.  We may waste some space due to alignment.
+  RemoveFreeBlock(free_block_iter);
+
+  MemoryBlock free_block;
+  block.Allocate(size, alignment, scan_from_front, &allocated_block,
+                 &free_block);
+  if (free_block.size() > 0) {
+    SB_DCHECK(free_block.address());
+    AddFreeBlock(free_block);
+  }
+  user_address = AlignUp(allocated_block.address(), alignment);
+
   SB_DCHECK(allocated_blocks_.find(user_address) == allocated_blocks_.end());
   allocated_blocks_[user_address] = allocated_block;
-  total_allocated_ += allocated_block.size;
+  total_allocated_ += allocated_block.size();
   return user_address;
 }
 
 void ReuseAllocator::Free(void* memory) {
-  if (!memory) {
-    return;
-  }
-
-  AllocatedBlockMap::iterator it = allocated_blocks_.find(memory);
-  SB_DCHECK(it != allocated_blocks_.end());
-
-  // Mark this block as free and remove it from the allocated set.
-  const MemoryBlock& block = (*it).second;
-  AddFreeBlock(block.address, block.size);
-
-  SB_DCHECK(block.size <= total_allocated_);
-  total_allocated_ -= block.size;
-
-  allocated_blocks_.erase(it);
+  bool result = TryFree(memory);
+  SB_DCHECK(result);
 }
 
 void ReuseAllocator::PrintAllocations() const {
@@ -218,7 +275,7 @@
   SizesHistogram sizes_histogram;
   for (AllocatedBlockMap::const_iterator iter = allocated_blocks_.begin();
        iter != allocated_blocks_.end(); ++iter) {
-    std::size_t block_size = iter->second.size;
+    std::size_t block_size = iter->second.size();
     if (sizes_histogram.find(block_size) == sizes_histogram.end()) {
       sizes_histogram[block_size] = 0;
     }
@@ -232,4 +289,25 @@
   SB_LOG(INFO) << "Total allocations: " << allocated_blocks_.size();
 }
 
+bool ReuseAllocator::TryFree(void* memory) {
+  if (!memory) {
+    return true;
+  }
+
+  AllocatedBlockMap::iterator it = allocated_blocks_.find(memory);
+  if (it == allocated_blocks_.end()) {
+    return false;
+  }
+
+  // Mark this block as free and remove it from the allocated set.
+  const MemoryBlock& block = (*it).second;
+  AddFreeBlock(block);
+
+  SB_DCHECK(block.size() <= total_allocated_);
+  total_allocated_ -= block.size();
+
+  allocated_blocks_.erase(it);
+  return true;
+}
+
 }  // namespace nb
diff --git a/src/nb/reuse_allocator.h b/src/nb/reuse_allocator.h
index 6f4f16a..71d43d8 100644
--- a/src/nb/reuse_allocator.h
+++ b/src/nb/reuse_allocator.h
@@ -30,16 +30,32 @@
 // maintaining all allocation meta data is outside of the allocated memory.
 // It is passed a fallback allocator that it can request additional memory
 // from as needed.
+// The default allocation strategy for the allocator is first-fit, i.e. it will
+// scan for free blocks sorted by addresses and allocate from the first free
+// block that can fulfill the allocation.  However, in some situations the
+// majority of the allocations can be small ones with some large allocations.
+// This may cause serious fragmentations and the failure of large allocations.
+// If |small_allocation_threshold| in the ctor is set to a non-zero value, the
+// class will allocate small allocations whose sizes are less than or equal to
+// the threshold using last-fit, i.e. it will scan from the back to the front
+// for free blocks.  This way the allocation for large blocks and small blocks
+// are separated thus cause much less fragmentations.
 class ReuseAllocator : public Allocator {
  public:
   explicit ReuseAllocator(Allocator* fallback_allocator);
+  // When |small_allocation_threshold| is non-zero, this class will allocate
+  // its full capacity from the |fallback_allocator| in the ctor so it is
+  // possible for the class to use the last-fit allocation strategy.  See the
+  // class comment above for more details.
+  ReuseAllocator(Allocator* fallback_allocator,
+                 std::size_t capacity,
+                 std::size_t small_allocation_threshold);
   virtual ~ReuseAllocator();
 
   // Search free memory blocks for an existing one, and if none are large
   // enough, allocate a new one from no-free memory and return that.
   void* Allocate(std::size_t size);
   void* Allocate(std::size_t size, std::size_t alignment);
-  void* AllocateForAlignment(std::size_t size, std::size_t alignment);
 
   // Marks the memory block as being free and it will then become recyclable
   void Free(void* memory);
@@ -49,30 +65,70 @@
 
   void PrintAllocations() const;
 
- private:
-  // We will allocate from the given allocator whenever we can't find
-  // pre-used memory to allocate.
-  Allocator* fallback_allocator_;
+  bool TryFree(void* memory);
 
-  struct MemoryBlock {
-    void* address;
-    std::size_t size;
+ private:
+  class MemoryBlock {
+   public:
+    MemoryBlock() : address_(0), size_(0) {}
+    MemoryBlock(void* address, std::size_t size)
+        : address_(address), size_(size) {}
+
+    void* address() const { return address_; }
+    std::size_t size() const { return size_; }
+
+    void set_address(void* address) { address_ = address; }
+    void set_size(std::size_t size) { size_ = size; }
+
     bool operator<(const MemoryBlock& other) const {
-      return address < other.address;
+      return address_ < other.address_;
     }
+    // If the current block and |other| can be combined into a continuous memory
+    // block, store the conmbined block in the current block and return true.
+    // Otherwise return false.
+    bool Merge(const MemoryBlock& other);
+    // Return true if the current block can be used to fulfill an allocation
+    // with the given size and alignment.
+    bool CanFullfill(std::size_t request_size, std::size_t alignment) const;
+    // Allocate a block from this block with the given size and alignment.
+    // Store the allocated block in |allocated|.  If the rest space is large
+    // enough to form a block, it will be stored into |free|.  Otherwise the
+    // whole block is stored into |allocated|.
+    // Note that the call of this function has to ensure that CanFulfill() is
+    // already called on this block and returns true.
+    void Allocate(std::size_t request_size,
+                  std::size_t alignment,
+                  bool allocate_from_front,
+                  MemoryBlock* allocated,
+                  MemoryBlock* free) const;
+
+   private:
+    void* address_;
+    std::size_t size_;
   };
+
   // Freelist sorted by address.
   typedef std::set<MemoryBlock> FreeBlockSet;
   // Map from pointers we returned to the user, back to memory blocks.
   typedef std::map<void*, MemoryBlock> AllocatedBlockMap;
-  void AddFreeBlock(void* address, std::size_t size);
-  void RemoveFreeBlock(FreeBlockSet::iterator);
+
+  FreeBlockSet::iterator AddFreeBlock(MemoryBlock block_to_add);
+  void RemoveFreeBlock(FreeBlockSet::iterator it);
 
   FreeBlockSet free_blocks_;
   AllocatedBlockMap allocated_blocks_;
 
-  // A list of allocations made from the fallback allocator.  We keep track
-  // of this so that we can free them all upon our destruction.
+  // We will allocate from the given allocator whenever we can't find pre-used
+  // memory to allocate.
+  Allocator* fallback_allocator_;
+
+  // Any allocations with size less than or equal to the following threshold
+  // will be allocated from the back of the pool.  See the comment of the class
+  // for more details.
+  std::size_t small_allocation_threshold_;
+
+  // A list of allocations made from the fallback allocator.  We keep track of
+  // this so that we can free them all upon our destruction.
   std::vector<void*> fallback_allocations_;
 
   // How much we have allocated from the fallback allocator.
diff --git a/src/nb/reuse_allocator_benchmark.cc b/src/nb/reuse_allocator_benchmark.cc
index 4117d5c..ac53c08 100644
--- a/src/nb/reuse_allocator_benchmark.cc
+++ b/src/nb/reuse_allocator_benchmark.cc
@@ -177,17 +177,14 @@
 
 class DefaultAllocator : public nb::Allocator {
  public:
-  void* Allocate(std::size_t size) { return Allocate(size, 0); }
-  void* Allocate(std::size_t size, std::size_t alignment) {
+  void* Allocate(std::size_t size) SB_OVERRIDE { return Allocate(size, 0); }
+  void* Allocate(std::size_t size, std::size_t alignment) SB_OVERRIDE {
     return SbMemoryAllocateAligned(alignment, size);
   }
-  void* AllocateForAlignment(std::size_t size, std::size_t alignment) {
-    return SbMemoryAllocateAligned(alignment, size);
-  }
-  void Free(void* memory) { SbMemoryFree(memory); }
-  std::size_t GetCapacity() const { return 0; }
-  std::size_t GetAllocated() const { return 0; }
-  void PrintAllocations() const {}
+  void Free(void* memory) SB_OVERRIDE { SbMemoryDeallocate(memory); }
+  std::size_t GetCapacity() const SB_OVERRIDE { return 0; }
+  std::size_t GetAllocated() const SB_OVERRIDE { return 0; }
+  void PrintAllocations() const SB_OVERRIDE {}
 };
 
 void MemoryPlaybackTest(const std::string& filename) {
@@ -213,7 +210,7 @@
   SB_DLOG(INFO) << "Allocator high water mark: " << allocator_high_water_mark;
   SB_DLOG(INFO) << "Elapsed (ms): " << elapsed_time / kSbTimeMillisecond;
 
-  SbMemoryFree(fixed_no_free_memory);
+  SbMemoryDeallocate(fixed_no_free_memory);
 
   // Test again using the system allocator, to compare performance.
   DefaultAllocator default_allocator;
diff --git a/src/nb/reuse_allocator_test.cc b/src/nb/reuse_allocator_test.cc
index 658c81a..4d42361 100644
--- a/src/nb/reuse_allocator_test.cc
+++ b/src/nb/reuse_allocator_test.cc
@@ -18,6 +18,7 @@
 
 #include "nb/fixed_no_free_allocator.h"
 #include "nb/scoped_ptr.h"
+#include "starboard/configuration.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -32,14 +33,21 @@
  public:
   static const int kBufferSize = 1 * 1024 * 1024;
 
-  ReuseAllocatorTest() {
+  ReuseAllocatorTest() { ResetAllocator(0); }
+
+ protected:
+  void ResetAllocator(size_t small_allocation_threshold) {
     buffer_.reset(new uint8_t[kBufferSize]);
     fallback_allocator_.reset(
         new nb::FixedNoFreeAllocator(buffer_.get(), kBufferSize));
-    allocator_.reset(new nb::ReuseAllocator(fallback_allocator_.get()));
+    if (small_allocation_threshold == 0) {
+      allocator_.reset(new nb::ReuseAllocator(fallback_allocator_.get()));
+    } else {
+      allocator_.reset(new nb::ReuseAllocator(
+          fallback_allocator_.get(), kBufferSize, small_allocation_threshold));
+    }
   }
 
- protected:
   nb::scoped_array<uint8_t> buffer_;
   nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
   nb::scoped_ptr<nb::ReuseAllocator> allocator_;
@@ -99,3 +107,32 @@
   allocator_->Free(test_p);
   allocator_->Free(blocks[0]);
 }
+
+TEST_F(ReuseAllocatorTest, SmallAlloc) {
+  // Recreate allocator with small allocation threshold to 256.
+  ResetAllocator(256);
+
+  const std::size_t kBlockSizes[] = {117, 193, 509, 1111};
+  const std::size_t kAlignment = 16;
+  void* blocks[] = {NULL, NULL, NULL, NULL};
+  for (int i = 0; i < SB_ARRAY_SIZE(kBlockSizes); ++i) {
+    blocks[i] = allocator_->Allocate(kBlockSizes[i], kAlignment);
+  }
+  // The two small allocs should be in the back in reverse order.
+  EXPECT_GT(reinterpret_cast<uintptr_t>(blocks[0]),
+            reinterpret_cast<uintptr_t>(blocks[1]));
+  // Small allocs should has higher address than other allocs.
+  EXPECT_GT(reinterpret_cast<uintptr_t>(blocks[1]),
+            reinterpret_cast<uintptr_t>(blocks[3]));
+  // Non-small allocs are allocated from the front and the first one has the
+  // lowest address.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[2]),
+            reinterpret_cast<uintptr_t>(blocks[3]));
+  for (int i = 0; i < SB_ARRAY_SIZE(kBlockSizes); ++i) {
+    allocator_->Free(blocks[i]);
+  }
+  // Should have one single free block equals to the capacity.
+  void* test_p = allocator_->Allocate(allocator_->GetCapacity());
+  EXPECT_TRUE(test_p != NULL);
+  allocator_->Free(test_p);
+}
diff --git a/src/nb/simple_thread.cc b/src/nb/simple_thread.cc
new file mode 100644
index 0000000..7bfea39
--- /dev/null
+++ b/src/nb/simple_thread.cc
@@ -0,0 +1,62 @@
+/*

+ * 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/simple_thread.h"

+

+#include "starboard/mutex.h"

+#include "starboard/thread.h"

+#include "starboard/types.h"

+#include "starboard/log.h"

+

+namespace nb {

+

+SimpleThread::SimpleThread(const std::string& name)

+    : thread_(kSbThreadInvalid), name_(name),

+      join_called_(false) {}

+

+SimpleThread::~SimpleThread() {

+  SB_DCHECK(join_called_.load()) << "Join not called on thread.";

+}

+

+void SimpleThread::Start() {

+  SbThreadEntryPoint entry_point = ThreadEntryPoint;

+

+  thread_ = SbThreadCreate(0,                    // default stack_size.

+                           kSbThreadNoPriority,  // default priority.

+                           kSbThreadNoAffinity,  // default affinity.

+                           true,                 // joinable.

+                           name_.c_str(), entry_point, this);

+

+  // SbThreadCreate() above produced an invalid thread handle.

+  SB_DCHECK(thread_ != kSbThreadInvalid);

+  return;

+}

+

+void* SimpleThread::ThreadEntryPoint(void* context) {

+  SimpleThread* this_ptr = static_cast<SimpleThread*>(context);

+  this_ptr->Run();

+  return NULL;

+}

+

+void SimpleThread::Join() {

+  SB_DCHECK(join_called_.load() == false);

+  if (!SbThreadJoin(thread_, NULL)) {

+    SB_DCHECK(false) << "Could not join thread.";

+  }

+  join_called_.store(true);

+}

+

+}  // namespace nb

diff --git a/src/nb/simple_thread.h b/src/nb/simple_thread.h
new file mode 100644
index 0000000..ce3bbfe
--- /dev/null
+++ b/src/nb/simple_thread.h
@@ -0,0 +1,57 @@
+/*

+ * 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_THREAD_H_

+#define NB_THREAD_H_

+

+#include <string>

+

+#include "nb/atomic.h"

+#include "starboard/thread.h"

+#include "starboard/types.h"

+

+namespace nb {

+

+class SimpleThread {

+ public:

+  explicit SimpleThread(const std::string& name);

+  virtual ~SimpleThread();

+

+  // Subclasses should override the Run method.

+  virtual void Run() = 0;

+

+  // Signals to the thread to break out of it's loop.

+  virtual void Cancel() {}

+

+  // Called by the main thread, this will cause Run() to be invoked

+  // on another thread.

+  void Start();

+  // Destroys the threads resources.

+  void Join();

+

+ private:

+  static void* ThreadEntryPoint(void* context);

+

+  const std::string name_;

+  SbThread thread_;

+  atomic_bool join_called_;

+

+  SB_DISALLOW_COPY_AND_ASSIGN(SimpleThread);

+};

+

+}  // namespace nb

+

+#endif  // NB_THREAD_H_

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
new file mode 100644
index 0000000..a303f5a
--- /dev/null
+++ b/src/nb/thread_local_object.h
@@ -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.
+ */
+
+#ifndef NB_THREAD_LOCAL_OBJECT_H_
+#define NB_THREAD_LOCAL_OBJECT_H_
+
+#include <set>
+
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+
+namespace nb {
+
+// Like base::ThreadLocalPointer<T> but destroys objects. This is important
+// for using ThreadLocalObjects that aren't tied to a singleton, or for
+// access by threads which will call join().
+//
+// FEATURE COMPARISON TABLE:
+//                         |  Thread Join       | Container Destroyed
+//   ------------------------------------------------------------------
+//   ThreadLocalPointer<T> | LEAKS              | LEAKS
+//   ThreadLocalObject<T>  | Object Destroyed   | Objects Destroyed
+//
+// EXAMPLE:
+//  ThreadLocalObject<std::map<std::string, int> > map_tls;
+//  Map* map = map_tls->GetOrCreate();
+//  (*map)["my string"] = 15;
+//  Thread t = new Thread(&map_tls);
+//  t->start();
+//  // t creates it's own thread local map.
+//  t->join();  // Make sure that thread joins before map_tls is destroyed!
+//
+// OBJECT DESTRUCTION:
+//   There are two ways for an object to be destroyed by the ThreadLocalObject.
+//   The first way is via a thread join. In this case only the object
+//   associated with the thread is deleted.
+//   The second way an object is destroyed is by the ThreadLocalObject
+//   container to be destroyed, in this case ALL thread local objects are
+//   destroyed.
+//
+// PERFORMANCE:
+//   ThreadLocalObject is fast for the Get() function if the object has
+//   has already been created, requiring one extra pointer dereference
+//   over ThreadLocalPointer<T>.
+template <typename Type>
+class ThreadLocalObject {
+ public:
+  ThreadLocalObject() : slot_() {
+    slot_ = SbThreadCreateLocalKey(DeleteEntry);
+    SB_DCHECK(kSbThreadLocalKeyInvalid != slot_);
+    constructing_thread_id_ = SbThreadGetId();
+  }
+
+  // Enables destruction by any other thread. Otherwise, the class instance
+  // will warn when a different thread than the constructing destroys this.
+  void EnableDestructionByAnyThread() {
+    constructing_thread_id_ = kSbThreadInvalidId;
+  }
+
+  // Thread Local Objects are destroyed after this call.
+  ~ThreadLocalObject() {
+    CheckCurrentThreadAllowedToDestruct();
+    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.
+    typedef typename Set::iterator Iter;
+    for (Iter it = entry_set_.begin(); it != entry_set_.end(); ++it) {
+      Entry* entry = *it;
+      SB_DCHECK(entry->owner_ == this);
+      delete entry->ptr_;
+      delete entry;
+    }
+
+    // Cleanup the thread local key.
+    SbThreadDestroyLocalKey(slot_);
+  }
+
+  // Warns if there is a misuse of this object.
+  void CheckCurrentThreadAllowedToDestruct() const {
+    if (kSbThreadInvalidId == constructing_thread_id_) {
+      return;   // EnableDestructionByAnyThread() called.
+    }
+    const SbThreadId curr_thread_id = SbThreadGetId();
+    if (curr_thread_id == constructing_thread_id_) {
+      return;   // Same thread that constructed this.
+    }
+
+    if (SB_DLOG_IS_ON(FATAL)) {
+      SB_DCHECK(false)
+          << "ThreadLocalObject<T> was created in thread "
+          << constructing_thread_id_ << "\nbut was destroyed by "
+          << curr_thread_id << ". If this is intentional then call "
+          << "EnableDestructionByAnyThread() to silence this "
+          << "warning.";
+    }
+  }
+
+  // Either returns the created pointer for the current thread, or otherwise
+  // constructs the object using the default constructor and returns it.
+  Type* GetOrCreate() {
+    Type* object = GetIfExists();
+    if (!object) {  // create object.
+      object = new Type();
+      Entry* entry = new Entry(this, object);
+      // Insert into the set of tls entries.
+      // Performance: Its assumed that creation of objects is much less
+      // frequent than getting an object.
+      {
+        starboard::ScopedLock lock(entry_set_mutex_);
+        entry_set_.insert(entry);
+      }
+      SbThreadSetLocalValue(slot_, entry);
+    }
+    return object;
+  }
+
+  // Returns the pointer if it exists in the current thread, otherwise NULL.
+  Type* GetIfExists() const {
+    Entry* entry = GetEntryIfExists();
+    if (!entry) { return NULL; }
+    return entry->ptr_;
+  }
+
+  // Releases ownership of the pointer FROM THE CURRENT THREAD.
+  // The caller has responsibility to make sure that the pointer is destroyed.
+  Type* Release() {
+    if (Entry* entry = GetEntryIfExists()) {
+      // The entry will no longer run it's destructor on thread join.
+      SbThreadSetLocalValue(slot_, NULL);  // NULL out pointer for TLS.
+      Type* object = entry->ptr_;
+      RemoveEntry(entry);
+      return object;
+    } else {
+      return NULL;
+    }
+  }
+
+ private:
+  struct Entry {
+    Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) {
+    }
+    ~Entry() {
+      ptr_ = NULL;
+      owner_ = NULL;
+    }
+    ThreadLocalObject* owner_;
+    Type* ptr_;
+  };
+
+  // Deletes the TLSEntry.
+  static void DeleteEntry(void* ptr) {
+    if (!ptr) {
+      SB_NOTREACHED();
+      return;
+    }
+    Entry* entry = reinterpret_cast<Entry*>(ptr);
+    ThreadLocalObject* tls = entry->owner_;
+    Type* object = entry->ptr_;
+    tls->RemoveEntry(entry);
+    delete object;
+  }
+
+  void RemoveEntry(Entry* entry) {
+    {
+      starboard::ScopedLock lock(entry_set_mutex_);
+      entry_set_.erase(entry);
+    }
+    delete entry;
+  }
+
+  Entry* GetEntryIfExists() const {
+    void* ptr = SbThreadGetLocalValue(slot_);
+    Entry* entry = static_cast<Entry*>(ptr);
+    return entry;
+  }
+
+  typedef std::set<Entry*> Set;
+  // Allows GetIfExists() to be const.
+  mutable SbThreadLocalKey slot_;
+  // entry_set_ contains all the outstanding entries for the thread local
+  // objects that have been created.
+  Set entry_set_;
+  mutable starboard::Mutex entry_set_mutex_;
+  // Used to warn when there is a mismatch between thread that constructed and
+  // thread that destroyed this object.
+  SbThreadId constructing_thread_id_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>);
+};
+
+}  // namespace nb
+
+#endif  // NB_THREAD_LOCAL_OBJECT_H_
diff --git a/src/nb/thread_local_object_test.cc b/src/nb/thread_local_object_test.cc
new file mode 100644
index 0000000..9fce6d6
--- /dev/null
+++ b/src/nb/thread_local_object_test.cc
@@ -0,0 +1,204 @@
+// 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 <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 nb {
+namespace {
+
+// Simple class that counts the number of instances alive.
+struct CountsInstances {
+  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); }
+};
+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 TestThread {
+ public:
+  explicit CreateThreadLocalObjectThenExit(
+      ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {}
+
+  virtual void Run() {
+    // volatile as a defensive measure to prevent compiler from optimizing this
+    // statement out.
+    volatile TYPE* val = tlo_->GetOrCreate();
+  }
+  ThreadLocalObject<TYPE>* tlo_;
+};
+
+// A simple thread that just deletes the object supplied on a thread and then
+// exists.
+template <typename TYPE>
+class DestroyTypeOnThread : public TestThread {
+ public:
+  explicit DestroyTypeOnThread(TYPE* ptr)
+      : ptr_(ptr) {}
+  virtual void Run() {
+    ptr_.reset(NULL);  // Destroys the object.
+  }
+ private:
+  nb::scoped_ptr<TYPE> ptr_;
+};
+
+// Tests the expectation that a ThreadLocalObject can be simply used by
+// the main thread.
+TEST(ThreadLocalObject, MainThread) {
+  ThreadLocalObject<bool> tlo_bool;
+  EXPECT_TRUE(NULL == tlo_bool.GetIfExists());
+  bool* the_bool = tlo_bool.GetOrCreate();
+  EXPECT_TRUE(the_bool != NULL);
+  EXPECT_FALSE(*the_bool);
+  *the_bool = true;
+  EXPECT_TRUE(*(tlo_bool.GetIfExists()));
+}
+
+// Tests the expectation that a ThreadLocalObject can be used on
+// complex objects type (i.e. non pod types).
+TEST(ThreadLocalObject, MainThreadComplexObject) {
+  typedef std::map<std::string, int> Map;
+  ThreadLocalObject<Map> map_tlo;
+  EXPECT_FALSE(map_tlo.GetIfExists());
+  ASSERT_TRUE(map_tlo.GetOrCreate());
+  Map* map = map_tlo.GetIfExists();
+  const Map* const_map = map_tlo.GetIfExists();
+  ASSERT_TRUE(map);
+  ASSERT_TRUE(const_map);
+  // If the object is properly constructed then this find operation
+  // should succeed.
+  (*map)["my string"] = 15;
+  ASSERT_EQ(15, (*map)["my string"]);
+}
+
+// Tests that when a ThreadLocalObject is destroyed on the main thread that
+// the pointers it contained are also destroyed.
+TEST(ThreadLocalObject, DestroysObjectOnTLODestruction) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  // Create the TLO object and then immediately destroy it.
+  nb::scoped_ptr<TLO> tlo_ptr(new TLO);
+  tlo_ptr->GetOrCreate(); // Instantiate the internal object.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+  tlo_ptr.reset(NULL);    // Should destroy all outstanding allocs.
+  // Now the TLO is destroyed and therefore the destructor should run on the
+  // internal object.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that the object can be released and that the pointer
+// won't be deleted when the ThreadLocalObject that created it is destroyed.
+TEST(ThreadLocalObject, ReleasesObject) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  nb::scoped_ptr<TLO> tlo_ptr(new TLO);
+  // Instantiate the internal object.
+  tlo_ptr->GetOrCreate();
+  // Now release the pointer into the container.
+  nb::scoped_ptr<CountsInstances> last_ref(tlo_ptr->Release());
+  // Destroying the TLO should not trigger the destruction of the object,
+  // because it was released.
+  tlo_ptr.reset(NULL);
+  // 1 instance left, which is held in last_ref.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+  last_ref.reset(NULL);   // Now the object should be destroyed and the
+                          // instance count drops to 0.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that a thread that creates an object from
+// the ThreadLocalObject store will automatically be destroyed by the
+// thread joining.
+TEST(ThreadLocalObject, ThreadJoinDestroysObject) {
+  CountsInstances::ResetNumInstances();
+  typedef ThreadLocalObject<CountsInstances> TLO;
+
+  nb::scoped_ptr<TLO> tlo(new TLO);
+  {
+    TestThread* thread =
+        new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get());
+    thread->Start();
+    thread->Join();
+    // Once the thread joins, the object should be deleted and the instance
+    // counter falls to 0.
+    EXPECT_EQ(0, CountsInstances::NumInstances());
+    delete thread;
+  }
+
+  tlo.reset(NULL);  // Now TLO destructor runs.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+// Tests the expectation that objects created on the main thread are not
+// leaked.
+TEST(ThreadLocalObject, NoLeaksOnMainThread) {
+  CountsInstances::ResetNumInstances();
+
+  ThreadLocalObject<CountsInstances>* tlo =
+      new ThreadLocalObject<CountsInstances>;
+  tlo->EnableDestructionByAnyThread();
+
+  // Creates the object on the main thread. This is important because the
+  // main thread will never join and therefore at-exit functions won't get
+  // run.
+  CountsInstances* main_thread_object = tlo->GetOrCreate();
+
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+
+  // Thread will simply create the thread local object (CountsInstances)
+  // and then return.
+  nb::scoped_ptr<TestThread> thread_ptr(
+        new CreateThreadLocalObjectThenExit<CountsInstances>(tlo));
+  thread_ptr->Start();  // Object is now created.
+  thread_ptr->Join();   // ...then destroyed.
+  thread_ptr.reset(NULL);
+
+  // Only main_thread_object should be alive now, therefore the count is 1.
+  EXPECT_EQ(1, CountsInstances::NumInstances());
+
+  // We COULD destroy the TLO on the main thread, but to be even fancier lets
+  // create a thread that will destroy the object on a back ground thread.
+  // The end result is that the TLO entry should be cleared out.
+  thread_ptr.reset(
+      new DestroyTypeOnThread<ThreadLocalObject<CountsInstances> >(tlo));
+  thread_ptr->Start();
+  thread_ptr->Join();
+  thread_ptr.reset(NULL);
+
+  // Now we expect that number of instances to be 0.
+  EXPECT_EQ(0, CountsInstances::NumInstances());
+  CountsInstances::ResetNumInstances();
+}
+
+}  // anonymous namespace
+}  // nb namespace
diff --git a/src/net/base/data_url.cc b/src/net/base/data_url.cc
index b7309cb..53a5ea8 100644
--- a/src/net/base/data_url.cc
+++ b/src/net/base/data_url.cc
@@ -13,6 +13,7 @@
 #include "base/string_split.h"
 #include "base/string_util.h"
 #include "googleurl/src/gurl.h"
+#include "nb/memory_scope.h"
 #include "net/base/escape.h"
 
 namespace net {
@@ -20,6 +21,7 @@
 // static
 bool DataURL::Parse(const GURL& url, std::string* mime_type,
                     std::string* charset, std::string* data) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(mime_type->empty());
   DCHECK(charset->empty());
   std::string::const_iterator begin = url.spec().begin();
diff --git a/src/net/base/file_stream_unittest.cc b/src/net/base/file_stream_unittest.cc
index 04b613d..2cdb252 100644
--- a/src/net/base/file_stream_unittest.cc
+++ b/src/net/base/file_stream_unittest.cc
@@ -437,6 +437,8 @@
     drainable->DidConsume(rv);
     total_bytes_written += rv;
   }
+  rv = stream.FlushSync();
+  EXPECT_EQ(OK, rv);
   ok = file_util::GetFileSize(temp_file_path(), &file_size);
   EXPECT_TRUE(ok);
   EXPECT_EQ(file_size, total_bytes_written);
@@ -533,6 +535,8 @@
     drainable->DidConsume(rv);
     total_bytes_written += rv;
   }
+  rv = stream.FlushSync();
+  EXPECT_EQ(OK, rv);
   ok = file_util::GetFileSize(temp_file_path(), &file_size);
   EXPECT_TRUE(ok);
   EXPECT_EQ(file_size, kTestDataSize * 2);
@@ -881,6 +885,8 @@
   EXPECT_LT(0, rv);
   EXPECT_EQ(kTestDataSize, total_bytes_written);
 
+  rv = stream->FlushSync();
+  EXPECT_EQ(OK, rv);
   stream.reset();
 
   ok = file_util::GetFileSize(temp_file_path(), &file_size);
diff --git a/src/net/base/host_resolver_impl.cc b/src/net/base/host_resolver_impl.cc
index 11ccd9f..9c1ac57 100644
--- a/src/net/base/host_resolver_impl.cc
+++ b/src/net/base/host_resolver_impl.cc
@@ -44,6 +44,7 @@
 #include "net/dns/dns_protocol.h"
 #include "net/dns/dns_response.h"
 #include "net/dns/dns_transaction.h"
+#include "nb/memory_scope.h"
 
 #if defined(OS_WIN)
 #include "net/base/winsock_init.h"
@@ -580,6 +581,7 @@
   ~ProcTask() {}
 
   void StartLookupAttempt() {
+    TRACK_MEMORY_SCOPE("Network");
     DCHECK(origin_loop_->BelongsToCurrentThread());
     base::TimeTicks start_time = base::TimeTicks::Now();
     ++attempt_number_;
diff --git a/src/net/base/host_resolver_proc.cc b/src/net/base/host_resolver_proc.cc
index 8950f5b..a4512e9 100644
--- a/src/net/base/host_resolver_proc.cc
+++ b/src/net/base/host_resolver_proc.cc
@@ -12,6 +12,7 @@
 #include "net/base/dns_reloader.h"
 #include "net/base/net_errors.h"
 #include "net/base/sys_addrinfo.h"
+#include "nb/memory_scope.h"
 
 #if defined(OS_STARBOARD)
 #include "starboard/socket.h"
@@ -133,6 +134,7 @@
                            HostResolverFlags host_resolver_flags,
                            AddressList* addrlist,
                            int* os_error) {
+  TRACK_MEMORY_SCOPE("Network");
   if (os_error)
     *os_error = 0;
 
diff --git a/src/net/base/net_errors.h b/src/net/base/net_errors.h
index 95a81e9..43cffab 100644
--- a/src/net/base/net_errors.h
+++ b/src/net/base/net_errors.h
@@ -49,7 +49,7 @@
 NET_EXPORT Error MapSocketError(SbSocketError error);
 
 // Gets the last socket error as a net error.
-SB_C_INLINE Error MapLastSocketError(SbSocket socket) {
+static SB_C_INLINE Error MapLastSocketError(SbSocket socket) {
   return MapSocketError(SbSocketGetLastError(socket));
 }
 
@@ -57,7 +57,7 @@
 NET_EXPORT Error MapSystemError(SbSystemError error);
 
 // Gets the last system error as a net error.
-SB_C_INLINE Error MapLastSystemError() {
+static SB_C_INLINE Error MapLastSystemError() {
   return MapSystemError(SbSystemGetLastError());
 }
 
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_auth_handler_ntlm.cc b/src/net/http/http_auth_handler_ntlm.cc
index ceca94a..aa0142a 100644
--- a/src/net/http/http_auth_handler_ntlm.cc
+++ b/src/net/http/http_auth_handler_ntlm.cc
@@ -15,7 +15,7 @@
 
 #if defined(OS_STARBOARD)
 #include "starboard/memory.h"
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #endif
 
 namespace net {
diff --git a/src/net/http/http_auth_handler_ntlm_portable.cc b/src/net/http/http_auth_handler_ntlm_portable.cc
index dbcfd94..63cdcee 100644
--- a/src/net/http/http_auth_handler_ntlm_portable.cc
+++ b/src/net/http/http_auth_handler_ntlm_portable.cc
@@ -7,7 +7,7 @@
 #if defined(OS_STARBOARD)
 #include "starboard/memory.h"
 #define malloc SbMemoryAllocate
-#define free SbMemoryFree
+#define free SbMemoryDeallocate
 #else
 #include <stdlib.h>
 #endif
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/http/http_stream_factory_impl_job.cc b/src/net/http/http_stream_factory_impl_job.cc
index 77422ce..9554ce6 100644
--- a/src/net/http/http_stream_factory_impl_job.cc
+++ b/src/net/http/http_stream_factory_impl_job.cc
@@ -12,6 +12,7 @@
 #include "base/stringprintf.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "nb/memory_scope.h"
 #include "net/base/connection_type_histograms.h"
 #include "net/base/net_log.h"
 #include "net/base/net_util.h"
@@ -267,6 +268,7 @@
 }
 
 void HttpStreamFactoryImpl::Job::OnStreamReadyCallback() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(stream_.get());
   DCHECK(!IsPreconnecting());
   if (IsOrphaned()) {
diff --git a/src/net/net.gyp b/src/net/net.gyp
index 3d7b9d4..d13a159 100644
--- a/src/net/net.gyp
+++ b/src/net/net.gyp
@@ -57,6 +57,7 @@
         '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
         '../googleurl/googleurl.gyp:googleurl',
         '../crypto/crypto.gyp:crypto',
+        '../nb/nb.gyp:nb',
         '../third_party/icu/icu.gyp:icui18n',
         '../third_party/icu/icu.gyp:icuuc',
         '../third_party/zlib/zlib.gyp:zlib',
diff --git a/src/net/quic/crypto/crypto_framer_test.cc b/src/net/quic/crypto/crypto_framer_test.cc
index eed175f..b959541 100644
--- a/src/net/quic/crypto/crypto_framer_test.cc
+++ b/src/net/quic/crypto/crypto_framer_test.cc
@@ -10,6 +10,7 @@
 #include "net/quic/crypto/crypto_framer.h"
 #include "net/quic/crypto/crypto_protocol.h"
 #include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/disabled_if_big_endian.h"
 
 using base::StringPiece;
 using std::map;
@@ -62,7 +63,7 @@
 
 }  // namespace test
 
-TEST(CryptoFramerTest, ConstructHandshakeMessage) {
+TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructHandshakeMessage)) {
   CryptoHandshakeMessage message;
   message.tag = 0xFFAA7733;
   message.tag_value_map[0x12345678] = "abcdef";
@@ -107,7 +108,8 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST(CryptoFramerTest, ConstructHandshakeMessageWithTwoKeys) {
+TEST(CryptoFramerTest,
+     DISABLED_IF_BIG_ENDIAN(ConstructHandshakeMessageWithTwoKeys)) {
   CryptoHandshakeMessage message;
   message.tag = 0xFFAA7733;
   message.tag_value_map[0x12345678] = "abcdef";
@@ -166,7 +168,7 @@
   EXPECT_TRUE(data.get() == NULL);
 }
 
-TEST(CryptoFramerTest, ProcessInput) {
+TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInput)) {
   test::TestCryptoVisitor visitor;
   CryptoFramer framer;
   framer.set_visitor(&visitor);
@@ -201,7 +203,7 @@
   EXPECT_EQ("ghijk", visitor.message_maps_[0][0x12345679]);
 }
 
-TEST(CryptoFramerTest, ProcessInputIncrementally) {
+TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputIncrementally)) {
   test::TestCryptoVisitor visitor;
   CryptoFramer framer;
   framer.set_visitor(&visitor);
@@ -237,7 +239,7 @@
   EXPECT_EQ("ghijk", visitor.message_maps_[0][0x12345679]);
 }
 
-TEST(CryptoFramerTest, ProcessInputTagsOutOfOrder) {
+TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputTagsOutOfOrder)) {
   test::TestCryptoVisitor visitor;
   CryptoFramer framer;
   framer.set_visitor(&visitor);
@@ -275,7 +277,7 @@
   EXPECT_EQ(QUIC_CRYPTO_TOO_MANY_ENTRIES, framer.error());
 }
 
-TEST(CryptoFramerTest, ProcessInputInvalidLength) {
+TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputInvalidLength)) {
   test::TestCryptoVisitor visitor;
   CryptoFramer framer;
   framer.set_visitor(&visitor);
diff --git a/src/net/quic/crypto/null_decrypter_test.cc b/src/net/quic/crypto/null_decrypter_test.cc
index ad73cea..f76de31 100644
--- a/src/net/quic/crypto/null_decrypter_test.cc
+++ b/src/net/quic/crypto/null_decrypter_test.cc
@@ -4,13 +4,14 @@
 
 #include "net/quic/crypto/null_decrypter.h"
 #include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/disabled_if_big_endian.h"
 
 using base::StringPiece;
 
 namespace net {
 namespace test {
 
-TEST(NullDecrypterTest, Decrypt) {
+TEST(NullDecrypterTest, DISABLED_IF_BIG_ENDIAN(Decrypt)) {
   unsigned char expected[] = {
     // fnv hash
     0x47, 0x11, 0xea, 0x5f,
diff --git a/src/net/quic/crypto/null_encrypter_test.cc b/src/net/quic/crypto/null_encrypter_test.cc
index 9f2cec2..9f9f2c4 100644
--- a/src/net/quic/crypto/null_encrypter_test.cc
+++ b/src/net/quic/crypto/null_encrypter_test.cc
@@ -4,13 +4,14 @@
 
 #include "net/quic/crypto/null_encrypter.h"
 #include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/disabled_if_big_endian.h"
 
 using base::StringPiece;
 
 namespace net {
 namespace test {
 
-TEST(NullEncrypterTest, Encrypt) {
+TEST(NullEncrypterTest, DISABLED_IF_BIG_ENDIAN(Encrypt)) {
   unsigned char expected[] = {
     // fnv hash
     0x47, 0x11, 0xea, 0x5f,
diff --git a/src/net/quic/quic_connection_test.cc b/src/net/quic/quic_connection_test.cc
index 6e14df7..97c9399 100644
--- a/src/net/quic/quic_connection_test.cc
+++ b/src/net/quic/quic_connection_test.cc
@@ -13,6 +13,7 @@
 #include "net/quic/test_tools/mock_clock.h"
 #include "net/quic/test_tools/quic_test_utils.h"
 #include "net/quic/quic_utils.h"
+#include "net/test/disabled_if_big_endian.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -794,7 +795,8 @@
   EXPECT_TRUE(last_feedback() == NULL);
 }
 
-TEST_F(QuicConnectionTest, WithQuicCongestionFeedbackFrame) {
+TEST_F(QuicConnectionTest,
+       DISABLED_IF_BIG_ENDIAN(WithQuicCongestionFeedbackFrame)) {
   QuicCongestionFeedbackFrame info;
   info.type = kFixRate;
   info.fix_rate.bitrate_in_bytes_per_second = 123;
diff --git a/src/net/quic/quic_framer_test.cc b/src/net/quic/quic_framer_test.cc
index 25c3f39..9ef87dc 100644
--- a/src/net/quic/quic_framer_test.cc
+++ b/src/net/quic/quic_framer_test.cc
@@ -16,6 +16,7 @@
 #include "net/quic/quic_protocol.h"
 #include "net/quic/quic_utils.h"
 #include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/disabled_if_big_endian.h"
 
 using base::hash_set;
 using base::StringPiece;
@@ -249,7 +250,7 @@
   EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
 }
 
-TEST_F(QuicFramerTest, LargePacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(LargePacket)) {
   unsigned char packet[kMaxPacketSize + 1] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -271,13 +272,14 @@
   EXPECT_FALSE(framer_.ProcessPacket(self_address_, peer_address_, encrypted));
 
   ASSERT_TRUE(visitor_.header_.get());
+
   // Make sure we've parsed the packet header, so we can send an error.
   EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210), visitor_.header_->guid);
   // Make sure the correct error is propogated.
   EXPECT_EQ(QUIC_PACKET_TOO_LARGE, framer_.error());
 }
 
-TEST_F(QuicFramerTest, PacketHeader) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(PacketHeader)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -318,7 +320,7 @@
   }
 }
 
-TEST_F(QuicFramerTest, StreamFrame) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(StreamFrame)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -435,7 +437,7 @@
   EXPECT_EQ(0u, visitor_.ack_frames_.size());
 }
 
-TEST_F(QuicFramerTest, RevivedStreamFrame) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(RevivedStreamFrame)) {
   unsigned char payload[] = {
     // frame count
     0x01,
@@ -486,7 +488,7 @@
   EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
 }
 
-TEST_F(QuicFramerTest, StreamFrameInFecGroup) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(StreamFrameInFecGroup)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -539,7 +541,7 @@
   EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
 }
 
-TEST_F(QuicFramerTest, AckFrame) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(AckFrame)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -606,7 +608,7 @@
   }
 }
 
-TEST_F(QuicFramerTest, CongestionFeedbackFrameTCP) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameTCP)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -666,7 +668,8 @@
   }
 }
 
-TEST_F(QuicFramerTest, CongestionFeedbackFrameInterArrival) {
+TEST_F(QuicFramerTest,
+       DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameInterArrival)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -775,7 +778,7 @@
   }
 }
 
-TEST_F(QuicFramerTest, CongestionFeedbackFrameFixRate) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameFixRate)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -858,7 +861,7 @@
   EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
 }
 
-TEST_F(QuicFramerTest, RstStreamFrame) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(RstStreamFrame)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -923,7 +926,7 @@
   }
 }
 
-TEST_F(QuicFramerTest, ConnectionCloseFrame) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConnectionCloseFrame)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -1008,7 +1011,7 @@
   }
 }
 
-TEST_F(QuicFramerTest, FecPacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(FecPacket)) {
   unsigned char packet[] = {
     // guid
     0x10, 0x32, 0x54, 0x76,
@@ -1047,7 +1050,7 @@
   EXPECT_EQ("abcdefghijklmnop", fec_data.redundancy);
 }
 
-TEST_F(QuicFramerTest, ConstructStreamFramePacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructStreamFramePacket)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1102,7 +1105,7 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructAckFramePacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructAckFramePacket)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1154,7 +1157,8 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketTCP) {
+TEST_F(QuicFramerTest,
+       DISABLED_IF_BIG_ENDIAN(ConstructCongestionFeedbackFramePacketTCP)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1201,7 +1205,9 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketInterArrival) {
+TEST_F(QuicFramerTest,
+       DISABLED_IF_BIG_ENDIAN(
+           ConstructCongestionFeedbackFramePacketInterArrival)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1274,7 +1280,8 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketFixRate) {
+TEST_F(QuicFramerTest,
+       DISABLED_IF_BIG_ENDIAN(ConstructCongestionFeedbackFramePacketFixRate)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1337,7 +1344,7 @@
   ASSERT_TRUE(data == NULL);
 }
 
-TEST_F(QuicFramerTest, ConstructRstFramePacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructRstFramePacket)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1393,7 +1400,7 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructCloseFramePacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructCloseFramePacket)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
@@ -1460,7 +1467,7 @@
                                       AsChars(packet), arraysize(packet));
 }
 
-TEST_F(QuicFramerTest, ConstructFecPacket) {
+TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructFecPacket)) {
   QuicPacketHeader header;
   header.guid = GG_UINT64_C(0xFEDCBA9876543210);
   header.packet_sequence_number = (GG_UINT64_C(0x123456789ABC));
diff --git a/src/net/socket/client_socket_handle.cc b/src/net/socket/client_socket_handle.cc
index 20ab672..cffb902 100644
--- a/src/net/socket/client_socket_handle.cc
+++ b/src/net/socket/client_socket_handle.cc
@@ -9,6 +9,7 @@
 #include "base/compiler_specific.h"
 #include "base/metrics/histogram.h"
 #include "base/logging.h"
+#include "nb/memory_scope.h"
 #include "net/base/net_errors.h"
 #include "net/socket/client_socket_pool.h"
 #include "net/socket/client_socket_pool_histograms.h"
@@ -104,6 +105,7 @@
 }
 
 void ClientSocketHandle::OnIOComplete(int result) {
+  TRACK_MEMORY_SCOPE("Network");
   CompletionCallback callback = user_callback_;
   user_callback_.Reset();
   HandleInitCompletion(result);
@@ -111,6 +113,7 @@
 }
 
 void ClientSocketHandle::HandleInitCompletion(int result) {
+  TRACK_MEMORY_SCOPE("Network");
   CHECK_NE(ERR_IO_PENDING, result);
   if (result != OK) {
     if (!socket_.get())
diff --git a/src/net/socket/ssl_client_socket_openssl.cc b/src/net/socket/ssl_client_socket_openssl.cc
index 1adcfb5..1505cb0 100644
--- a/src/net/socket/ssl_client_socket_openssl.cc
+++ b/src/net/socket/ssl_client_socket_openssl.cc
@@ -1092,7 +1092,7 @@
     DCHECK(recv_buffer_);
     int ret = BIO_write(transport_bio_, recv_buffer_->data(), result);
     // A write into a memory BIO should always succeed.
-    CHECK_EQ(result, ret);
+    DCHECK_EQ(result, ret);
   }
   recv_buffer_ = NULL;
   transport_recv_busy_ = false;
diff --git a/src/net/socket/tcp_client_socket_starboard.cc b/src/net/socket/tcp_client_socket_starboard.cc
index 516be9d..c970ac7 100644
--- a/src/net/socket/tcp_client_socket_starboard.cc
+++ b/src/net/socket/tcp_client_socket_starboard.cc
@@ -23,6 +23,7 @@
 #include "base/posix/eintr_wrapper.h"
 #include "base/string_util.h"
 #include "base/stringprintf.h"
+#include "nb/memory_scope.h"
 #include "net/base/address_family.h"
 #include "net/base/connection_type_histograms.h"
 #include "net/base/io_buffer.h"
@@ -134,7 +135,27 @@
   net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
 }
 
+int TCPClientSocketStarboard::AdoptSocket(SbSocket socket) {
+  DCHECK(!SbSocketIsValid(socket_));
+
+  int error = SetupSocket(socket);
+  if (error) {
+    return error;
+  }
+
+  socket_ = socket;
+
+  // This is to make GetPeerAddress() work. It's up to the caller to ensure that
+  // |address_| contains a reasonable address for this socket. (i.e. at least
+  // match IPv4 vs IPv6!).
+  current_address_index_ = 0;
+  use_history_.set_was_ever_connected();
+
+  return OK;
+}
+
 int TCPClientSocketStarboard::Connect(const CompletionCallback& callback) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(CalledOnValidThread());
 
   // If this socket is already valid, then just return OK.
@@ -167,6 +188,7 @@
 // pretty much cribbed directly from TCPClientSocketLibevent and/or
 // TCPClientSocketWin, take your pick, they're identical
 int TCPClientSocketStarboard::DoConnectLoop(int result) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);
 
   int rv = result;
@@ -192,6 +214,7 @@
 }
 
 int TCPClientSocketStarboard::DoConnect() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_GE(current_address_index_, 0);
   DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size()));
 
@@ -238,6 +261,7 @@
 }
 
 int TCPClientSocketStarboard::DoConnectComplete(int result) {
+  TRACK_MEMORY_SCOPE("Network");
   // Log the end of this attempt (and any OS error it threw).
   int error = connect_error_;
   connect_error_ = OK;
@@ -268,6 +292,7 @@
 }
 
 void TCPClientSocketStarboard::DidCompleteConnect() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
 
   connect_error_ = SbSocketIsConnected(socket_) ? OK : ERR_FAILED;
@@ -279,6 +304,7 @@
 }
 
 void TCPClientSocketStarboard::LogConnectCompletion(int net_error) {
+  TRACK_MEMORY_SCOPE("Network");
   if (net_error == OK)
     UpdateConnectionTypeHistograms(CONNECTION_ANY);
 
@@ -403,6 +429,7 @@
 int TCPClientSocketStarboard::Read(IOBuffer* buf,
                                    int buf_len,
                                    const CompletionCallback& callback) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(SbSocketIsValid(socket_));
   DCHECK(!waiting_connect());
   DCHECK(!waiting_read_);
@@ -440,6 +467,7 @@
 }
 
 void TCPClientSocketStarboard::DidCompleteRead() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(waiting_read_);
   int bytes_read =
       SbSocketReceiveFrom(socket_, read_buf_->data(), read_buf_len_, NULL);
@@ -466,6 +494,7 @@
 }
 
 void TCPClientSocketStarboard::DoReadCallback(int rv) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_NE(rv, ERR_IO_PENDING);
 
   // since Run may result in Read being called, clear read_callback_ up front.
@@ -477,6 +506,7 @@
 int TCPClientSocketStarboard::Write(IOBuffer* buf,
                                     int buf_len,
                                     const CompletionCallback& callback) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(SbSocketIsValid(socket_));
   DCHECK(!waiting_connect());
   DCHECK(!waiting_write_);
@@ -516,6 +546,7 @@
 }
 
 void TCPClientSocketStarboard::DidCompleteWrite() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(waiting_write_);
   int nwrite =
       SbSocketSendTo(socket_, write_buf_->data(), write_buf_len_, NULL);
@@ -541,6 +572,7 @@
 }
 
 void TCPClientSocketStarboard::DoWriteCallback(int rv) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_NE(rv, ERR_IO_PENDING);
 
   // since Run may result in Write being called, clear write_callback_ up front.
@@ -550,22 +582,27 @@
 }
 
 bool TCPClientSocketStarboard::SetReceiveBufferSize(int32 size) {
+  TRACK_MEMORY_SCOPE("Network");
   return SbSocketSetReceiveBufferSize(socket_, size);
 }
 
 bool TCPClientSocketStarboard::SetSendBufferSize(int32 size) {
+  TRACK_MEMORY_SCOPE("Network");
   return SbSocketSetSendBufferSize(socket_, size);
 }
 
 bool TCPClientSocketStarboard::SetKeepAlive(bool enable, int delay) {
+  TRACK_MEMORY_SCOPE("Network");
   return SbSocketSetTcpKeepAlive(socket_, enable, delay);
 }
 
 bool TCPClientSocketStarboard::SetNoDelay(bool no_delay) {
+  TRACK_MEMORY_SCOPE("Network");
   return SbSocketSetTcpNoDelay(socket_, no_delay);
 }
 
 bool TCPClientSocketStarboard::SetWindowScaling(bool enable) {
+  TRACK_MEMORY_SCOPE("Network");
   return SbSocketSetTcpWindowScaling(socket_, enable);
 }
 
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/net/test/disabled_if_big_endian.h b/src/net/test/disabled_if_big_endian.h
new file mode 100644
index 0000000..dfc0eed
--- /dev/null
+++ b/src/net/test/disabled_if_big_endian.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef NET_TEST_DISABLED_IF_BIG_ENDIAN_H_
+#define NET_TEST_DISABLED_IF_BIG_ENDIAN_H_
+
+#define DISABLED_IF_BIG_ENDIAN(Test) Test
+
+#if defined(OS_STARBOARD)
+#include "starboard/configuration.h"
+#if SB_IS(BIG_ENDIAN)
+#undef DISABLED_IF_BIG_ENDIAN
+#define DISABLED_IF_BIG_ENDIAN(Test) DISABLED_##Test
+#endif  // SB_IS(BIG_ENDIAN)
+#endif  // defined(OS_STARBOARD)
+
+#endif  // NET_TEST_DISABLED_IF_BIG_ENDIAN_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.cc b/src/net/tools/dump_cache/url_to_filename_encoder.cc
new file mode 100644
index 0000000..e928ee9
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder.cc
@@ -0,0 +1,292 @@
+// Copyright (c) 2011 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 <stdlib.h>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+using std::string;
+
+namespace {
+
+// Returns 1 if buf is prefixed by "num_digits" of hex digits
+// Teturns 0 otherwise.
+// The function checks for '\0' for string termination.
+int HexDigitsPrefix(const char* buf, int num_digits) {
+  for (int i = 0; i < num_digits; i++) {
+    if (!IsHexDigit(buf[i]))
+      return 0;  // This also detects end of string as '\0' is not xdigit.
+  }
+  return 1;
+}
+
+#if defined(WIN32) || defined(__LB_XB1__) || defined(__LB_XB360__)
+#define strtoull _strtoui64
+#endif
+
+// A simple parser for long long values. Returns the parsed value if a
+// valid integer is found; else returns deflt
+// UInt64 and Int64 cannot handle decimal numbers with leading 0s.
+uint64 ParseLeadingHex64Value(const char *str, uint64 deflt) {
+  char *error = NULL;
+  const uint64 value = strtoull(str, &error, 16);
+  return (error == str) ? deflt : value;
+}
+
+}
+
+namespace net {
+
+// The escape character choice is made here -- all code and tests in this
+// directory are based off of this constant.  However, our testdata
+// has tons of dependencies on this, so it cannot be changed without
+// re-running those tests and fixing them.
+const char UrlToFilenameEncoder::kEscapeChar = ',';
+const char UrlToFilenameEncoder::kTruncationChar = '-';
+const size_t UrlToFilenameEncoder::kMaximumSubdirectoryLength = 128;
+
+void UrlToFilenameEncoder::AppendSegment(string* segment, string* dest) {
+  CHECK(!segment->empty());
+  if ((*segment == ".") || (*segment == "..")) {
+    dest->append(1, kEscapeChar);
+    dest->append(*segment);
+    segment->clear();
+  } else {
+    size_t segment_size = segment->size();
+    if (segment_size > kMaximumSubdirectoryLength) {
+      // We need to inject ",-" at the end of the segment to signify that
+      // we are inserting an artificial '/'.  This means we have to chop
+      // off at least two characters to make room.
+      segment_size = kMaximumSubdirectoryLength - 2;
+
+      // But we don't want to break up an escape sequence that happens to lie at
+      // the end.  Escape sequences are at most 2 characters.
+      if ((*segment)[segment_size - 1] == kEscapeChar) {
+        segment_size -= 1;
+      } else if ((*segment)[segment_size - 2] == kEscapeChar) {
+        segment_size -= 2;
+      }
+      dest->append(segment->data(), segment_size);
+      dest->append(1, kEscapeChar);
+      dest->append(1, kTruncationChar);
+      segment->erase(0, segment_size);
+
+      // At this point, if we had segment_size=3, and segment="abcd",
+      // then after this erase, we will have written "abc,-" and set segment="d"
+    } else {
+      dest->append(*segment);
+      segment->clear();
+    }
+  }
+}
+
+void UrlToFilenameEncoder::EncodeSegment(const string& filename_prefix,
+                                         const string& escaped_ending,
+                                         char dir_separator,
+                                         string* encoded_filename) {
+  string filename_ending = UrlUtilities::Unescape(escaped_ending);
+
+  char encoded[3];
+  int encoded_len;
+  string segment;
+
+  // TODO(jmarantz): This code would be a bit simpler if we disallowed
+  // Instaweb allowing filename_prefix to not end in "/".  We could
+  // then change the is routine to just take one input string.
+  size_t start_of_segment = filename_prefix.find_last_of(dir_separator);
+  if (start_of_segment == string::npos) {
+    segment = filename_prefix;
+  } else {
+    segment = filename_prefix.substr(start_of_segment + 1);
+    *encoded_filename = filename_prefix.substr(0, start_of_segment + 1);
+  }
+
+  size_t index = 0;
+  // Special case the first / to avoid adding a leading kEscapeChar.
+  if (!filename_ending.empty() && (filename_ending[0] == dir_separator)) {
+    encoded_filename->append(segment);
+    segment.clear();
+    encoded_filename->append(1, dir_separator);
+    ++index;
+  }
+
+  for (; index < filename_ending.length(); ++index) {
+    unsigned char ch = static_cast<unsigned char>(filename_ending[index]);
+
+    // Note: instead of outputing an empty segment, we let the second slash
+    // be escaped below.
+    if ((ch == dir_separator) && !segment.empty()) {
+      AppendSegment(&segment, encoded_filename);
+      encoded_filename->append(1, dir_separator);
+      segment.clear();
+    } else {
+      // After removing unsafe chars the only safe ones are _.=+- and alphanums.
+      if ((ch == '_') || (ch == '.') || (ch == '=') || (ch == '+') ||
+          (ch == '-') || (('0' <= ch) && (ch <= '9')) ||
+          (('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) {
+        encoded[0] = ch;
+        encoded_len = 1;
+      } else {
+        encoded[0] = kEscapeChar;
+        encoded[1] = ch / 16;
+        encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+        encoded[2] = ch % 16;
+        encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+        encoded_len = 3;
+      }
+      segment.append(encoded, encoded_len);
+
+      // If segment is too big, we must chop it into chunks.
+      if (segment.size() > kMaximumSubdirectoryLength) {
+        AppendSegment(&segment, encoded_filename);
+        encoded_filename->append(1, dir_separator);
+      }
+    }
+  }
+
+  // Append "," to the leaf filename so the leaf can also be a branch., e.g.
+  // allow http://a/b/c and http://a/b/c/d to co-exist as files "/a/b/c," and
+  // /a/b/c/d".  So we will rename the "d" here to "d,".  If doing that pushed
+  // us over the 128 char limit, then we will need to append "/" and the
+  // remaining chars.
+  segment += kEscapeChar;
+  AppendSegment(&segment, encoded_filename);
+  if (!segment.empty()) {
+    // The last overflow segment is special, because we appended in
+    // kEscapeChar above.  We won't need to check it again for size
+    // or further escaping.
+    encoded_filename->append(1, dir_separator);
+    encoded_filename->append(segment);
+  }
+}
+
+// Note: this decoder is not the exact inverse of the EncodeSegment above,
+// because it does not take into account a prefix.
+bool UrlToFilenameEncoder::Decode(const string& encoded_filename,
+                                  char dir_separator,
+                                  string* decoded_url) {
+  enum State {
+    kStart,
+    kEscape,
+    kFirstDigit,
+    kTruncate,
+    kEscapeDot
+  };
+  State state = kStart;
+  char hex_buffer[3];
+  hex_buffer[2] = '\0';
+  for (size_t i = 0; i < encoded_filename.size(); ++i) {
+    char ch = encoded_filename[i];
+    switch (state) {
+      case kStart:
+        if (ch == kEscapeChar) {
+          state = kEscape;
+        } else if (ch == dir_separator) {
+          decoded_url->append(1, '/');  // URLs only use '/' not '\\'
+        } else {
+          decoded_url->append(1, ch);
+        }
+        break;
+      case kEscape:
+        if (HexDigitsPrefix(&ch, 1) == 1) {
+          hex_buffer[0] = ch;
+          state = kFirstDigit;
+        } else if (ch == kTruncationChar) {
+          state = kTruncate;
+        } else if (ch == '.') {
+          decoded_url->append(1, '.');
+          state = kEscapeDot;  // Look for at most one more dot.
+        } else if (ch == dir_separator) {
+          // Consider url "//x".  This was once encoded to "/,/x,".
+          // This code is what skips the first Escape.
+          decoded_url->append(1, '/');  // URLs only use '/' not '\\'
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kFirstDigit:
+        if (HexDigitsPrefix(&ch, 1) == 1) {
+          hex_buffer[1] = ch;
+          uint64 hex_value = ParseLeadingHex64Value(hex_buffer, 0);
+          decoded_url->append(1, static_cast<char>(hex_value));
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kTruncate:
+        if (ch == dir_separator) {
+          // Skip this separator, it was only put in to break up long
+          // path segments, but is not part of the URL.
+          state = kStart;
+        } else {
+          return false;
+        }
+        break;
+      case kEscapeDot:
+        decoded_url->append(1, ch);
+        state = kStart;
+        break;
+    }
+  }
+
+  // All legal encoded filenames end in kEscapeChar.
+  return (state == kEscape);
+}
+
+// Escape the given input |path| and chop any individual components
+// of the path which are greater than kMaximumSubdirectoryLength characters
+// into two chunks.
+//
+// This legacy version has several issues with aliasing of different URLs,
+// inability to represent both /a/b/c and /a/b/c/d, and inability to decode
+// the filenames back into URLs.
+//
+// But there is a large body of slurped data which depends on this format,
+// so leave it as the default for spdy_in_mem_edsm_server.
+string UrlToFilenameEncoder::LegacyEscape(const string& path) {
+  string output;
+
+  // Note:  We also chop paths into medium sized 'chunks'.
+  //        This is due to the incompetence of the windows
+  //        filesystem, which still hasn't figured out how
+  //        to deal with long filenames.
+  int last_slash = 0;
+  for (size_t index = 0; index < path.length(); index++) {
+    char ch = path[index];
+    if (ch == 0x5C)
+      last_slash = index;
+    if ((ch == 0x2D) ||                    // hyphen
+        (ch == 0x5C) || (ch == 0x5F) ||    // backslash, underscore
+        ((0x30 <= ch) && (ch <= 0x39)) ||  // Digits [0-9]
+        ((0x41 <= ch) && (ch <= 0x5A)) ||  // Uppercase [A-Z]
+        ((0x61 <= ch) && (ch <= 0x7A))) {  // Lowercase [a-z]
+      output.append(&path[index], 1);
+    } else {
+      char encoded[3];
+      encoded[0] = 'x';
+      encoded[1] = ch / 16;
+      encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+      encoded[2] = ch % 16;
+      encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+      output.append(encoded, 3);
+    }
+    if (index - last_slash > kMaximumSubdirectoryLength) {
+#ifdef WIN32
+      char slash = '\\';
+#else
+      char slash = '/';
+#endif
+      output.append(&slash, 1);
+      last_slash = index;
+    }
+  }
+  return output;
+}
+
+}  // namespace net
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.h b/src/net/tools/dump_cache/url_to_filename_encoder.h
new file mode 100644
index 0000000..b81a854
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2010 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.
+
+// URL filename encoder goals:
+//
+// 1. Allow URLs with arbitrary path-segment length, generating filenames
+//    with a maximum of 128 characters.
+// 2. Provide a somewhat human readable filenames, for easy debugging flow.
+// 3. Provide reverse-mapping from filenames back to URLs.
+// 4. Be able to distinguish http://x from http://x/ from http://x/index.html.
+//    Those can all be different URLs.
+// 5. Be able to represent http://a/b/c and http://a/b/c/d, a pattern seen
+//    with Facebook Connect.
+//
+// We need an escape-character for representing characters that are legal
+// in URL paths, but not in filenames, such as '?'.
+//
+// We can pick any legal character as an escape, as long as we escape it too.
+// But as we have a goal of having filenames that humans can correlate with
+// URLs, we should pick one that doesn't show up frequently in URLs. Candidates
+// are ~`!@#$%^&()-=_+{}[],. but we would prefer to avoid characters that are
+// shell escapes or that various build tools use.
+//
+// .#&%-=_+ occur frequently in URLs.
+// <>:"/\|?* are illegal in Windows
+//   See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+// ~`!$^&(){}[]'; are special to Unix shells
+// In addition, build tools do not like ^@#%
+//
+// Josh took a quick look at the frequency of some special characters in
+// Sadeesh's slurped directory from Fall 09 and found the following occurances:
+//
+//   ^   3               build tool doesn't like ^ in testdata filenames
+//   @   10              build tool doesn't like @ in testdata filenames
+//   .   1676            too frequent in URLs
+//   ,   76              THE WINNER
+//   #   0               build tool doesn't like it
+//   &   487             Prefer to avoid shell escapes
+//   %   374             g4 doesn't like it
+//   =   579             very frequent in URLs -- leave unmodified
+//   -   464             very frequent in URLs -- leave unmodified
+//   _   798             very frequent in URLs -- leave unmodified
+//
+//
+// The escaping algorithm is:
+//  1) Escape all unfriendly symbols as ,XX where XX is the hex code.
+//  2) Add a ',' at the end (We do not allow ',' at end of any directory name,
+//     so this assures that e.g. /a and /a/b can coexist in the filesystem).
+//  3) Go through the path segment by segment (where a segment is one directory
+//     or leaf in the path) and
+//     3a) If the segment is empty, escape the second slash. i.e. if it was
+//         www.foo.com//a then we escape the second / like www.foo.com/,2Fa,
+//     3a) If it is "." or ".." prepend with ',' (so that we have a non-
+//         empty and non-reserved filename).
+//     3b) If it is over 128 characters, break it up into smaller segments by
+//         inserting ,-/ (Windows limits paths to 128 chars, other OSes also
+//         have limits that would restrict us)
+//
+// For example:
+//     URL               File
+//     /                 /,
+//     /index.html       /index.html,
+//     /.                /.,
+//     /a/b              /a/b,
+//     /a/b/             /a/b/,
+//     /a/b/c            /a/b/c,   Note: no prefix problem
+//     /u?foo=bar        /u,3Ffoo=bar,
+//     //                /,2F,
+//     /./               /,./,
+//     /../              /,../,
+//     /,                /,2C,
+//     /,./              /,2C./,
+//     /very...longname/ /very...long,-/name   If very...long is about 126 long.
+
+// NOTE: we avoid using some classes here (like FilePath and GURL) because we
+//       share this code with other projects externally.
+
+#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+#define NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+
+#include <string>
+
+#include "base/string_util.h"
+#include "net/tools/dump_cache/url_utilities.h"
+
+namespace net {
+
+// Helper class for converting a URL into a filename.
+class UrlToFilenameEncoder {
+ public:
+  // Given a |url| and a |base_path|, returns a filename which represents this
+  // |url|. |url| may include URL escaping such as %21 for !
+  // |legacy_escape| indicates that this function should use the old-style
+  // of encoding.
+  // TODO(mbelshe): delete the legacy_escape code.
+  static std::string Encode(const std::string& url, std::string base_path,
+                            bool legacy_escape) {
+    std::string filename;
+    if (!legacy_escape) {
+      std::string url_no_scheme = UrlUtilities::GetUrlHostPath(url);
+      EncodeSegment(base_path, url_no_scheme, '/', &filename);
+#ifdef WIN32
+      ReplaceAll(&filename, "/", "\\");
+#endif
+    } else {
+      std::string clean_url(url);
+      if (clean_url.length() && clean_url[clean_url.length()-1] == '/')
+        clean_url.append("index.html");
+
+      std::string host = UrlUtilities::GetUrlHost(clean_url);
+      filename.append(base_path);
+      filename.append(host);
+#ifdef WIN32
+      filename.append("\\");
+#else
+      filename.append("/");
+#endif
+
+      std::string url_filename = UrlUtilities::GetUrlPath(clean_url);
+      // Strip the leading '/'.
+      if (url_filename[0] == '/')
+        url_filename = url_filename.substr(1);
+
+      // Replace '/' with '\'.
+      ConvertToSlashes(&url_filename);
+
+      // Strip double back-slashes ("\\\\").
+      StripDoubleSlashes(&url_filename);
+
+      // Save path as filesystem-safe characters.
+      url_filename = LegacyEscape(url_filename);
+      filename.append(url_filename);
+
+#ifndef WIN32
+      // Last step - convert to native slashes.
+      const std::string slash("/");
+      const std::string backslash("\\");
+      ReplaceAll(&filename, backslash, slash);
+#endif
+    }
+
+    return filename;
+  }
+
+  // Rewrite HTML in a form that the SPDY in-memory server
+  // can read.
+  // |filename_prefix| is prepended without escaping.
+  // |escaped_ending| is the URL to be encoded into a filename. It may have URL
+  // escaped characters (like %21 for !).
+  // |dir_separator| is "/" on Unix, "\" on Windows.
+  // |encoded_filename| is the resultant filename.
+  static void EncodeSegment(
+      const std::string& filename_prefix,
+      const std::string& escaped_ending,
+      char dir_separator,
+      std::string* encoded_filename);
+
+  // Decodes a filename that was encoded with EncodeSegment,
+  // yielding back the original URL.
+  static bool Decode(const std::string& encoded_filename,
+                     char dir_separator,
+                     std::string* decoded_url);
+
+  static const char kEscapeChar;
+  static const char kTruncationChar;
+  static const size_t kMaximumSubdirectoryLength;
+
+  friend class UrlToFilenameEncoderTest;
+
+ private:
+  // Appends a segment of the path, special-casing "." and "..", and
+  // ensuring that the segment does not exceed the path length.  If it does,
+  // it chops the end off the segment, writes the segment with a separator of
+  // ",-/", and then rewrites segment to contain just the truncated piece so
+  // it can be used in the next iteration.
+  // |segment| is a read/write parameter containing segment to write
+  // Note: this should not be called with empty segment.
+  static void AppendSegment(std::string* segment, std::string* dest);
+
+  // Allow reading of old slurped files.
+  static std::string LegacyEscape(const std::string& path);
+
+  // Replace all instances of |from| within |str| as |to|.
+  static void ReplaceAll(std::string* str, const std::string& from,
+                         const std::string& to) {
+    std::string::size_type pos(0);
+    while ((pos = str->find(from, pos)) != std::string::npos) {
+      str->replace(pos, from.size(), to);
+      pos += from.size();
+    }
+  }
+
+  // Replace all instances of "/" with "\" in |path|.
+  static void ConvertToSlashes(std::string* path) {
+    const std::string slash("/");
+    const std::string backslash("\\");
+    ReplaceAll(path, slash, backslash);
+  }
+
+  // Replace all instances of "\\" with "%5C%5C" in |path|.
+  static void StripDoubleSlashes(std::string* path) {
+    const std::string doubleslash("\\\\");
+    const std::string escaped_doubleslash("%5C%5C");
+    ReplaceAll(path, doubleslash, escaped_doubleslash);
+  }
+};
+
+}  // namespace net
+
+#endif  // NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
new file mode 100644
index 0000000..2e09e0b
--- /dev/null
+++ b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_to_filename_encoder.h"
+
+#include <string>
+#include <vector>
+
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+#ifdef WIN32
+char kDirSeparator = '\\';
+char kOtherDirSeparator = '/';
+#else
+char kDirSeparator = '/';
+char kOtherDirSeparator = '\\';
+#endif
+
+class UrlToFilenameEncoderTest : public ::testing::Test {
+ protected:
+  UrlToFilenameEncoderTest() : escape_(1, UrlToFilenameEncoder::kEscapeChar),
+                               dir_sep_(1, kDirSeparator) {
+  }
+
+  void CheckSegmentLength(const StringPiece& escaped_word) {
+    std::vector<StringPiece> components;
+    Tokenize(escaped_word, StringPiece("/"), &components);
+    for (size_t i = 0; i < components.size(); ++i) {
+      EXPECT_GE(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+                components[i].size());
+    }
+  }
+
+  void CheckValidChars(const StringPiece& escaped_word, char invalid_slash) {
+    // These characters are invalid in Windows.  We add in ', as that's pretty
+    // inconvenient in a Unix filename.
+    //
+    // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+    const string kInvalidChars = "<>:\"|?*'";
+    for (size_t i = 0; i < escaped_word.size(); ++i) {
+      char c = escaped_word[i];
+      EXPECT_EQ(string::npos, kInvalidChars.find(c));
+      EXPECT_NE(invalid_slash, c);
+      EXPECT_NE('\0', c);  // only invalid character in Posix
+      EXPECT_GT(0x7E, c);  // only English printable characters
+    }
+  }
+
+  void Validate(const string& in_word, const string& gold_word) {
+    string escaped_word, url;
+    UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+    EXPECT_EQ(gold_word, escaped_word);
+    CheckSegmentLength(escaped_word);
+    CheckValidChars(escaped_word, '\\');
+    UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+    EXPECT_EQ(in_word, url);
+  }
+
+  void ValidateAllSegmentsSmall(const string& in_word) {
+    string escaped_word, url;
+    UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word);
+    CheckSegmentLength(escaped_word);
+    CheckValidChars(escaped_word, '\\');
+    UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+    EXPECT_EQ(in_word, url);
+  }
+
+  void ValidateNoChange(const string& word) {
+    // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+    Validate(word, word + escape_);
+  }
+
+  void ValidateEscaped(unsigned char ch) {
+    // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+    char escaped[100];
+    const char escape = UrlToFilenameEncoder::kEscapeChar;
+    base::snprintf(escaped, sizeof(escaped), "%c%02X%c", escape, ch, escape);
+    Validate(string(1, ch), escaped);
+  }
+
+  void ValidateUrl(const string& url, const string& base_path,
+                   bool legacy_escape, const string& gold_filename) {
+    string encoded_filename = UrlToFilenameEncoder::Encode(
+        url, base_path, legacy_escape);
+    EXPECT_EQ(gold_filename, encoded_filename);
+    if (!legacy_escape) {
+      CheckSegmentLength(encoded_filename);
+      CheckValidChars(encoded_filename, kOtherDirSeparator);
+      string decoded_url;
+      UrlToFilenameEncoder::Decode(encoded_filename, kDirSeparator,
+                                   &decoded_url);
+      if (url != decoded_url) {
+        EXPECT_EQ(url, "http://" + decoded_url);
+      }
+    }
+  }
+
+  void ValidateUrlOldNew(const string& url, const string& gold_old_filename,
+                         const string& gold_new_filename) {
+    ValidateUrl(url, "", true, gold_old_filename);
+    ValidateUrl(url, "", false, gold_new_filename);
+  }
+
+  void ValidateEncodeSame(const string& url1, const string& url2) {
+    string filename1 = UrlToFilenameEncoder::Encode(url1, "", false);
+    string filename2 = UrlToFilenameEncoder::Encode(url2, "", false);
+    EXPECT_EQ(filename1, filename2);
+  }
+
+  string escape_;
+  string dir_sep_;
+};
+
+TEST_F(UrlToFilenameEncoderTest, DoesNotEscape) {
+  ValidateNoChange("");
+  ValidateNoChange("abcdefg");
+  ValidateNoChange("abcdefghijklmnopqrstuvwxyz");
+  ValidateNoChange("ZYXWVUT");
+  ValidateNoChange("ZYXWVUTSRQPONMLKJIHGFEDCBA");
+  ValidateNoChange("01234567689");
+  ValidateNoChange("_.=+-");
+  ValidateNoChange("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA"
+                   "01234567689_.=+-");
+  ValidateNoChange("index.html");
+  ValidateNoChange("/");
+  ValidateNoChange("/.");
+  ValidateNoChange(".");
+  ValidateNoChange("..");
+}
+
+TEST_F(UrlToFilenameEncoderTest, Escapes) {
+  const string bad_chars =
+      "<>:\"\\|?*"      // Illegal on Windows
+      "~`!$^&(){}[]';"  // Bad for Unix shells
+      "^@"              // Build tool doesn't like
+      "#%"              // Tool doesn't like
+      ",";              // The escape char has to be escaped
+
+  for (size_t i = 0; i < bad_chars.size(); ++i) {
+    ValidateEscaped(bad_chars[i]);
+  }
+
+  // Check non-printable characters.
+  ValidateEscaped('\0');
+  for (size_t i = 127; i < 256; ++i) {
+    ValidateEscaped(static_cast<char>(i));
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, DoesEscapeCorrectly) {
+  Validate("mysite.com&x", "mysite.com" + escape_ + "26x" + escape_);
+  Validate("/./", "/" + escape_ + "./" + escape_);
+  Validate("/../", "/" + escape_ + "../" + escape_);
+  Validate("//", "/" + escape_ + "2F" + escape_);
+  Validate("/./leaf", "/" + escape_ + "./leaf" + escape_);
+  Validate("/../leaf", "/" + escape_ + "../leaf" + escape_);
+  Validate("//leaf", "/" + escape_ + "2Fleaf" + escape_);
+  Validate("mysite/u?param1=x&param2=y",
+           "mysite/u" + escape_ + "3Fparam1=x" + escape_ + "26param2=y" +
+           escape_);
+  Validate("search?q=dogs&go=&form=QBLH&qs=n",  // from Latency Labs bing test.
+           "search" + escape_ + "3Fq=dogs" + escape_ + "26go=" + escape_ +
+           "26form=QBLH" + escape_ + "26qs=n" + escape_);
+  Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true",
+           "" + escape_ + "7Ejoebob/my_neeto-website+with_stuff.asp" + escape_ +
+           "3Fid=138" + escape_ + "26content=true" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, EncodeUrlCorrectly) {
+  ValidateUrlOldNew("http://www.google.com/index.html",
+                    "www.google.com" + dir_sep_ + "indexx2Ehtml",
+                    "www.google.com" + dir_sep_ + "index.html" + escape_);
+  ValidateUrlOldNew("http://www.google.com/x/search?hl=en&q=dogs&oq=",
+                    "www.google.com" + dir_sep_ + "x" + dir_sep_ +
+                    "searchx3Fhlx3Denx26qx3Ddogsx26oqx3D",
+
+                    "www.google.com" + dir_sep_ + "x" + dir_sep_ + "search" +
+                    escape_ + "3Fhl=en" + escape_ + "26q=dogs" + escape_ +
+                    "26oq=" + escape_);
+  ValidateUrlOldNew("http://www.foo.com/a//",
+                    "www.foo.com" + dir_sep_ + "ax255Cx255Cindexx2Ehtml",
+                    "www.foo.com" + dir_sep_ + "a" + dir_sep_ + escape_ + "2F" +
+                    escape_);
+
+  // From bug: Double slash preserved.
+  ValidateUrl("http://www.foo.com/u?site=http://www.google.com/index.html",
+              "", false,
+              "www.foo.com" + dir_sep_ + "u" + escape_ + "3Fsite=http" +
+              escape_ + "3A" + dir_sep_ + escape_ + "2Fwww.google.com" +
+              dir_sep_ + "index.html" + escape_);
+  ValidateUrlOldNew(
+      "http://blogutils.net/olct/online.php?"
+      "site=http://thelwordfanfics.blogspot.&interval=600",
+
+      "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "onlinex2Ephpx3F"
+      "sitex3Dhttpx3Ax255Cx255Cthelwordfanficsx2Eblogspotx2Ex26intervalx3D600",
+
+      "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "online.php" + escape_ +
+      "3Fsite=http" + escape_ + "3A" + dir_sep_ + escape_ +
+      "2Fthelwordfanfics.blogspot." + escape_ + "26interval=600" + escape_);
+}
+
+// From bug: Escapes treated the same as normal char.
+TEST_F(UrlToFilenameEncoderTest, UnescapeUrlsBeforeEncode) {
+  for (int i = 0; i < 128; ++i) {
+    string unescaped(1, static_cast<char>(i));
+    string escaped = base::StringPrintf("%%%02X", i);
+    ValidateEncodeSame(unescaped, escaped);
+  }
+
+  ValidateEncodeSame(
+      "http://www.blogger.com/navbar.g?bName=God!&Mode=FOO&searchRoot"
+      "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch",
+
+      "http://www.blogger.com/navbar.g?bName=God%21&Mode=FOO&searchRoot"
+      "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch");
+}
+
+// From bug: Filename encoding is not prefix-free.
+TEST_F(UrlToFilenameEncoderTest, EscapeSecondSlash) {
+  Validate("/", "/" + escape_);
+  Validate("//", "/" + escape_ + "2F" + escape_);
+  Validate("///", "/" + escape_ + "2F" + "/" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTail) {
+  static char long_word[] =
+      "~joebob/briggs/12345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890"
+      "1234567890123456789012345678901234567890123456789012345678901234567890";
+
+  // the long lines in the string below are 64 characters, so we can see
+  // the slashes every 128.
+  string gold_long_word =
+      escape_ + "7Ejoebob/briggs/"
+      "1234567890123456789012345678901234567890123456789012345678901234"
+      "56789012345678901234567890123456789012345678901234567890123456" +
+      escape_ + "-/"
+      "7890123456789012345678901234567890123456789012345678901234567890"
+      "12345678901234567890123456789012345678901234567890123456789012" +
+      escape_ + "-/"
+      "3456789012345678901234567890123456789012345678901234567890123456"
+      "78901234567890123456789012345678901234567890123456789012345678" +
+      escape_ + "-/"
+      "9012345678901234567890" + escape_;
+  EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            sizeof(long_word));
+  Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTailQuestion) {
+  // Here the '?' in the last path segment expands to @3F, making
+  // it hit 128 chars before the input segment gets that big.
+  static char long_word[] =
+      "~joebob/briggs/1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+      "1234567?1234567?1234567?1234567?1234567?1234567?1234567?";
+
+  // Notice that at the end of the third segment, we avoid splitting
+  // the (escape_ + "3F") that was generated from the "?", so that segment is
+  // only 127 characters.
+  string pattern = "1234567" + escape_ + "3F";  // 10 characters
+  string gold_long_word =
+      escape_ + "7Ejoebob/briggs/" +
+      pattern + pattern + pattern + pattern + pattern + pattern + "1234"
+      "567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+       "123456" + escape_ + "-/"
+      "7" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+      pattern + pattern + pattern + pattern + pattern + pattern + pattern +
+      "12" +
+      escape_ + "-/"
+      "34567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern
+      + "1234567" + escape_ + "3F" + pattern + pattern + pattern + pattern
+      + pattern + "1234567" +
+      escape_ + "-/" +
+      escape_ + "3F" + pattern + pattern + escape_;
+  EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            sizeof(long_word));
+  Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenNoEscape) {
+  // hit corner cases, +/- 4 characters from kMaxLen
+  for (int i = -4; i <= 4; ++i) {
+    string input;
+    input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength, 'x');
+    ValidateAllSegmentsSmall(input);
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenWithEscape) {
+  // hit corner cases, +/- 4 characters from kMaxLen.  This time we
+  // leave off the last 'x' and put in a '.', which ensures that we
+  // are truncating with '/' *after* the expansion.
+  for (int i = -4; i <= 4; ++i) {
+    string input;
+    input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength - 1, 'x');
+    input.append(1, '.');  // this will expand to 3 characters.
+    ValidateAllSegmentsSmall(input);
+  }
+}
+
+TEST_F(UrlToFilenameEncoderTest, LeafBranchAlias) {
+  Validate("/a/b/c", "/a/b/c" + escape_);        // c is leaf file "c,"
+  Validate("/a/b/c/d", "/a/b/c/d" + escape_);    // c is directory "c"
+  Validate("/a/b/c/d/", "/a/b/c/d/" + escape_);
+}
+
+
+TEST_F(UrlToFilenameEncoderTest, BackslashSeparator) {
+  string long_word;
+  string escaped_word;
+  long_word.append(UrlToFilenameEncoder::kMaximumSubdirectoryLength + 1, 'x');
+  UrlToFilenameEncoder::EncodeSegment("", long_word, '\\', &escaped_word);
+
+  // check that one backslash, plus the escape ",-", and the ending , got added.
+  EXPECT_EQ(long_word.size() + 4, escaped_word.size());
+  ASSERT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+            escaped_word.size());
+  // Check that the backslash got inserted at the correct spot.
+  EXPECT_EQ('\\', escaped_word[
+      UrlToFilenameEncoder::kMaximumSubdirectoryLength]);
+}
+
+}  // namespace net
+
diff --git a/src/net/tools/dump_cache/url_utilities.cc b/src/net/tools/dump_cache/url_utilities.cc
new file mode 100644
index 0000000..fe64bd9
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+
+namespace net {
+
+std::string UrlUtilities::GetUrlHost(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  size_t next_slash = url.find_first_of('/', b);
+  size_t next_colon = url.find_first_of(':', b);
+  if (next_slash != std::string::npos
+      && next_colon != std::string::npos
+      && next_colon < next_slash) {
+    return std::string(url, b, next_colon - b);
+  }
+  if (next_slash == std::string::npos) {
+    if (next_colon != std::string::npos) {
+      return std::string(url, b, next_colon - b);
+    } else {
+      next_slash = url.size();
+    }
+  }
+  return std::string(url, b, next_slash - b);
+}
+
+std::string UrlUtilities::GetUrlHostPath(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  return std::string(url, b);
+}
+
+std::string UrlUtilities::GetUrlPath(const std::string& url) {
+  size_t b = url.find("//");
+  if (b == std::string::npos)
+    b = 0;
+  else
+    b += 2;
+  b = url.find("/", b);
+  if (b == std::string::npos)
+    return "/";
+
+  size_t e = url.find("#", b+1);
+  if (e != std::string::npos)
+    return std::string(url, b, (e - b));
+  return std::string(url, b);
+}
+
+namespace {
+
+// Parsing states for UrlUtilities::Unescape
+enum UnescapeState {
+  NORMAL,   // We are not in the middle of parsing an escape.
+  ESCAPE1,  // We just parsed % .
+  ESCAPE2   // We just parsed %X for some hex digit X.
+};
+
+}  // namespace
+
+std::string UrlUtilities::Unescape(const std::string& escaped_url) {
+  std::string unescaped_url, escape_text;
+  int escape_value;
+  UnescapeState state = NORMAL;
+  std::string::const_iterator iter = escaped_url.begin();
+  while (iter < escaped_url.end()) {
+    char c = *iter;
+    switch (state) {
+      case NORMAL:
+        if (c == '%') {
+          escape_text.clear();
+          state = ESCAPE1;
+        } else {
+          unescaped_url.push_back(c);
+        }
+        ++iter;
+        break;
+      case ESCAPE1:
+        if (IsHexDigit(c)) {
+          escape_text.push_back(c);
+          state = ESCAPE2;
+          ++iter;
+        } else {
+          // Unexpected, % followed by non-hex chars, pass it through.
+          unescaped_url.push_back('%');
+          state = NORMAL;
+        }
+        break;
+      case ESCAPE2:
+        if (IsHexDigit(c)) {
+          escape_text.push_back(c);
+          bool ok = base::HexStringToInt(escape_text, &escape_value);
+          DCHECK(ok);
+          unescaped_url.push_back(static_cast<unsigned char>(escape_value));
+          state = NORMAL;
+          ++iter;
+        } else {
+          // Unexpected, % followed by non-hex chars, pass it through.
+          unescaped_url.push_back('%');
+          unescaped_url.append(escape_text);
+          state = NORMAL;
+        }
+        break;
+    }
+  }
+  // Unexpected, % followed by end of string, pass it through.
+  if (state == ESCAPE1 || state == ESCAPE2) {
+    unescaped_url.push_back('%');
+    unescaped_url.append(escape_text);
+  }
+  return unescaped_url;
+}
+
+}  // namespace net
+
diff --git a/src/net/tools/dump_cache/url_utilities.h b/src/net/tools/dump_cache/url_utilities.h
new file mode 100644
index 0000000..c9d8ea5
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 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 NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+#define NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+
+#include <string>
+
+namespace net {
+
+struct UrlUtilities {
+  // Gets the host from an url, strips the port number as well if the url
+  // has one.
+  // For example: calling GetUrlHost(www.foo.com:8080/boo) returns www.foo.com
+  static std::string GetUrlHost(const std::string& url);
+
+  // Get the host + path portion of an url
+  // e.g   http://www.foo.com/path
+  //       returns www.foo.com/path
+  static std::string GetUrlHostPath(const std::string& url);
+
+  // Gets the path portion of an url.
+  // e.g   http://www.foo.com/path
+  //       returns /path
+  static std::string GetUrlPath(const std::string& url);
+
+  // Unescape a url, converting all %XX to the the actual char 0xXX.
+  // For example, this will convert "foo%21bar" to "foo!bar".
+  static std::string Unescape(const std::string& escaped_url);
+};
+
+}  // namespace net
+
+#endif  // NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
diff --git a/src/net/tools/dump_cache/url_utilities_unittest.cc b/src/net/tools/dump_cache/url_utilities_unittest.cc
new file mode 100644
index 0000000..0f9cb06
--- /dev/null
+++ b/src/net/tools/dump_cache/url_utilities_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h"
+
+#include <string>
+
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(UrlUtilitiesTest, GetUrlHost) {
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com:80"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com:80/"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("http://www.foo.com/news"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHost("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlHostPath) {
+  EXPECT_EQ("www.foo.com",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com:80",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com:80"));
+  EXPECT_EQ("www.foo.com:80/",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com:80/"));
+  EXPECT_EQ("www.foo.com/news",
+            UrlUtilities::GetUrlHostPath("http://www.foo.com/news"));
+  EXPECT_EQ("www.foo.com:80/news?q=hello",
+            UrlUtilities::GetUrlHostPath("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("www.foo.com/news?q=a:b",
+            UrlUtilities::GetUrlHostPath("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("www.foo.com:80",
+            UrlUtilities::GetUrlHostPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlPath) {
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com:80"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("http://www.foo.com:80/"));
+  EXPECT_EQ("/news",
+            UrlUtilities::GetUrlPath("http://www.foo.com/news"));
+  EXPECT_EQ("/news?q=hello",
+            UrlUtilities::GetUrlPath("www.foo.com:80/news?q=hello"));
+  EXPECT_EQ("/news?q=a:b",
+            UrlUtilities::GetUrlPath("www.foo.com/news?q=a:b"));
+  EXPECT_EQ("/",
+            UrlUtilities::GetUrlPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, Unescape) {
+  // Basic examples are left alone.
+  EXPECT_EQ("http://www.foo.com",
+            UrlUtilities::Unescape("http://www.foo.com"));
+  EXPECT_EQ("www.foo.com:80/news?q=hello",
+            UrlUtilities::Unescape("www.foo.com:80/news?q=hello"));
+
+  // All chars can be unescaped.
+  EXPECT_EQ("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/",
+            UrlUtilities::Unescape("%7E%60%21%40%23%24%25%5E%26%2A%28%29%5F%2D"
+                                   "%2B%3D%7B%5B%7D%5D%7C%5C%3A%3B%22%27%3C%2C"
+                                   "%3E%2E%3F%2F"));
+  for (int c = 0; c < 256; ++c) {
+    std::string unescaped_char(1, implicit_cast<unsigned char>(c));
+    std::string escaped_char = base::StringPrintf("%%%02X", c);
+    EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+        << "escaped_char = " << escaped_char;
+    escaped_char = base::StringPrintf("%%%02x", c);
+    EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+        << "escaped_char = " << escaped_char;
+  }
+
+  // All non-% chars are left alone.
+  EXPECT_EQ("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/",
+            UrlUtilities::Unescape("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/"));
+  for (int c = 0; c < 256; ++c) {
+    if (c != '%') {
+      std::string just_char(1, implicit_cast<unsigned char>(c));
+      EXPECT_EQ(just_char, UrlUtilities::Unescape(just_char));
+    }
+  }
+
+  // Some examples to unescape.
+  EXPECT_EQ("Hello, world!", UrlUtilities::Unescape("Hello%2C world%21"));
+
+  // Not actually escapes.
+  EXPECT_EQ("%", UrlUtilities::Unescape("%"));
+  EXPECT_EQ("%www", UrlUtilities::Unescape("%www"));
+  EXPECT_EQ("%foo", UrlUtilities::Unescape("%foo"));
+  EXPECT_EQ("%1", UrlUtilities::Unescape("%1"));
+  EXPECT_EQ("%1x", UrlUtilities::Unescape("%1x"));
+  EXPECT_EQ("%%", UrlUtilities::Unescape("%%"));
+  // Escapes following non-escapes.
+  EXPECT_EQ("%!", UrlUtilities::Unescape("%%21"));
+  EXPECT_EQ("%2!", UrlUtilities::Unescape("%2%21"));
+}
+
+}  // namespace net
+
diff --git a/src/net/udp/udp_listen_socket.cc b/src/net/udp/udp_listen_socket.cc
index 848290b..82fdfd9 100644
--- a/src/net/udp/udp_listen_socket.cc
+++ b/src/net/udp/udp_listen_socket.cc
@@ -33,6 +33,7 @@
 #include "base/sys_byteorder.h"
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
+#include "nb/memory_scope.h"
 #include "net/base/net_util.h"
 #if defined(OS_STARBOARD)
 #include "starboard/socket.h"
@@ -87,12 +88,14 @@
 
 void UDPListenSocket::SendTo(const IPEndPoint& address,
                              const std::string& str) {
+  TRACK_MEMORY_SCOPE("Network");
   SendTo(address, str.data(), static_cast<int>(str.length()));
 }
 
 void UDPListenSocket::SendTo(const IPEndPoint& address,
                              const char* bytes,
                              int len) {
+  TRACK_MEMORY_SCOPE("Network");
 #if defined(OS_STARBOARD)
   SbSocketAddress dst_addr;
   if (!address.ToSbSocketAddress(&dst_addr)) {
@@ -130,6 +133,7 @@
 }
 
 void UDPListenSocket::Read() {
+  TRACK_MEMORY_SCOPE("Network");
   if (buffer_ == NULL) {
     // +1 for null termination
     buffer_.reset(new char[kUdpMaxPacketSize + 1]);
@@ -192,6 +196,7 @@
 }
 
 void UDPListenSocket::WatchSocket() {
+  TRACK_MEMORY_SCOPE("Network");
 #if defined(OS_STARBOARD)
   MessageLoopForIO::current()->Watch(
       socket_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
diff --git a/src/net/udp/udp_listen_socket_unittest.cc b/src/net/udp/udp_listen_socket_unittest.cc
index 4f3a077..dddabd2 100644
--- a/src/net/udp/udp_listen_socket_unittest.cc
+++ b/src/net/udp/udp_listen_socket_unittest.cc
@@ -71,6 +71,7 @@
   // Create another socket.
   SocketDescriptor cs = SbSocketCreate(sb_address.type, kSbSocketProtocolUdp);
   SbSocketSendTo(cs, data, size, &sb_address);
+  SbSocketDestroy(cs);
 #else   // defined(OS_STARBOARD)
   // Get the watching address
   SockaddrStorage bind_addr;
diff --git a/src/net/url_request/url_fetcher_core.cc b/src/net/url_request/url_fetcher_core.cc
index 2097903..82976da 100644
--- a/src/net/url_request/url_fetcher_core.cc
+++ b/src/net/url_request/url_fetcher_core.cc
@@ -5,6 +5,7 @@
 #include "net/url_request/url_fetcher_core.h"
 
 #include "base/bind.h"
+#include "base/debug/trace_event.h"
 #include "base/file_util_proxy.h"
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
@@ -12,6 +13,7 @@
 #include "base/stl_util.h"
 #include "base/thread_task_runner_handle.h"
 #include "base/tracked_objects.h"
+#include "nb/memory_scope.h"
 #include "net/base/io_buffer.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
@@ -41,6 +43,7 @@
 URLFetcherCore::Registry::~Registry() {}
 
 void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(!ContainsKey(fetchers_, core));
   fetchers_.insert(core);
 }
@@ -74,6 +77,7 @@
 
 void URLFetcherCore::FileWriter::CreateFileAtPath(
     const FilePath& file_path) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
   DCHECK(file_task_runner_.get());
   base::FileUtilProxy::CreateOrOpen(
@@ -86,6 +90,7 @@
 }
 
 void URLFetcherCore::FileWriter::CreateTempFile() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
   DCHECK(file_task_runner_.get());
   base::FileUtilProxy::CreateTemporary(
@@ -96,6 +101,7 @@
 }
 
 void URLFetcherCore::FileWriter::WriteBuffer(int num_bytes) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   // Start writing to the file by setting the initial state
@@ -109,6 +115,7 @@
 void URLFetcherCore::FileWriter::ContinueWrite(
     base::PlatformFileError error_code,
     int bytes_written) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   if (file_handle_ == base::kInvalidPlatformFileValue) {
@@ -153,6 +160,7 @@
 }
 
 void URLFetcherCore::FileWriter::DisownFile() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   // Disowning is done by the delegate's OnURLFetchComplete method.
@@ -164,6 +172,7 @@
 }
 
 void URLFetcherCore::FileWriter::CloseFileAndCompleteRequest() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   if (file_handle_ != base::kInvalidPlatformFileValue) {
@@ -176,6 +185,7 @@
 }
 
 void URLFetcherCore::FileWriter::CloseAndDeleteFile() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   if (file_handle_ == base::kInvalidPlatformFileValue) {
@@ -192,6 +202,7 @@
 
 void URLFetcherCore::FileWriter::DeleteFile(
     base::PlatformFileError error_code) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
   if (file_path_.empty())
     return;
@@ -208,6 +219,7 @@
     base::PlatformFileError error_code,
     base::PassPlatformFile file_handle,
     bool created) {
+  TRACK_MEMORY_SCOPE("Network");
   DidCreateFileInternal(file_path, error_code, file_handle);
 }
 
@@ -215,6 +227,7 @@
     base::PlatformFileError error_code,
     base::PassPlatformFile file_handle,
     const FilePath& file_path) {
+  TRACK_MEMORY_SCOPE("Network");
   DidCreateFileInternal(file_path, error_code, file_handle);
 }
 
@@ -222,6 +235,7 @@
     const FilePath& file_path,
     base::PlatformFileError error_code,
     base::PassPlatformFile file_handle) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   if (base::PLATFORM_FILE_OK != error_code) {
@@ -244,6 +258,7 @@
 
 void URLFetcherCore::FileWriter::DidCloseFile(
     base::PlatformFileError error_code) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
 
   if (base::PLATFORM_FILE_OK != error_code) {
@@ -299,6 +314,7 @@
 }
 
 void URLFetcherCore::Start() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(delegate_task_runner_);
   DCHECK(request_context_getter_) << "We need an URLRequestContext!";
   if (network_task_runner_) {
@@ -314,6 +330,7 @@
 }
 
 void URLFetcherCore::Stop() {
+  TRACK_MEMORY_SCOPE("Network");
   if (delegate_task_runner_)  // May be NULL in tests.
     DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
@@ -522,6 +539,7 @@
 
 bool URLFetcherCore::GetResponseAsFilePath(bool take_ownership,
                                            FilePath* out_response_path) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
   const bool destination_is_file =
       response_destination_ == URLFetcherCore::TEMP_FILE ||
@@ -542,6 +560,7 @@
 void URLFetcherCore::OnReceivedRedirect(URLRequest* request,
                                         const GURL& new_url,
                                         bool* defer_redirect) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_EQ(request, request_.get());
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (stop_on_redirect_) {
@@ -555,6 +574,7 @@
 }
 
 void URLFetcherCore::OnResponseStarted(URLRequest* request) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK_EQ(request, request_.get());
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (request_->status().is_success()) {
@@ -592,6 +612,7 @@
 
 void URLFetcherCore::OnReadCompleted(URLRequest* request,
                                      int bytes_read) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(request == request_);
   DCHECK(network_task_runner_->BelongsToCurrentThread());
 #if !defined(COBALT)
@@ -611,6 +632,10 @@
     if (!request_->status().is_success() || bytes_read <= 0)
       break;
 
+    if (current_response_bytes_ == 0) {
+      TRACE_EVENT_ASYNC_STEP0("net::url_request", "URLFetcher", this,
+                              "Fetch Content");
+    }
     current_response_bytes_ += bytes_read;
     InformDelegateDownloadDataIfNecessary(bytes_read);
 
@@ -709,7 +734,10 @@
 }
 
 void URLFetcherCore::StartURLRequest() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
+  TRACE_EVENT_ASYNC_STEP0("net::url_request", "URLFetcher", this,
+                          "Waiting For Data");
 
   if (was_cancelled_) {
     // Since StartURLRequest() is posted as a *delayed* task, it may
@@ -801,6 +829,9 @@
 }
 
 void URLFetcherCore::StartURLRequestWhenAppropriate() {
+  TRACK_MEMORY_SCOPE("Network");
+  TRACE_EVENT_ASYNC_BEGIN1("net::url_request", "URLFetcher", this, "url",
+                           original_url_.path());
   DCHECK(network_task_runner_->BelongsToCurrentThread());
 
   if (was_cancelled_)
@@ -832,11 +863,13 @@
 }
 
 void URLFetcherCore::CancelURLRequest() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
 
   if (request_.get()) {
     request_->Cancel();
     ReleaseRequest();
+    TRACE_EVENT_ASYNC_END0("net::url_request", "URLFetcher", this);
   }
   // Release the reference to the request context. There could be multiple
   // references to URLFetcher::Core at this point so it may take a while to
@@ -852,6 +885,9 @@
 
 void URLFetcherCore::OnCompletedURLRequest(
     base::TimeDelta backoff_delay) {
+  TRACK_MEMORY_SCOPE("Network");
+  TRACE_EVENT_ASYNC_END1("net::url_request", "URLFetcher", this, "url",
+                         original_url_.path());
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
   // Save the status and backoff_delay so that delegates can read it.
@@ -862,12 +898,14 @@
 }
 
 void URLFetcherCore::InformDelegateFetchIsComplete() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
   if (delegate_)
     delegate_->OnURLFetchComplete(fetcher_);
 }
 
 void URLFetcherCore::NotifyMalformedContent() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (url_throttler_entry_ != NULL) {
     int status_code = response_code_;
@@ -884,6 +922,7 @@
 }
 
 void URLFetcherCore::RetryOrCompleteUrlFetch() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   base::TimeDelta backoff_delay;
 
@@ -953,6 +992,7 @@
 }
 
 base::TimeTicks URLFetcherCore::GetBackoffReleaseTime() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
 
   if (original_url_throttler_entry_) {
@@ -974,6 +1014,7 @@
 
 void URLFetcherCore::CompleteAddingUploadDataChunk(
     const std::string& content, bool is_last_chunk) {
+  TRACK_MEMORY_SCOPE("Network");
   if (was_cancelled_) {
     // Since CompleteAddingUploadDataChunk() is posted as a *delayed* task, it
     // may run after the URLFetcher was already stopped.
@@ -991,6 +1032,7 @@
 // Return false if the write is pending, and the next read will
 // be done later.
 bool URLFetcherCore::WriteBuffer(int num_bytes) {
+  TRACK_MEMORY_SCOPE("Network");
   bool write_complete = false;
   switch (response_destination_) {
     case STRING:
@@ -1019,6 +1061,7 @@
 }
 
 void URLFetcherCore::ReadResponse() {
+  TRACK_MEMORY_SCOPE("Network");
   // Some servers may treat HEAD requests as GET requests.  To free up the
   // network connection as soon as possible, signal that the request has
   // completed immediately, without trying to read any data back (all we care
@@ -1057,6 +1100,7 @@
 #endif  // defined(COBALT)
 
 void URLFetcherCore::InformDelegateUploadProgress() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (request_.get()) {
     int64 current = request_->GetUploadProgress().position();
@@ -1076,12 +1120,14 @@
 
 void URLFetcherCore::InformDelegateUploadProgressInDelegateThread(
     int64 current, int64 total) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
   if (delegate_)
     delegate_->OnURLFetchUploadProgress(fetcher_, current, total);
 }
 
 void URLFetcherCore::InformDelegateDownloadDataIfNecessary(int bytes_read) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (delegate_ && delegate_->ShouldSendDownloadData() && bytes_read != 0) {
     if (!download_data_cache_) {
@@ -1102,6 +1148,7 @@
 }
 
 void URLFetcherCore::InformDelegateDownloadData() {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(network_task_runner_->BelongsToCurrentThread());
   if (delegate_ && delegate_->ShouldSendDownloadData() &&
       download_data_cache_) {
@@ -1114,6 +1161,7 @@
 
 void URLFetcherCore::InformDelegateDownloadDataInDelegateThread(
     scoped_ptr<std::string> download_data) {
+  TRACK_MEMORY_SCOPE("Network");
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
   if (delegate_) {
     delegate_->OnURLFetchDownloadData(fetcher_, download_data.Pass());
diff --git a/src/net/url_request/url_request_data_job.cc b/src/net/url_request/url_request_data_job.cc
index 5dffe32..e0c0cba 100644
--- a/src/net/url_request/url_request_data_job.cc
+++ b/src/net/url_request/url_request_data_job.cc
@@ -6,6 +6,7 @@
 
 #include "net/url_request/url_request_data_job.h"
 
+#include "nb/memory_scope.h"
 #include "net/base/data_url.h"
 #include "net/base/net_errors.h"
 
@@ -27,6 +28,7 @@
                                std::string* charset,
                                std::string* data,
                                const CompletionCallback& callback) const {
+  TRACK_MEMORY_SCOPE("Network");
   // Check if data URL is valid. If not, don't bother to try to extract data.
   // Otherwise, parse the data from the data URL.
   const GURL& url = request_->url();
diff --git a/src/starboard/README.md b/src/starboard/README.md
index 3d35ee3..5e81034 100644
--- a/src/starboard/README.md
+++ b/src/starboard/README.md
@@ -160,7 +160,7 @@
 
 In order to use a new platform configuration in a build, you need to ensure that
 you have a `gyp_configuration.py`, `gyp_configuration.gypi`, and
-`starboard_platform.gypi` in their own directory for each binary variant, plus
+`starboard_platform.gyp` in their own directory for each binary variant, plus
 the header files `configuration_public.h`, `atomic_public.h`, and
 `thread_types_public.h`. `gyp_cobalt` will scan your directories for these
 files, and then calculate a port name based on the directories between
diff --git a/src/starboard/atomic.h b/src/starboard/atomic.h
index bdc3ad1..243c4ea 100644
--- a/src/starboard/atomic.h
+++ b/src/starboard/atomic.h
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Module Overview: Starboard Atomic API
+//
 // Defines a set of atomic integer operations that can be used as lightweight
-// synchronization or as building blocks for heavier synchronization
-// primitives. Their use is very subtle and requires detailed understanding of
-// the behavior of supported architectures, so their direct use is not
-// recommended except when rigorously deemed absolutely necessary for
-// performance reasons.
+// synchronization or as building blocks for heavier synchronization primitives.
+// Their use is very subtle and requires detailed understanding of the behavior
+// of supported architectures, so their direct use is not recommended except
+// when rigorously deemed absolutely necessary for performance reasons.
 
 #ifndef STARBOARD_ATOMIC_H_
 #define STARBOARD_ATOMIC_H_
@@ -41,23 +42,23 @@
 // Always return the old value of "*ptr"
 //
 // This routine implies no memory barriers.
-SbAtomic32 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr,
-                                            SbAtomic32 old_value,
-                                            SbAtomic32 new_value);
+static SbAtomic32 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr,
+                                                   SbAtomic32 old_value,
+                                                   SbAtomic32 new_value);
 
 // Atomically store new_value into *ptr, returning the previous value held in
 // *ptr.  This routine implies no memory barriers.
-SbAtomic32 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr,
-                                      SbAtomic32 new_value);
+static SbAtomic32 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr,
+                                             SbAtomic32 new_value);
 
 // Atomically increment *ptr by "increment".  Returns the new value of
 // *ptr with the increment applied.  This routine implies no memory barriers.
-SbAtomic32 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr,
-                                       SbAtomic32 increment);
+static SbAtomic32 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr,
+                                              SbAtomic32 increment);
 
 // Same as SbAtomicNoBarrier_Increment, but with a memory barrier.
-SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
-                                     SbAtomic32 increment);
+static SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
+                                            SbAtomic32 increment);
 
 // These following lower-level operations are typically useful only to people
 // implementing higher-level synchronization operations like spinlocks, mutexes,
@@ -68,54 +69,55 @@
 // after the operation.  "Barrier" operations have both "Acquire" and "Release"
 // semantics.  A SbAtomicMemoryBarrier() has "Barrier" semantics, but does no
 // memory access.
-SbAtomic32 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr,
-                                          SbAtomic32 old_value,
-                                          SbAtomic32 new_value);
-SbAtomic32 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr,
-                                          SbAtomic32 old_value,
-                                          SbAtomic32 new_value);
+static SbAtomic32 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr,
+                                                 SbAtomic32 old_value,
+                                                 SbAtomic32 new_value);
+static SbAtomic32 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr,
+                                                 SbAtomic32 old_value,
+                                                 SbAtomic32 new_value);
 
-void SbAtomicMemoryBarrier();
-void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
-void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
-void SbAtomicRelease_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
+static void SbAtomicMemoryBarrier();
+static void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
+static void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
+static void SbAtomicRelease_Store(volatile SbAtomic32* ptr, SbAtomic32 value);
 
-SbAtomic32 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr);
-SbAtomic32 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr);
-SbAtomic32 SbAtomicRelease_Load(volatile const SbAtomic32* ptr);
+static SbAtomic32 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr);
+static SbAtomic32 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr);
+static SbAtomic32 SbAtomicRelease_Load(volatile const SbAtomic32* ptr);
 
 // 64-bit atomic operations (only available on 64-bit processors).
 #if SB_HAS(64_BIT_ATOMICS)
 typedef int64_t SbAtomic64;
 
-SbAtomic64 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr,
-                                              SbAtomic64 old_value,
-                                              SbAtomic64 new_value);
-SbAtomic64 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr,
-                                        SbAtomic64 new_value);
-SbAtomic64 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr,
-                                         SbAtomic64 increment);
-SbAtomic64 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr,
-                                       SbAtomic64 increment);
-SbAtomic64 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr,
-                                            SbAtomic64 old_value,
-                                            SbAtomic64 new_value);
-SbAtomic64 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr,
-                                            SbAtomic64 old_value,
-                                            SbAtomic64 new_value);
-void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr, SbAtomic64 value);
-void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, SbAtomic64 value);
-void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, SbAtomic64 value);
-SbAtomic64 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr);
-SbAtomic64 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr);
-SbAtomic64 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr);
+static SbAtomic64 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr,
+                                                     SbAtomic64 old_value,
+                                                     SbAtomic64 new_value);
+static SbAtomic64 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr,
+                                               SbAtomic64 new_value);
+static SbAtomic64 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr,
+                                                SbAtomic64 increment);
+static SbAtomic64 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr,
+                                              SbAtomic64 increment);
+static SbAtomic64 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr,
+                                                   SbAtomic64 old_value,
+                                                   SbAtomic64 new_value);
+static SbAtomic64 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr,
+                                                   SbAtomic64 old_value,
+                                                   SbAtomic64 new_value);
+static void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr,
+                                      SbAtomic64 value);
+static void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, SbAtomic64 value);
+static void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, SbAtomic64 value);
+static SbAtomic64 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr);
+static SbAtomic64 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr);
+static SbAtomic64 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr);
 #endif  // SB_HAS(64_BIT_ATOMICS)
 
 // Pointer-sized atomic operations. Forwards to either 32-bit or 64-bit
 // functions as appropriate.
 typedef intptr_t SbAtomicPtr;
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicNoBarrier_CompareAndSwapPtr(volatile SbAtomicPtr* ptr,
                                     SbAtomicPtr old_value,
                                     SbAtomicPtr new_value) {
@@ -126,7 +128,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicNoBarrier_ExchangePtr(volatile SbAtomicPtr* ptr,
                               SbAtomicPtr new_value) {
 #if SB_HAS(64_BIT_POINTERS)
@@ -136,7 +138,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicNoBarrier_IncrementPtr(volatile SbAtomicPtr* ptr,
                                SbAtomicPtr increment) {
 #if SB_HAS(64_BIT_POINTERS)
@@ -146,7 +148,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicBarrier_IncrementPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr increment) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicBarrier_Increment64(ptr, increment);
@@ -155,7 +157,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicAcquire_CompareAndSwapPtr(volatile SbAtomicPtr* ptr,
                                   SbAtomicPtr old_value,
                                   SbAtomicPtr new_value) {
@@ -166,7 +168,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicRelease_CompareAndSwapPtr(volatile SbAtomicPtr* ptr,
                                   SbAtomicPtr old_value,
                                   SbAtomicPtr new_value) {
@@ -177,8 +179,8 @@
 #endif
 }
 
-SB_C_FORCE_INLINE void SbAtomicNoBarrier_StorePtr(volatile SbAtomicPtr* ptr,
-                                                  SbAtomicPtr value) {
+static SB_C_FORCE_INLINE void
+SbAtomicNoBarrier_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicNoBarrier_Store64(ptr, value);
 #else
@@ -186,8 +188,8 @@
 #endif
 }
 
-SB_C_FORCE_INLINE void SbAtomicAcquire_StorePtr(volatile SbAtomicPtr* ptr,
-                                                SbAtomicPtr value) {
+static SB_C_FORCE_INLINE void
+SbAtomicAcquire_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicAcquire_Store64(ptr, value);
 #else
@@ -195,8 +197,8 @@
 #endif
 }
 
-SB_C_FORCE_INLINE void SbAtomicRelease_StorePtr(volatile SbAtomicPtr* ptr,
-                                                SbAtomicPtr value) {
+static SB_C_FORCE_INLINE void
+SbAtomicRelease_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicRelease_Store64(ptr, value);
 #else
@@ -204,7 +206,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicNoBarrier_LoadPtr(volatile const SbAtomicPtr* ptr) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicNoBarrier_Load64(ptr);
@@ -213,7 +215,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicAcquire_LoadPtr(volatile const SbAtomicPtr* ptr) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicAcquire_Load64(ptr);
@@ -222,7 +224,7 @@
 #endif
 }
 
-SB_C_FORCE_INLINE SbAtomicPtr
+static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicRelease_LoadPtr(volatile const SbAtomicPtr* ptr) {
 #if SB_HAS(64_BIT_POINTERS)
   return SbAtomicRelease_Load64(ptr);
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/blitter.h b/src/starboard/blitter.h
index d183475..3ed8258 100644
--- a/src/starboard/blitter.h
+++ b/src/starboard/blitter.h
@@ -12,14 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Starboard Blitter API.  The Blitter API provides support for issuing simple
-// blit-style draw commands to either an offscreen surface or to a Starboard
-// SbWindow object.  This API is designed to allow implementations make use
-// of GPU hardware acceleration, if it is available.  Draw commands exist for
-// solid-color rectangles and rasterization/blitting of rectangular images onto
+// Module Overview: Starboard Blitter API
+//
+// The Blitter API provides support for issuing simple blit-style draw
+// commands to either an offscreen surface or to a Starboard SbWindow object.
+// Blitter is jargon that means "BLock Transfer," which might be abbreviated
+// as BLT and, hence, the word "blit."
+//
+// This API is designed to allow implementations make use of GPU hardware
+// acceleration, if it is available.  Draw commands exist for solid-color
+// rectangles and rasterization/blitting of rectangular images onto
 // rectangular target patches.
-
-// Threading Concerns:
+//
+// #### Threading Concerns
+//
 // Note that in general the Blitter API is not thread safe, except for all
 // SbBlitterDevice-related functions.  All functions that are not required to
 // internally ensure any thread safety guarantees are prefaced with a comment
@@ -97,12 +103,16 @@
 #define kSbBlitterInvalidSurface ((SbBlitterSurface)NULL)
 
 // SbBlitterContext objects represent a stateful communications channel with
-// a device.  All state changes and draw calls will be made through a specific
-// SbBlitterContext object.  Every draw call made on a SbBlitterContext will
-// be submitted to the device with the SbBlitterContext's current state applied
-// to it.  Draw calls may be submitted to the device as they are made on the
-// SbBlitterContext, however they are not guaranteed to be submitted until
-// the SbBlitterContext object is flushed.
+// a device.  All state changes and draw calls are made through a specific
+// SbBlitterContext object.  Every draw call made on a SbBlitterContext is
+// submitted to the device with the SbBlitterContext's current state applied
+// to it.
+//
+// Draw calls may be submitted to the device as they are made on the
+// SbBlitterContext. However, they are not guaranteed to be submitted until
+// the SbBlitterContext object is flushed. That is, until you call
+// SbBlitterFlushContext, you are not guaranteed that any API calls you have
+// made have been received or acted on by the graphics device.
 typedef struct SbBlitterContextPrivate SbBlitterContextPrivate;
 typedef SbBlitterContextPrivate* SbBlitterContext;
 #define kSbBlitterInvalidContext ((SbBlitterContext)NULL)
@@ -283,66 +293,84 @@
   return context != kSbBlitterInvalidContext;
 }
 
-// Creates and returns a SbBlitterDevice object based on the Blitter API
-// implementation's decision of which device should be default.  The
-// SbBlitterDevice object represents a connection to a device (like a GPU).  On
-// many platforms there is always one single obvious choice for a device to use,
-// and that is the one that this function will return.  For example, if this is
-// called on a platform that has a single GPU, a device representing that GPU
-// should be returned by this call.  On a platform that has no GPU, a device
-// representing a software CPU implementation may be returned.  Only one
-// default device can exist within a process at a time.
-// This function is thread safe.
+// Creates and returns an SbBlitterDevice based on the Blitter API
+// implementation's decision of which device should be the default. The
+// returned SbBlitterDevice represents a connection to a device (like a GPU).
+//
+// On many platforms there is always one single obvious choice for a device
+// to use, and that is the one that this function will return. For example,
+// if this is called on a platform that has a single GPU, this call should
+// return an object that represents that GPU. On a platform that has no GPU,
+// an object representing a software CPU implementation may be returned.
+//
+// Only one default device can exist within a process at a time.
+// This function is thread-safe.
 // Returns kSbBlitterInvalidDevice on failure.
 SB_EXPORT SbBlitterDevice SbBlitterCreateDefaultDevice();
 
 // Destroys |device|, cleaning up all resources associated with it.
-// Returns whether the destruction succeeded.
-// This function is thread safe, though of course it should not be called if
-// |device| is still being accessed elsewhere.
+// This function is thread-safe, but it should not be called if |device| is
+// still being accessed elsewhere.
+//
+// The return value indicates whether the destruction succeeded.
+//
+// |device|: The SbBlitterDevice object to be destroyed.
 SB_EXPORT bool SbBlitterDestroyDevice(SbBlitterDevice device);
 
-// Creates and returns a SbBlitterSwapChain object that can be used to send
-// graphics to the display.  By calling this function, |device| will be linked
-// to |window|'s output and drawing to the returned swap chain will result in
-// |device| being used to render to |window|.  kSbBlitterInvalidSwapChain is
-// returned on failure.
+// Creates and returns an SbBlitterSwapChain that can then be used to send
+// graphics to the display. This function links |device| to |window|'s output,
+// and drawing to the returned swap chain will result in |device| being used
+// to render to |window|.
+//
 // This function must be called from the thread that called SbWindowCreate()
 // to create |window|.
+//
+// Returns kSbBlitterInvalidSwapChain on failure.
 SB_EXPORT SbBlitterSwapChain
 SbBlitterCreateSwapChainFromWindow(SbBlitterDevice device, SbWindow window);
 
 // Destroys |swap_chain|, cleaning up all resources associated with it.
-// This function must be called on the same thread that called
-// SbBlitterCreateSwapChainFromWindow().
-// This function is not thread safe.
-// Returns the destruction succeeded.
+// This function is not thread-safe and must be called on the same thread
+// that called SbBlitterCreateSwapChainFromWindow().
+//
+// The return value indicates whether the destruction succeeded.
+//
+// |swap_chain|: The SbBlitterSwapChain to be destroyed.
 SB_EXPORT bool SbBlitterDestroySwapChain(SbBlitterSwapChain swap_chain);
 
 // Returns the |SbBlitterRenderTarget| object that is owned by |swap_chain|.
 // The returned object can be used to provide a target to blitter draw calls
-// wishing to draw directly to the display buffer.
-// This function is not thread safe.
-// kSbBlitterInvalidRenderTarget is returned on failure.
+// that draw directly to the display buffer. This function is not thread-safe.
+//
+// Returns kSbBlitterInvalidRenderTarget on failure.
+//
+// |swap_chain|: The SbBlitterSwapChain for which the target object is being
+// retrieved.
 SB_EXPORT SbBlitterRenderTarget
 SbBlitterGetRenderTargetFromSwapChain(SbBlitterSwapChain swap_chain);
 
-// Returns whether |device| supports calls to SbBlitterCreatePixelData()
-// with |pixel_format|.
-// This function is thread safe.
+// Indicates whether |device| supports calls to SbBlitterCreatePixelData
+// with the specified |pixel_format|. This function is thread-safe.
+//
+// |device|: The device for which compatibility is being checked.
+// |pixel_format|: The SbBlitterPixelDataFormat for which compatibility is
+// being checked.
 SB_EXPORT bool SbBlitterIsPixelFormatSupportedByPixelData(
     SbBlitterDevice device,
     SbBlitterPixelDataFormat pixel_format);
 
-// Allocates a SbBlitterPixelData object through |device| with |width|, |height|
-// and |pixel_format|.  |pixel_format| must be supported by |device| (see
-// SbBlitterIsPixelFormatSupportedByPixelData()).  Calling this function will
-// result in the allocation of CPU-accessible (though perhaps
-// blitter-device-resident) memory to store pixel data of the requested
-// size/format. A SbBlitterPixelData object should either eventually be passed
-// into a call to SbBlitterCreateSurfaceFromPixelData(), or passed into a call
-// to SbBlitterDestroyPixelData().
-// This function is thread safe.
+// Allocates an SbBlitterPixelData object through |device| with |width|,
+// |height| and |pixel_format|. |pixel_format| must be supported by |device|
+// (see SbBlitterIsPixelFormatSupportedByPixelData()). This function is
+// thread-safe.
+//
+// Calling this function results in the allocation of CPU-accessible
+// (though perhaps blitter-device-resident) memory to store pixel data
+// of the requested size/format. An SbBlitterPixelData object should
+// eventually be passed either into a call to
+// SbBlitterCreateSurfaceFromPixelData() or into a call to
+// SbBlitterDestroyPixelData().
+//
 // Returns kSbBlitterInvalidPixelData upon failure.
 SB_EXPORT SbBlitterPixelData
 SbBlitterCreatePixelData(SbBlitterDevice device,
@@ -350,57 +378,74 @@
                          int height,
                          SbBlitterPixelDataFormat pixel_format);
 
-// Destroys the |pixel_data| object.  Note that
-// if SbBlitterCreateSurfaceFromPixelData() has been called on |pixel_data|
-// before, this function does not need to be and should not be called.
-// This function is thread safe.
-// Returns whether the destruction succeeded.
+// Destroys |pixel_data|. Note that this function does not need to be called
+// and should not be called if SbBlitterCreateSurfaceFromPixelData() has been
+// called on |pixel_data| before. This function is thread-safe.
+//
+// The return value indicates whether the destruction succeeded.
+//
+// |pixel_data|: The object to be destroyed.
 SB_EXPORT bool SbBlitterDestroyPixelData(SbBlitterPixelData pixel_data);
 
-// Getter method to return the pitch (in bytes) for |pixel_data|.  This
-// indicates the number of bytes per row of pixel data in the image.  -1 is
-// returned in case of an error.
-// This function is not thread safe.
+// Retrieves the pitch (in bytes) for |pixel_data|. This indicates the number of
+// bytes per row of pixel data in the image. This function is not thread-safe.
+//
+// Returns |-1| in the event of an error.
+//
+// |pixel_data|: The object for which you are retrieving the pitch.
 SB_EXPORT int SbBlitterGetPixelDataPitchInBytes(SbBlitterPixelData pixel_data);
 
-// Getter method to return a CPU-accessible pointer to the pixel data
-// represented by |pixel_data|.  This pixel data can be modified by the CPU
-// in order to initialize it on the CPU before calling
-// SbBlitterCreateSurfaceFromPixelData().  Note that the pointe returned here
-// is valid as long as |pixel_data| is valid, i.e. until either
+// Retrieves a CPU-accessible pointer to the pixel data represented by
+// |pixel_data|. This pixel data can be modified by the CPU to initialize it
+// on the CPU before calling SbBlitterCreateSurfaceFromPixelData().
+//
+// Note that the pointer returned here is valid as long as |pixel_data| is
+// valid, which means it is valid until either
 // SbBlitterCreateSurfaceFromPixelData() or SbBlitterDestroyPixelData() is
 // called.
-// This function is not thread safe.
-// If there is an error, NULL is returned.
+//
+// This function is not thread-safe.
+//
+// Returns |NULL| in the event of an error.
 SB_EXPORT void* SbBlitterGetPixelDataPointer(SbBlitterPixelData pixel_data);
 
-// Creates a SbBlitterSurface object on |device| (which must match the device
-// used to create the input SbBlitterPixelData object, |pixel_format|).
-// This function will destroy the input |pixel_data| object and so
-// |pixel_data| should not be accessed again after this function is called.
-// The returned surface cannot be used as a render target (e.g. calling
+// Creates an SbBlitterSurface object on |device|. Note that |device| must
+// match the device that was used to create the SbBlitterPixelData object
+// provided via the |pixel_data| parameter.
+//
+// This function also destroys the input |pixel_data| object. As a result,
+// |pixel_data| should not be accessed again after a call to this function.
+//
+// The returned object cannot be used as a render target (e.g. calling
 // SbBlitterGetRenderTargetFromSurface() on it will return
 // SbBlitterInvalidRenderTarget).
-// This function is thread safe with respect to |device|, however
-// |pixel_data| should not be modified on another thread while this function
-// is called.
-// kSbBlitterInvalidSurface is returned if there was an error.
+//
+// This function is thread-safe with respect to |device|, but |pixel_data|
+// should not be modified on another thread while this function is called.
+//
+// Returns kSbBlitterInvalidSurface in the event of an error.
 SB_EXPORT SbBlitterSurface
 SbBlitterCreateSurfaceFromPixelData(SbBlitterDevice device,
                                     SbBlitterPixelData pixel_data);
 
-// Returns whether the |device| supports calls to
-// SbBlitterCreateRenderTargetSurface() with |pixel_format|.
-// This function is thread safe.
+// Indicates whether the |device| supports calls to
+// SbBlitterCreateRenderTargetSurface() with |surface_format|.
+//
+// This function is thread-safe.
+//
+// |device|: The device being checked for compatibility.
+// |surface_format|: The surface format being checked for compatibility.
 SB_EXPORT bool SbBlitterIsSurfaceFormatSupportedByRenderTargetSurface(
     SbBlitterDevice device,
     SbBlitterSurfaceFormat surface_format);
 
 // Creates a new surface with undefined pixel data on |device| with the
-// specified |width|, |height| and |pixel_format|.  One can set the pixel data
+// specified |width|, |height| and |surface_format|. One can set the pixel data
 // on the resulting surface by getting its associated SbBlitterRenderTarget
-// object by calling SbBlitterGetRenderTargetFromSurface().
-// This function is thread safe.
+// object and then calling SbBlitterGetRenderTargetFromSurface().
+//
+// This function is thread-safe.
+//
 // Returns kSbBlitterInvalidSurface upon failure.
 SB_EXPORT SbBlitterSurface
 SbBlitterCreateRenderTargetSurface(SbBlitterDevice device,
@@ -408,195 +453,277 @@
                                    int height,
                                    SbBlitterSurfaceFormat surface_format);
 
-// Destroys |surface|, cleaning up all resources associated with it.
+// Destroys the |surface| object, cleaning up all resources associated with it.
 // This function is not thread safe.
-// Returns whether the destruction succeeded.
+//
+// The return value indicates whether the destruction succeeded.
+//
+// |surface|: The object to be destroyed.
 SB_EXPORT bool SbBlitterDestroySurface(SbBlitterSurface surface);
 
 // Returns the SbBlitterRenderTarget object owned by |surface|.  The returned
 // object can be used as a target for draw calls.
-// This function is not thread safe.
-// Returns kSbBlitterInvalidRenderTarget if |surface| is not able to provide a
-// render target, or on any other error.
+//
+// This function returns kSbBlitterInvalidRenderTarget if |surface| is not
+// able to provide a render target or on any other error.
+//
+// This function is not thread-safe.
 SB_EXPORT SbBlitterRenderTarget
 SbBlitterGetRenderTargetFromSurface(SbBlitterSurface surface);
 
-// Returns a SbBlitterSurfaceInfo structure describing immutable parameters of
-// |surface|, such as width, height and pixel format.  The results will be
-// set on the output parameter |surface_info| which cannot be NULL.
-// This function is not thread safe.
-// Returns whether the information was retrieved successfully.
+// Retrieves an SbBlitterSurfaceInfo structure, which describes immutable
+// parameters of the |surface|, such as its width, height and pixel format.
+// The results are set on the output parameter |surface_info|, which cannot
+// be NULL.
+//
+// The return value indicates whether the information was retrieved
+// successfully.
+//
+// This function is not thread-safe.
 SB_EXPORT bool SbBlitterGetSurfaceInfo(SbBlitterSurface surface,
                                        SbBlitterSurfaceInfo* surface_info);
 
-// Returns whether the combination of parameters (|surface|, |pixel_format|) are
-// valid for calls to SbBlitterDownloadSurfacePixels().
-// This function is not thread safe.
+// Indicates whether the combination of parameter values is valid for calls
+// to SbBlitterDownloadSurfacePixels().
+//
+// This function is not thread-safe.
+//
+// |surface|: The surface being checked.
+// |pixel_format|: The pixel format that would be used on the surface.
 SB_EXPORT bool SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels(
     SbBlitterSurface surface,
     SbBlitterPixelDataFormat pixel_format);
 
 // Downloads |surface| pixel data into CPU memory pointed to by
 // |out_pixel_data|, formatted according to the requested |pixel_format| and
-// the requested |pitch_in_bytes|.  Thus, |out_pixel_data| must point to a
-// region of memory with a size of surface_height * |pitch_in_bytes| *
-// SbBlitterBytesPerPixelForFormat(pixel_format) bytes.  The function
-// SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels() can be called first
-// to check that your requested |pixel_format| is valid for |surface|.  When
-// this function is called, it will first wait for all previously flushed
+// the requested |pitch_in_bytes|. Before calling this function, you can call
+// SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels() to confirm that
+// |pixel_format| is, in fact, valid for |surface|.
+//
+// When this function is called, it first waits for all previously flushed
 // graphics commands to be executed by the device before downloading the data.
 // Since this function waits for the pipeline to empty, it should be used
-// sparingly, such as within in debug or test environments.  The returned
-// alpha format will be premultiplied.
-// This function is not thread safe.
-// Returns whether the pixel data was downloaded successfully or not.
+// sparingly, such as within in debug or test environments.
+//
+// The return value indicates whether the pixel data was downloaded
+// successfully.
+//
+// The returned alpha format is premultiplied.
+//
+// This function is not thread-safe.
+//
+// |out_pixel_data|: A pointer to a region of memory with a size of
+//                   surface_height * |pitch_in_bytes| bytes.
 SB_EXPORT bool SbBlitterDownloadSurfacePixels(
     SbBlitterSurface surface,
     SbBlitterPixelDataFormat pixel_format,
     int pitch_in_bytes,
     void* out_pixel_data);
 
-// Flips |swap_chain|, making the buffer that was previously accessible to
-// draw commands via SbBlitterGetRenderTargetFromSwapChain() now visible on the
-// display, while another buffer in an initially undefined state is setup as the
-// draw command target.  Note that you do not need to call
+// Flips the |swap_chain| by making the buffer previously accessible to
+// draw commands via SbBlitterGetRenderTargetFromSwapChain() visible on the
+// display, while another buffer in an initially undefined state is set up
+// as the new draw command target. Note that you do not need to call
 // SbBlitterGetRenderTargetFromSwapChain() again after flipping, the swap
-// chain's render target will always refer to its current back buffer.  This
-// function will stall the calling thread until the next vertical refresh.
-// Note that to ensure consistency with the Starboard Player API when rendering
-// punch-out video, calls to SbPlayerSetBounds() will not take effect until
-// this method is called.
-// This function is not thread safe.
-// Returns whether the flip succeeded.
+// chain's render target always refers to its current back buffer.
+//
+// This function stalls the calling thread until the next vertical refresh.
+// In addition, to ensure consistency with the Starboard Player API when
+// rendering punch-out video, calls to SbPlayerSetBounds() do not take effect
+// until this method is called.
+//
+// The return value indicates whether the flip succeeded.
+//
+// This function is not thread-safe.
+//
+// |swap_chain|: The SbBlitterSwapChain to be flipped.
 SB_EXPORT bool SbBlitterFlipSwapChain(SbBlitterSwapChain swap_chain);
 
-// Returns the maximum number of contexts that |device| can support in parallel.
-// In many cases, devices support only a single context.  If
-// SbBlitterCreateContext() has been used to create the maximum number of
-// contexts, all subsequent calls to SbBlitterCreateContext() will fail.
-// This function is thread safe.
-// This function returns -1 upon failure.
+// Returns the maximum number of contexts that |device| can support in
+// parallel. Note that devices often support only a single context.
+//
+// This function is thread-safe.
+//
+// This function returns |-1| upon failure.
+//
+// |device|: The SbBlitterDevice for which the maximum number of contexts is
+// returned.
 SB_EXPORT int SbBlitterGetMaxContexts(SbBlitterDevice device);
 
-// Creates a SbBlitterContext object on the specified |device|.  The returned
-// context can be used to setup draw state and issue draw calls.  Note that
-// there is a limit on the number of contexts that can exist at the same time
-// (in many cases this is 1), and this can be queried by calling
-// SbBlitterGetMaxContexts().  SbBlitterContext objects keep track of draw
-// state between a series of draw calls.  Please refer to the documentation
-// around the definition of SbBlitterContext for more information about
-// contexts.
-// This function is thread safe.
-// This function returns kSbBlitterInvalidContext upon failure.
+// Creates an SbBlitterContext object on |device|. The returned context can be
+// used to set up draw state and issue draw calls.
+//
+// Note that there is a limit on the number of contexts that can exist
+// simultaneously, which can be queried by calling SbBlitterGetMaxContexts().
+// (The limit is often |1|.)
+//
+// SbBlitterContext objects keep track of draw state between a series of draw
+// calls. Please refer to the SbBlitterContext() definition for more
+// information about contexts.
+//
+// This function is thread-safe.
+//
+// This function returns kSbBlitterInvalidContext upon failure. Note that the
+// function fails if it has already been used to create the maximum number
+// of contexts.
+//
+// |device|: The SbBlitterDevice for which the SbBlitterContext object is
+// created.
 SB_EXPORT SbBlitterContext SbBlitterCreateContext(SbBlitterDevice device);
 
-// Destroys the specified |context| created by |device|, freeing all its
-// resources.
-// This function is not thread safe.
-// Returns whether the destruction succeeded.
+// Destroys the specified |context|, freeing all its resources. This function
+// is not thread-safe.
+//
+// The return value indicates whether the destruction succeeded.
+//
+// |context|: The object to be destroyed.
 SB_EXPORT bool SbBlitterDestroyContext(SbBlitterContext context);
 
-// Flushes all draw calls previously issued to |context|.  After this call,
-// all subsequent draw calls (on any context) are guaranteed to be processed
-// by the device after all previous draw calls issued on this |context|.
-// In many cases you will want to call this before calling
-// SbBlitterFlipSwapChain(), to ensure that all draw calls are submitted before
-// the flip occurs.
-// This function is not thread safe.
-// Returns whether the flush succeeded.
+// Flushes all draw calls previously issued to |context|. Calling this function
+// guarantees that the device processes all draw calls issued to this point on
+// this |context| before processing any subsequent draw calls on any context.
+//
+// Before calling SbBlitterFlipSwapChain(), it is often prudent to call this
+// function to ensure that all draw calls are submitted before the flip occurs.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the flush succeeded.
+//
+// |context|: The context for which draw calls are being flushed.
 SB_EXPORT bool SbBlitterFlushContext(SbBlitterContext context);
 
 // Sets up |render_target| as the render target that all subsequent draw calls
-// made on |context| will draw to.
-// This function is not thread safe.
-// Returns whether the render target was successfully set.
+// made on |context| will use.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the render target was set successfully.
+//
+// |context|: The object for which the render target is being set.
+// |render_target|: The target that the |context| should use for draw calls.
 SB_EXPORT bool SbBlitterSetRenderTarget(SbBlitterContext context,
                                         SbBlitterRenderTarget render_target);
 
-// Sets blending state on the specified |context|.  If |blending| is true, the
-// source alpha of subsequent draw calls will be used to blend with the
-// destination color.  In particular, Fc = Sc * Sa + Dc * (1 - Sa), where
-// Fc is the final color, Sc is the source color, Sa is the source alpha, and
-// Dc is the destination color.  If |blending| is false, the source color and
-// alpha will overwrite the destination color and alpha.  By default blending
+// Sets the blending state for the specified |context|. By default, blending
 // is disabled on a SbBlitterContext.
-// This function is not thread safe.
-// Returns whether the blending state was succcessfully set.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the blending state was set successfully.
+//
+// |context|: The context for which the blending state is being set.
+// |blending|: The blending state for the |context|.
+// If |blending| is |true|, the source alpha of subsequent draw calls
+// is used to blend with the destination color. In particular,
+// (|Fc = Sc * Sa + Dc * (1 - Sa)|), where:
+// - |Fc| is the final color.
+// - |Sc| is the source color.
+// - |Sa| is the source alpha.
+// - |Dc| is the destination color.
+// If |blending| is |false|, the source color and source alpha overwrite
+// the destination color and alpha.
 SB_EXPORT bool SbBlitterSetBlending(SbBlitterContext context, bool blending);
 
 // Sets the context's current color.  The current color's default value is
-// SbBlitterColorFromRGBA(255, 255, 255 255).  The current color affects the
-// fill rectangle's color in calls to SbBlitterFillRect(), and if
-// SbBlitterSetModulateBlitsWithColor() has been called to enable blit color
-// modulation, the source blit surface pixel color will also be modulated by
-// the color before being output.  The color is specified in unpremultiplied
-// alpha format.
-// This function is not thread safe.
-// Returns whether the color was successfully set.
+// |SbBlitterColorFromRGBA(255, 255, 255 255)|.
+//
+// The current color affects the fill rectangle's color in calls to
+// SbBlitterFillRect(). If SbBlitterSetModulateBlitsWithColor() has been called
+// to enable blit color modulation, the source blit surface pixel color is also
+// modulated by the color before being output.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the color was set successfully.
+//
+// |context|: The context for which the color is being set.
+// |color|: The context's new color, specified in unpremultiplied alpha format.
 SB_EXPORT bool SbBlitterSetColor(SbBlitterContext context,
                                  SbBlitterColor color);
 
 // Sets whether or not blit calls should have their source pixels modulated by
-// the current color (set via a call to SbBlitterSetColor()) before being
-// output.  This can be used to apply opacity to blit calls, as well as for
-// coloring alpha-only surfaces, and other effects.
-// This function is not thread safe.
-// Returns whether the state was successfully set.
+// the current color, which is set using SbBlitterSetColor(), before being
+// output. This function can apply opacity to blit calls, color alpha-only
+// surfaces, and apply other effects.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the state was set successfully.
+//
+// |modulate_blits_with_color|: Indicates whether to modulate source pixels
+// in blit calls.
 SB_EXPORT bool SbBlitterSetModulateBlitsWithColor(
     SbBlitterContext context,
     bool modulate_blits_with_color);
 
-// Sets the scissor rectangle.  The scissor rectangle dictates a rectangle of
-// visibility that affects all draw calls.  Only pixels within the scissor
-// rectangle will be rendered, all drawing outside of the scissor rectangle will
-// be clipped.  When SbBlitterSetRenderTarget() is called, the scissor rectangle
-// is automatically set to the extents of the specified render target.  If a
+// Sets the scissor rectangle, which dictates a visibility area that affects
+// all draw calls. Only pixels within the scissor rectangle are rendered, and
+// all drawing outside of that area is clipped.
+//
+// When SbBlitterSetRenderTarget() is called, that function automatically sets
+// the scissor rectangle to the size of the specified render target. If a
 // scissor rectangle is specified outside of the extents of the current render
-// target bounds, it will be intersected with the render target boudns.  It is
-// an error to call this function before a render target has been specified for
-// the context.
-// This function is not thread safe.
-// Returns whether the scissor was successfully set.
+// target bounds, it will be intersected with the render target bounds.
+//
+// This function is not thread-safe.
+//
+// Returns whether the scissor was successfully set. It returns an error if
+// it is called before a render target has been specified for the context.
 SB_EXPORT bool SbBlitterSetScissor(SbBlitterContext context,
                                    SbBlitterRect rect);
 
-// Issues a draw call on |context| that fills a rectangle |rect|.  The color
-// of the rectangle is specified by the last call to SbBlitterSetColor().
-// This function is not thread safe.
-// Returns whether the draw call succeeded.
+// Issues a draw call on |context| that fills the specified rectangle |rect|.
+// The rectangle's color is determined by the last call to SbBlitterSetColor().
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the draw call succeeded.
+//
+// |context|: The context on which the draw call will operate.
+// |rect|: The rectangle to be filled.
 SB_EXPORT bool SbBlitterFillRect(SbBlitterContext context, SbBlitterRect rect);
 
-// Issues a draw call on |context| that blits an area of |source_surface|
-// specified by a |src_rect| to |context|'s current render target at |dst_rect|.
-// The source rectangle must lie within the dimensions of |source_surface|.  The
-// |source_surface|'s alpha will be modulated by |opacity| before being drawn.
-// For |opacity|, a value of 0 implies complete invisibility and a value of
-// 255 implies complete opaqueness.
-// This function is not thread safe.
-// Returns whether the draw call succeeded.
+// Issues a draw call on |context| that blits the area of |source_surface|
+// specified by |src_rect| to |context|'s current render target at |dst_rect|.
+// The source rectangle must lie within the dimensions of |source_surface|.
+// Note that the |source_surface|'s alpha is modulated by |opacity| before
+// being drawn. For |opacity|, a value of 0 implies complete invisibility,
+// and a value of 255 implies complete opacity.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the draw call succeeded.
+//
+// |src_rect|: The area to be block transferred (blitted).
 SB_EXPORT bool SbBlitterBlitRectToRect(SbBlitterContext context,
                                        SbBlitterSurface source_surface,
                                        SbBlitterRect src_rect,
                                        SbBlitterRect dst_rect);
 
-// This function does the exact same as SbBlitterBlitRectToRect(), except
-// it permits values of |src_rect| outside of the dimensions of
-// |source_surface| and in these regions the pixel data from |source_surface|
-// will be wrapped.  Negative values for |src_rect.x| and |src_rect.y| are
-// allowed.  The output will all be stretched to fit inside of |dst_rect|.
-// This function is not thread safe.
-// Returns whether the draw call succeeded.
+// This function functions identically to SbBlitterBlitRectToRect(), except
+// it permits values of |src_rect| outside the dimensions of |source_surface|.
+// In those regions, the pixel data from |source_surface| will be wrapped.
+// Negative values for |src_rect.x| and |src_rect.y| are allowed.
+//
+// The output is all stretched to fit inside of |dst_rect|.
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the draw call succeeded.
 SB_EXPORT bool SbBlitterBlitRectToRectTiled(SbBlitterContext context,
                                             SbBlitterSurface source_surface,
                                             SbBlitterRect src_rect,
                                             SbBlitterRect dst_rect);
 
 // This function achieves the same effect as calling SbBlitterBlitRectToRect()
-// |num_rects| time with each of the |num_rects| values of |src_rects| and
-// |dst_rects|.  Using this function allows for greater efficiency than calling
-// SbBlitterBlitRectToRect() in a loop.
-// This function is not thread safe.
-// Returns whether the draw call succeeded.
+// |num_rects| times with each of the |num_rects| values of |src_rects| and
+// |dst_rects|. This function allows for greater efficiency than looped calls
+// to SbBlitterBlitRectToRect().
+//
+// This function is not thread-safe.
+//
+// The return value indicates whether the draw call succeeded.
 SB_EXPORT bool SbBlitterBlitRectsToRects(SbBlitterContext context,
                                          SbBlitterSurface source_surface,
                                          const SbBlitterRect* src_rects,
diff --git a/src/starboard/build/convert_i18n_data.py b/src/starboard/build/convert_i18n_data.py
index a34916a..0f56a59 100644
--- a/src/starboard/build/convert_i18n_data.py
+++ b/src/starboard/build/convert_i18n_data.py
@@ -51,7 +51,7 @@
   messages = root[0]
 
   # Write each message to the output file on its own line.
-  with open(output_filename, 'w') as output_file:
+  with open(output_filename, 'wb') as output_file:
     for msg in messages:
       # Use ; as the separator. Which means it better not be in the name.
       assert not (';' in msg.attrib['name'])
diff --git a/src/starboard/build/copy_data.py b/src/starboard/build/copy_data.py
index cd1d6a2..c19c020 100644
--- a/src/starboard/build/copy_data.py
+++ b/src/starboard/build/copy_data.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 # This file is based on build/copy_test_data_ios.py
-
 """Copies data files or directories into a given output directory.
 
 Since the output of this script is intended to be use by GYP, all resulting
@@ -29,6 +28,12 @@
 if os.name == 'nt':
   import win32api
 
+# The name of an environment variable that when set to |'1'|, signals to us
+# that we should log all output directories that we have staged a copy to to
+# our stderr output. This output could then, for example, be captured and
+# analyzed by an external tool that is interested in these directories.
+_ShouldLogEnvKey = 'STARBOARD_GYP_SHOULD_LOG_COPIES'
+
 
 class WrongNumberOfArgumentsException(Exception):
   pass
@@ -99,14 +104,27 @@
   """Called by GYP using pymod_do_main."""
   parser = argparse.ArgumentParser()
   parser.add_argument('-o', dest='output_dir', help='output directory')
-  parser.add_argument('--inputs', action='store_true', dest='list_inputs',
-                      help='prints a list of all input files')
-  parser.add_argument('--outputs', action='store_true', dest='list_outputs',
-                      help='prints a list of all output files')
-  parser.add_argument('input_paths', metavar='path', nargs='+',
-                      help='path to an input file or directory')
+  parser.add_argument(
+      '--inputs',
+      action='store_true',
+      dest='list_inputs',
+      help='prints a list of all input files')
+  parser.add_argument(
+      '--outputs',
+      action='store_true',
+      dest='list_outputs',
+      help='prints a list of all output files')
+  parser.add_argument(
+      'input_paths',
+      metavar='path',
+      nargs='+',
+      help='path to an input file or directory')
   options = parser.parse_args(argv)
 
+  if os.environ.get(_ShouldLogEnvKey, None) == '1':
+    if options.output_dir is not None:
+      print >> sys.stderr, 'COPY_LOG:', options.output_dir
+
   escaped_files = [EscapePath(x) for x in options.input_paths]
   files_to_copy = CalcInputs(escaped_files)
   if options.list_inputs:
@@ -133,5 +151,6 @@
     print result
   return 0
 
+
 if __name__ == '__main__':
   sys.exit(main(sys.argv))
diff --git a/src/starboard/byte_swap.h b/src/starboard/byte_swap.h
index 7c2e968..9fbd1fa 100644
--- a/src/starboard/byte_swap.h
+++ b/src/starboard/byte_swap.h
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Functions for swapping byte order, used to deal with endianness when
-// performing I/O
+// Module Overview: Starboard Byte Swap module
+//
+// Specifies functions for swapping byte order. These functions are used to
+// deal with endianness when performing I/O.
 
 #ifndef STARBOARD_BYTE_SWAP_H_
 #define STARBOARD_BYTE_SWAP_H_
@@ -53,21 +55,27 @@
 // and then act on that determination somehow.
 
 // Unconditionally swaps the byte order in signed 16-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT int16_t SbByteSwapS16(int16_t value);
 
 // Unconditionally swaps the byte order in unsigned 16-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT uint16_t SbByteSwapU16(uint16_t value);
 
 // Unconditionally swaps the byte order in signed 32-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT int32_t SbByteSwapS32(int32_t value);
 
 // Unconditionally swaps the byte order in unsigned 32-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT uint32_t SbByteSwapU32(uint32_t value);
 
 // Unconditionally swaps the byte order in signed 64-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT int64_t SbByteSwapS64(int64_t value);
 
 // Unconditionally swaps the byte order in unsigned 64-bit |value|.
+// |value|: The value for which the byte order will be swapped.
 SB_EXPORT uint64_t SbByteSwapU64(uint64_t value);
 
 #ifdef __cplusplus
diff --git a/src/starboard/character.h b/src/starboard/character.h
index f4d064f..f7758c7 100644
--- a/src/starboard/character.h
+++ b/src/starboard/character.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Functions for interacting with characters.
+// Module Overview: Starboard Character module
+//
+// Provides functions for interacting with characters.
 
 #ifndef STARBOARD_CHARACTER_H_
 #define STARBOARD_CHARACTER_H_
@@ -26,36 +28,50 @@
 #endif
 
 // Converts the given 8-bit character (as an int) to uppercase in the current
-// locale, returning the result, also an 8-bit character. If there is no
-// uppercase version of the character, or the character is already uppercase, it
-// will just return the character as-is.
-int SbCharacterToUpper(int c);
+// locale and returns an 8-bit character. If there is no uppercase version of
+// the character, or the character is already uppercase, the function just
+// returns the character as-is.
+//
+// |c|: The character to be converted.
+SB_EXPORT int SbCharacterToUpper(int c);
 
 // Converts the given 8-bit character (as an int) to lowercase in the current
-// locale, returning the result, also an 8-bit character. If there is no
-// lowercase version of the character, or the character is already lowercase, it
-// will just return the character as-is.
-int SbCharacterToLower(int c);
+// locale and returns an 8-bit character. If there is no lowercase version of
+// the character, or the character is already lowercase, the function just
+// returns the character as-is.
+//
+// |c|: The character to be converted.
+SB_EXPORT int SbCharacterToLower(int c);
 
-// Returns whether the given 8-bit character |c| (as an int) is a space in the
-// current locale.
-bool SbCharacterIsSpace(int c);
-
-// Returns whether the given 8-bit character |c| (as an int) is uppercase in the
-// current locale.
-bool SbCharacterIsUpper(int c);
-
-// Returns whether the given 8-bit character |c| (as an int) is a decimal digit
-// in the current locale.
-bool SbCharacterIsDigit(int c);
-
-// Returns whether the given 8-bit character |c| (as an int) is a hexidecimal
-// digit in the current locale.
-bool SbCharacterIsHexDigit(int c);
-
-// Returns whether the given 8-bit character |c| (as an int) is alphanumeric in
+// Indicates whether the given 8-bit character |c| (as an int) is a space in
 // the current locale.
-bool SbCharacterIsAlphanumeric(int c);
+//
+// |c|: The character to be evaluated.
+SB_EXPORT bool SbCharacterIsSpace(int c);
+
+// Indicates whether the given 8-bit character |c| (as an int) is uppercase
+// in the current locale.
+//
+// |c|: The character to be evaluated.
+SB_EXPORT bool SbCharacterIsUpper(int c);
+
+// Indicates whether the given 8-bit character |c| (as an int) is a
+// decimal digit in the current locale.
+//
+// |c|: The character to be evaluated.
+SB_EXPORT bool SbCharacterIsDigit(int c);
+
+// Indicates whether the given 8-bit character |c| (as an int) is a hexadecimal
+// in the current locale.
+//
+// |c|: The character to be evaluated.
+SB_EXPORT bool SbCharacterIsHexDigit(int c);
+
+// Indicates whether the given 8-bit character |c| (as an int) is alphanumeric
+// in the current locale.
+//
+// |c|: The character to be evaluated.
+SB_EXPORT bool SbCharacterIsAlphanumeric(int c);
 
 #ifdef __cplusplus
 }  // extern "C"
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/client_porting/eztime/eztime.h b/src/starboard/client_porting/eztime/eztime.h
index 8402006..8221c2f 100644
--- a/src/starboard/client_porting/eztime/eztime.h
+++ b/src/starboard/client_porting/eztime/eztime.h
@@ -15,7 +15,9 @@
 #ifndef STARBOARD_CLIENT_PORTING_EZTIME_EZTIME_H_
 #define STARBOARD_CLIENT_PORTING_EZTIME_EZTIME_H_
 
+#include "starboard/log.h"
 #include "starboard/time.h"
+#include "starboard/types.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -119,7 +121,12 @@
 // Converts SbTime to EzTimeValue.
 static SB_C_FORCE_INLINE EzTimeValue EzTimeValueFromSbTime(SbTime in_time) {
   EzTimeT sec = EzTimeTFromSbTime(in_time);
-  EzTimeValue value = {sec, in_time - EzTimeTToSbTime(sec)};
+  SbTime diff = in_time - EzTimeTToSbTime(sec);
+  SB_DCHECK(diff >= INT_MIN);
+  SB_DCHECK(diff <= INT_MAX);
+  EzTimeValue value = {sec, (int)diff};  // Some compilers do not support
+                                         // returning the initializer list
+                                         // directly.
   return value;
 }
 
diff --git a/src/starboard/client_porting/poem/assert_poem.h b/src/starboard/client_porting/poem/assert_poem.h
new file mode 100644
index 0000000..9d01a43
--- /dev/null
+++ b/src/starboard/client_porting/poem/assert_poem.h
@@ -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.
+
+// A poem (POsix EMulation) for functions in string.h
+
+#ifndef STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_
+#define STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_
+
+#if defined(STARBOARD)
+
+#include "starboard/log.h"
+
+#if !defined(POEM_NO_EMULATION)
+
+// On one line so that the assert macros do not interfere with reporting of line
+// numbers in compiler error messages.
+#define assert(x)                                                            \
+  do {                                                                       \
+    if (!(x)) {                                                              \
+      SbLogFormatF("expression %s failed at %s:%d", #x, __FILE__, __LINE__); \
+      SbSystemBreakIntoDebugger();                                           \
+    }                                                                        \
+  } while (false);
+
+#endif  // POEM_NO_EMULATION
+
+#endif  // STARBOARD
+
+#endif  // STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_
diff --git a/src/starboard/client_porting/poem/getenv_stub_poem.h b/src/starboard/client_porting/poem/getenv_stub_poem.h
new file mode 100644
index 0000000..8e4ab81
--- /dev/null
+++ b/src/starboard/client_porting/poem/getenv_stub_poem.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.
+
+// A poem (POsix EMulation) stub implementation for getenv.
+
+#ifndef STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_
+#define STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_
+
+#include <stdlib.h>
+
+#include "starboard/configuration.h"
+
+static SB_C_INLINE const char* getenv(const char* unused) {
+  return NULL;
+}
+
+#endif  // STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_
diff --git a/src/starboard/client_porting/poem/inet_poem.h b/src/starboard/client_porting/poem/inet_poem.h
new file mode 100644
index 0000000..55cf47a
--- /dev/null
+++ b/src/starboard/client_porting/poem/inet_poem.h
@@ -0,0 +1,27 @@
+// 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.
+
+// A poem (POsix EMulation) for functions usually declared in <arpa/inet.h>
+
+#ifndef STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_
+#define STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_
+
+#include "starboard/byte_swap.h"
+
+#define htonl(x) SB_HOST_TO_NET_U32(x)
+#define htons(x) SB_HOST_TO_NET_U16(x)
+#define ntohl(x) SB_NET_TO_HOST_U32(x)
+#define ntohs(x) SB_NET_TO_HOST_U16(x)
+
+#endif  // STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_
diff --git a/src/starboard/client_porting/poem/math_poem.h b/src/starboard/client_porting/poem/math_poem.h
new file mode 100644
index 0000000..7f406d2
--- /dev/null
+++ b/src/starboard/client_porting/poem/math_poem.h
@@ -0,0 +1,62 @@
+// 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.
+
+// A poem (POsix EMulation) for functions in string.h
+
+#ifndef STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_
+#define STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_
+
+#if defined(STARBOARD)
+
+#include "starboard/double.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Takes floor of a float |f|.  Meant to be a drop-in replacement for |floorf|
+static SB_C_INLINE float PoemSingleFloor(const float f) {
+  double d(f);
+  return SbDoubleFloor(d);
+}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#if !defined(POEM_NO_EMULATION)
+
+#define fabs(x) SbDoubleAbsolute(x)
+#define floor(x) SbDoubleFloor(x)
+#define floorf(x) PoemSingleFloor(x)
+#define pow(x, y) SbDoubleExponent(x, y)
+
+#include <math.h>
+#define ceil(x) ceil(x)
+#define fmod(x, y) fmod(x, y)
+#define modf(x, y) modf(x, y)
+#define log(x) log(x)
+#define sqrt(x) sqrt(x)
+#define sin(x) sin(x)
+#define cos(x) cos(x)
+#define tan(x) tan(x)
+#define atan(x) atan(x)
+#define atan2(x, y) atan2(x, y)
+#define asin(x) asin(x)
+#define acos(x) acos(x)
+#endif  // POEM_NO_EMULATION
+
+#endif  // STARBOARD
+
+#endif  // STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_
diff --git a/src/starboard/client_porting/poem/poem.gyp b/src/starboard/client_porting/poem/poem.gyp
index afde08e..947cc36 100644
--- a/src/starboard/client_porting/poem/poem.gyp
+++ b/src/starboard/client_porting/poem/poem.gyp
@@ -19,10 +19,13 @@
       'type': 'static_library',
       'sources': [
         'eztime_poem.h',
+        'getenv_stub_poem.h',
+        'inet_poem.h',
         'stdio_poem.h',
         'stdlib_poem.h',
         'string_poem.h',
         'strings_poem.h',
+        'strnlen_poem.h',
         'wchar_poem.h',
       ],
       'dependencies': [
@@ -37,11 +40,23 @@
         'abs_tests.cc',
         'include_all.c',
         'main.cc',
+        'string_poem_test.cc',
       ],
       'dependencies': [
         '<(DEPTH)/testing/gtest.gyp:gtest',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
       ],
     },
+    {
+      'target_name': 'poem_unittests_deploy',
+      'type': 'none',
+      'dependencies': [
+        'poem_unittests',
+      ],
+      'variables': {
+        'executable_name': 'poem_unittests',
+      },
+      'includes': [ '../../build/deploy.gypi' ],
+    },
   ],
 }
diff --git a/src/starboard/client_porting/poem/stdio_poem.h b/src/starboard/client_porting/poem/stdio_poem.h
index 2500568..eab3f05 100644
--- a/src/starboard/client_porting/poem/stdio_poem.h
+++ b/src/starboard/client_porting/poem/stdio_poem.h
@@ -21,8 +21,8 @@
 
 #if !defined(POEM_NO_EMULATION)
 
-#include "starboard/string.h"
 #include "starboard/memory.h"
+#include "starboard/string.h"
 
 #define wcsncmp(s1, s2, c) SbStringCompareWide(s1, s2, c)
 
@@ -34,9 +34,10 @@
 #define sprintf SbStringFormatUnsafeF
 #define vsscanf SbStringScan
 #define sscanf SbStringScanF
-#define malloc(sz) SbMemoryAllocateUnchecked(sz)
-#define free(a) SbMemoryFree(a)
-#define realloc(m, sz) SbMemoryReallocateUnchecked(m, sz)
+#define malloc(sz) SbMemoryAllocate(sz)
+#define calloc(c, s) SbMemoryCalloc(c, s)
+#define free(a) SbMemoryDeallocate(a)
+#define realloc(m, sz) SbMemoryReallocate(m, sz)
 
 #endif  // POEM_NO_EMULATION
 
diff --git a/src/starboard/client_porting/poem/string_poem.h b/src/starboard/client_porting/poem/string_poem.h
index 97ef46d..61ca795 100644
--- a/src/starboard/client_porting/poem/string_poem.h
+++ b/src/starboard/client_porting/poem/string_poem.h
@@ -19,8 +19,9 @@
 
 #if defined(STARBOARD)
 
-#include "starboard/string.h"
+#include "starboard/log.h"
 #include "starboard/memory.h"
+#include "starboard/string.h"
 
 #ifdef __cplusplus
 
@@ -31,7 +32,7 @@
 // Finds the last occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr, C++ signature
-inline char* PoemFindLastCharacter(char* str, int character) {
+inline char* PoemFindLastCharacterInString(char* str, int character) {
   const char* const_str = static_cast<const char*>(str);
   const char c = static_cast<char>(character);
   return const_cast<char*>(SbStringFindLastCharacter(const_str, c));
@@ -40,7 +41,8 @@
 // Finds the last occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr, C++ signature
-inline const char* PoemFindLastCharacter(const char* str, int character) {
+inline const char* PoemFindLastCharacterInString(const char* str,
+                                                 int character) {
   const char c = static_cast<char>(character);
   return SbStringFindLastCharacter(str, c);
 }
@@ -48,7 +50,7 @@
 // Finds the first occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr
-inline char* PoemFindCharacter(char* str, int character) {
+inline char* PoemFindCharacterInString(char* str, int character) {
   const char* const_str = static_cast<const char*>(str);
   const char c = static_cast<char>(character);
   return const_cast<char*>(SbStringFindCharacter(const_str, c));
@@ -57,7 +59,7 @@
 // Finds the first occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr
-inline const char* PoemFindCharacter(const char* str, int character) {
+inline const char* PoemFindCharacterInString(const char* str, int character) {
   const char c = static_cast<char>(character);
   return SbStringFindCharacter(str, c);
 }
@@ -67,7 +69,8 @@
 // Finds the first occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr
-static SB_C_INLINE char* PoemFindCharacter(const char* str, int character) {
+static SB_C_INLINE char* PoemFindCharacterInString(const char* str,
+                                                   int character) {
   // C-style cast used for C code
   return (char*)(SbStringFindCharacter(str, character));
 }
@@ -75,7 +78,8 @@
 // Finds the last occurrence of |character| in |str|, returning a pointer to
 // the found character in the given string, or NULL if not found.
 // Meant to be a drop-in replacement for strchr
-static SB_C_INLINE char* PoemFindLastCharacter(const char* str, int character) {
+static SB_C_INLINE char* PoemFindLastCharacterInString(const char* str,
+                                                       int character) {
   // C-style cast used for C code
   return (char*)(SbStringFindLastCharacter(str, character));
 }
@@ -86,21 +90,60 @@
 #endif
 
 // Concatenates |source| onto the end of |out_destination|, presuming it has
-// |destination_size| total characters of storage available. Returns
-// |out_destination|.  This method is a drop-in replacement for strncat
-static SB_C_INLINE char* PoemConcat(char* out_destination,
-                                    const char* source,
-                                    int destination_size) {
-  SbStringConcat(out_destination, source, destination_size);
+// atleast strlen(out_destination) + |num_chars_to_copy| + 1 total characters of
+// storage available. Returns |out_destination|.  This method is a drop-in
+// replacement for strncat.
+// Note: even if num_chars_to_copy == 0, we will still write a NULL character.
+// This is consistent with the language of linux's strncat man page:
+// "Therefore, the size of dest must be at least strlen(dest)+n+1."
+static SB_C_INLINE char* PoemStringConcat(char* out_destination,
+                                          const char* source,
+                                          int num_chars_to_copy) {
+  int destination_length = (int)(SbStringGetLength(out_destination));
+  SbStringConcat(out_destination, source,
+                 destination_length + num_chars_to_copy + 1);
   return out_destination;
 }
 
-// Inline wrapper for an unsafe PoemConcat that assumes |out_destination| is
-// big enough. Returns |out_destination|.  Meant to be a drop-in replacement for
-// strcat.
-static SB_C_INLINE char* PoemConcatUnsafe(char* out_destination,
-                                          const char* source) {
-  return PoemConcat(out_destination, source, INT_MAX);
+// Inline wrapper for an unsafe PoemStringConcat that assumes |out_destination|
+// is big enough. Returns |out_destination|.  Meant to be a drop-in replacement
+// for strcat.
+static SB_C_INLINE char* PoemStringConcatUnsafe(char* out_destination,
+                                                const char* source) {
+  return PoemStringConcat(out_destination, source, INT_MAX);
+}
+
+// Inline wrapper for a drop-in replacement for |strncpy|.  This function
+// copies the null terminated string from src to dest.  If the src string is
+// shorter than num_chars_to_copy, then null padding is used.
+// Warning: As with strncpy spec, if there is no null character within the first
+// num_chars_to_copy in src, this function will write a null character at the
+// end.
+static SB_C_INLINE char* PoemStringCopyN(char* dest,
+                                         const char* src,
+                                         int num_chars_to_copy) {
+  SB_DCHECK(num_chars_to_copy >= 0);
+  if (num_chars_to_copy < 0) {
+    return dest;
+  }
+
+  char* dest_write_iterator = dest;
+  char* dest_write_iterator_end = dest + num_chars_to_copy;
+  const char* src_iterator = src;
+
+  while ((*src_iterator != '\0') &&
+         (dest_write_iterator != dest_write_iterator_end)) {
+    *dest_write_iterator = *src_iterator;
+
+    ++src_iterator;
+    ++dest_write_iterator;
+  }
+
+  SB_DCHECK(dest_write_iterator_end >= dest_write_iterator);
+  SbMemorySet(dest_write_iterator, '\0',
+              dest_write_iterator_end - dest_write_iterator);
+
+  return dest;
 }
 
 #ifdef __cplusplus
@@ -111,12 +154,12 @@
 
 #define strlen(s) SbStringGetLength(s)
 #define strcpy(o, s) SbStringCopyUnsafe(o, s)
-#define strncpy(o, s, ds) SbStringCopy(o, s, ds)
-#define strcat(o, s) PoemConcatUnsafe(o, s)
-#define strncat(o, s, ds) PoemConcat(o, s, ds)
+#define strncpy(o, s, ds) PoemStringCopyN(o, s, ds)
+#define strcat(o, s) PoemStringConcatUnsafe(o, s)
+#define strncat(o, s, ds) PoemStringConcat(o, s, ds)
 #define strdup(s) SbStringDuplicate(s)
-#define strchr(s, c) PoemFindCharacter(s, c)
-#define strrchr(s, c) PoemFindLastCharacter(s, c)
+#define strchr(s, c) PoemFindCharacterInString(s, c)
+#define strrchr(s, c) PoemFindLastCharacterInString(s, c)
 #define strstr(s, c) SbStringFindString(s, c)
 #define strncmp(s1, s2, c) SbStringCompare(s1, s2, c)
 #define strcmp(s1, s2) SbStringCompareAll(s1, s2)
diff --git a/src/starboard/client_porting/poem/string_poem_test.cc b/src/starboard/client_porting/poem/string_poem_test.cc
new file mode 100644
index 0000000..1a3e899
--- /dev/null
+++ b/src/starboard/client_porting/poem/string_poem_test.cc
@@ -0,0 +1,96 @@
+// 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.
+
+// Test basic functionality of string poems.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "starboard/client_porting/poem/string_poem.h"
+#include "starboard/client_porting/poem/strnlen_poem.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+TEST(StringPoemTest, PoemStringConcat) {
+  char a[64] = "Hello ";
+  char b[] = "Yu";
+
+  strncat(a, b, sizeof(b) + 1);
+
+  EXPECT_STREQ(a, "Hello Yu");
+}
+
+TEST(StringPoemTest, PoemStringCopy) {
+  char a[] = "Hello ";
+  char b[] = "Yu";
+
+  strncpy(a, b, sizeof(b));
+
+  EXPECT_STREQ(a, "Yu");
+}
+
+TEST(StringPoemTest, PoemStringCopyZeroBytes) {
+  char a[] = "Hello ";
+  char b[] = "Yu";
+
+  strncpy(a, b, 0);
+
+  EXPECT_STREQ(a, "Hello ");
+}
+
+TEST(StringPoemTest, PoemStringCopySrcNotNullTerminated) {
+  char a[] = "Hello ";
+  char b[] = "Yu";
+
+  strncpy(a, b, sizeof(b) - 1);
+
+  EXPECT_STREQ(a, "Yullo ");
+}
+
+TEST(StringPoemTest, PoemStringCopyNotEnoughSpace) {
+  char a[] = "Hello";
+  char b[] = "Cobalt";
+
+  strncpy(a, b, sizeof(a) - 1);
+
+  EXPECT_STREQ(a, "Cobal");
+}
+
+TEST(StringPoemTest, PoemStringCopySizeMoreThanNeeded) {
+  char a[] = "Hello";
+  char b[] = "Wu";
+
+  strncpy(a, b, sizeof(a));
+
+  EXPECT_STREQ(a, "Wu");
+}
+
+TEST(StringPoemTest, PoemStringGetLengthFixed) {
+  char a[] = "abcdef";
+  char b[] = "abc\0def";
+
+  EXPECT_EQ(strnlen(a, 0), 0);
+  EXPECT_EQ(strnlen(a, 3), 3);
+  EXPECT_EQ(strnlen(a, sizeof(a)), 6);
+  EXPECT_EQ(strnlen(a, 256), 6);
+  EXPECT_EQ(strnlen(b, 2), 2);
+  EXPECT_EQ(strnlen(b, 3), 3);
+  EXPECT_EQ(strnlen(b, sizeof(b)), 3);
+  EXPECT_EQ(strnlen(b, 256), 3);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/client_porting/poem/strnlen_poem.h b/src/starboard/client_porting/poem/strnlen_poem.h
new file mode 100644
index 0000000..fb2d674
--- /dev/null
+++ b/src/starboard/client_porting/poem/strnlen_poem.h
@@ -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.
+
+// A poem (POsix EMulation) implementation for strnlen. Usually declared in
+// <string.h>, but may be missing on some platforms (e.g. PS3).
+
+#ifndef STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_
+#define STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_
+
+#include "starboard/configuration.h"
+
+static SB_C_INLINE size_t StringGetLengthFixed(const char* s, size_t maxlen) {
+  size_t i = 0;
+  while (i < maxlen && s[i]) {
+    ++i;
+  }
+  return i;
+}
+
+#define strnlen(s, maxlen) StringGetLengthFixed(s, maxlen)
+
+#endif  // STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.cc b/src/starboard/client_porting/pr_starboard/pr_starboard.cc
new file mode 100644
index 0000000..732ea14
--- /dev/null
+++ b/src/starboard/client_porting/pr_starboard/pr_starboard.cc
@@ -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.
+
+#include "starboard/client_porting/pr_starboard/pr_starboard.h"
+
+#include "starboard/condition_variable.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/mutex.h"
+#include "starboard/once.h"
+#include "starboard/queue.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
+#include "starboard/types.h"
+
+namespace {
+
+typedef starboard::Queue<bool> SetupSignalQueue;
+
+// Utility function to convert a PRInterval to signed 64 bit integer
+// microseconds.
+int64_t PR_IntervalToMicrosecondsInt64(PRIntervalTime ticks) {
+  uint32_t microseconds_as_uint32 = PR_IntervalToMicroseconds(ticks);
+  int64_t microseconds_as_int64 = static_cast<int64_t>(microseconds_as_uint32);
+  return microseconds_as_int64;
+}
+
+// Struct to bundle up arguments to be passed into SbThreadCreate.
+struct ThreadEntryPointWrapperContext {
+  ThreadEntryPointWrapperContext(void* pr_context,
+                                 PRThread* pr_thread,
+                                 PRThreadEntryPoint pr_entry_point,
+                                 SetupSignalQueue* setup_signal_queue)
+      : pr_context(pr_context),
+        pr_thread(pr_thread),
+        pr_entry_point(pr_entry_point),
+        setup_signal_queue(setup_signal_queue) {}
+  void* pr_context;
+  PRThread* pr_thread;
+  PRThreadEntryPoint pr_entry_point;
+  SetupSignalQueue* setup_signal_queue;
+};
+
+// The thread local key that corresponds to where local PRThread* data is held.
+SbThreadLocalKey g_pr_thread_local_key = kSbThreadLocalKeyInvalid;
+// The SbOnceControlStructure to to ensure that the local key is only created
+// once.
+SbOnceControl g_pr_thread_key_once_control = SB_ONCE_INITIALIZER;
+
+void PrThreadDtor(void* value) {
+  PRThread* pr_thread = reinterpret_cast<PRThread*>(value);
+  delete pr_thread;
+}
+
+void InitPrThreadKey() {
+  g_pr_thread_local_key = SbThreadCreateLocalKey(PrThreadDtor);
+}
+
+SbThreadLocalKey GetPrThreadKey() {
+  SB_CHECK(SbOnce(&g_pr_thread_key_once_control, InitPrThreadKey));
+  return g_pr_thread_local_key;
+}
+
+void* ThreadEntryPointWrapper(void* context_as_void_pointer) {
+  ThreadEntryPointWrapperContext* context =
+      reinterpret_cast<ThreadEntryPointWrapperContext*>(
+          context_as_void_pointer);
+  void* pr_context = context->pr_context;
+  PRThreadEntryPoint pr_entry_point = context->pr_entry_point;
+  PRThread* pr_thread = context->pr_thread;
+  SetupSignalQueue* setup_signal_queue = context->setup_signal_queue;
+
+  delete context;
+
+  pr_thread->sb_thread = SbThreadGetCurrent();
+  SbThreadLocalKey key = GetPrThreadKey();
+  SB_CHECK(SbThreadIsValidLocalKey(key));
+  SbThreadSetLocalValue(key, pr_thread);
+
+  setup_signal_queue->Put(true);
+  pr_entry_point(pr_context);
+
+  return NULL;
+}
+
+}  // namespace
+
+PRLock* PR_NewLock() {
+  PRLock* lock = new PRLock();
+  if (!SbMutexCreate(lock)) {
+    delete lock;
+    return NULL;
+  }
+  return lock;
+}
+
+void PR_DestroyLock(PRLock* lock) {
+  SB_DCHECK(lock);
+  SbMutexDestroy(lock);
+  delete lock;
+}
+
+PRCondVar* PR_NewCondVar(PRLock* lock) {
+  SB_DCHECK(lock);
+  PRCondVar* cvar = new PRCondVar();
+  if (!SbConditionVariableCreate(&cvar->sb_condition_variable, lock)) {
+    delete cvar;
+    return NULL;
+  }
+  cvar->lock = lock;
+  return cvar;
+}
+
+void PR_DestroyCondVar(PRCondVar* cvar) {
+  SbConditionVariableDestroy(&cvar->sb_condition_variable);
+  delete cvar;
+}
+
+PRStatus PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout) {
+  SbConditionVariableResult result;
+  if (timeout == PR_INTERVAL_NO_WAIT) {
+    result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable,
+                                          cvar->lock, 0);
+  } else if (timeout == PR_INTERVAL_NO_TIMEOUT) {
+    result = SbConditionVariableWait(&cvar->sb_condition_variable, cvar->lock);
+  } else {
+    int64_t microseconds = PR_IntervalToMicrosecondsInt64(timeout);
+    result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable,
+                                          cvar->lock, microseconds);
+  }
+  return pr_starboard::ToPRStatus(result != kSbConditionVariableFailed);
+}
+
+PRThread* PR_GetCurrentThread() {
+  SbThreadLocalKey key = GetPrThreadKey();
+  SB_CHECK(SbThreadIsValidLocalKey(key));
+
+  PRThread* value = static_cast<PRThread*>(SbThreadGetLocalValue(key));
+  // We could potentially be a thread that was not created through
+  // PR_CreateThread.  In this case, we must allocate a PRThread and do the
+  // setup that would normally have been done in PR_CreateThread.
+  if (!value) {
+    PRThread* pr_thread = new PRThread(SbThreadGetCurrent());
+    SbThreadSetLocalValue(key, pr_thread);
+    value = pr_thread;
+  }
+
+  return value;
+}
+
+uint32_t PR_snprintf(char* out, uint32_t outlen, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  uint32_t ret = PR_vsnprintf(out, outlen, fmt, args);
+  va_end(args);
+  return ret;
+}
+
+PRThread* PR_CreateThread(PRThreadType type,
+                          PRThreadEntryPoint start,
+                          void* arg,
+                          PRThreadPriority priority,
+                          PRThreadScope scope,
+                          PRThreadState state,
+                          PRUint32 stackSize) {
+  int64_t sb_stack_size = static_cast<int64_t>(stackSize);
+
+  SbThreadPriority sb_priority;
+  switch (priority) {
+    case PR_PRIORITY_LOW:
+      sb_priority = kSbThreadPriorityLow;
+      break;
+    case PR_PRIORITY_NORMAL:
+      sb_priority = kSbThreadPriorityNormal;
+      break;
+    case PR_PRIORITY_HIGH:
+      sb_priority = kSbThreadPriorityHigh;
+      break;
+    case PR_PRIORITY_LAST:
+      sb_priority = kSbThreadPriorityHighest;
+      break;
+    default:
+      sb_priority = kSbThreadNoPriority;
+  }
+
+  SbThreadAffinity sb_affinity = kSbThreadNoAffinity;
+
+  SB_DCHECK(state == PR_JOINABLE_THREAD || state == PR_UNJOINABLE_THREAD);
+  bool sb_joinable = (state == PR_JOINABLE_THREAD);
+
+  // This heap allocated PRThread object will have a pointer to it stored in
+  // the newly created child thread's thread local storage.  Once the newly
+  // created child thread finishes, it will be freed in the destructor
+  // associated with it through thread local storage.
+  PRThread* pr_thread = new PRThread(kSbThreadInvalid);
+
+  // Utility queue for the ThreadEntryWrapper to signal us once it's done
+  // running its initial setup and we can safely exit.
+  SetupSignalQueue setup_signal_queue;
+
+  // This heap allocated context object is freed after
+  // ThreadEntryPointWrapper's initial setup is complete, right before the
+  // nspr level entry point is run.
+  ThreadEntryPointWrapperContext* context = new ThreadEntryPointWrapperContext(
+      arg, pr_thread, start, &setup_signal_queue);
+
+  // Note that pr_thread->sb_thread will be set to the correct value in the
+  // setup section of ThreadEntryPointWrapper.  It is done there rather than
+  // here to account for the unlikely but possible case in which we enter the
+  // newly created child thread, and then the child thread passes references
+  // to itself off into its potential children or co-threads that interact
+  // with it before we can copy what SbThreadCreate returns into
+  // pr_thread->sb_thread from this current thread.
+  SbThreadCreate(sb_stack_size, sb_priority, sb_affinity, sb_joinable, NULL,
+                 ThreadEntryPointWrapper, context);
+
+  // Now we must wait for the setup section of ThreadEntryPointWrapper to run
+  // and initialize pr_thread (both the struct itself and the corresponding
+  // new thread's private data) before we can safely return.  We expect to
+  // receive true rather than false by convention.
+  bool setup_signal = setup_signal_queue.Get();
+  SB_DCHECK(setup_signal);
+
+  return pr_thread;
+}
+
+PRStatus PR_CallOnceWithArg(PRCallOnceType* once,
+                            PRCallOnceWithArgFN func,
+                            void* arg) {
+  SB_NOTREACHED() << "Not implemented";
+  return PR_FAILURE;
+}
+
+PRStatus PR_NewThreadPrivateIndex(PRTLSIndex* newIndex,
+                                  PRThreadPrivateDTOR destructor) {
+  SbThreadLocalKey key = SbThreadCreateLocalKey(destructor);
+  if (!SbThreadIsValidLocalKey(key)) {
+    return pr_starboard::ToPRStatus(false);
+  }
+  *newIndex = key;
+  return pr_starboard::ToPRStatus(true);
+}
+
+PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli) {
+  PRUint64 tock = static_cast<PRUint64>(milli);
+  PRUint64 msecPerSec = static_cast<PRInt64>(PR_MSEC_PER_SEC);
+  PRUint64 rounding = static_cast<PRInt64>(PR_MSEC_PER_SEC >> 1);
+  PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond());
+
+  tock *= tps;
+  tock += rounding;
+  tock /= msecPerSec;
+
+  PRUint64 ticks = static_cast<PRUint64>(tock);
+  return ticks;
+}
+
+PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks) {
+  PRUint64 tock = static_cast<PRInt64>(ticks);
+  PRUint64 usecPerSec = static_cast<PRInt64>(PR_USEC_PER_SEC);
+  PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond());
+  PRUint64 rounding = static_cast<PRUint64>(tps) >> 1;
+
+  tock *= usecPerSec;
+  tock += rounding;
+  tock /= tps;
+
+  PRUint32 micro = static_cast<PRUint32>(tock);
+  return micro;
+}
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.gyp b/src/starboard/client_porting/pr_starboard/pr_starboard.gyp
new file mode 100644
index 0000000..0874b65
--- /dev/null
+++ b/src/starboard/client_porting/pr_starboard/pr_starboard.gyp
@@ -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.
+
+{
+  'targets': [
+    {
+      'target_name': 'pr_starboard',
+      'type': 'static_library',
+      'sources': [
+        'pr_starboard.cc',
+        'pr_starboard.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/starboard/starboard.gyp:starboard',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.h b/src/starboard/client_porting/pr_starboard/pr_starboard.h
new file mode 100644
index 0000000..63e5c4d
--- /dev/null
+++ b/src/starboard/client_porting/pr_starboard/pr_starboard.h
@@ -0,0 +1,189 @@
+// 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.
+
+// This header defines the interface for a starboard based implementation of
+// the subset of nspr that SpiderMonkey depends on.  It directly matches the
+// NSPR API, with the exception that accessing thread local data should use
+// PRTLSIndex, rather than PRUintn.
+
+#ifndef STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_
+#define STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_
+
+#include "starboard/condition_variable.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/mutex.h"
+#include "starboard/string.h"
+#include "starboard/thread.h"
+#include "starboard/types.h"
+
+#define PR_CALLBACK
+
+#define PR_MSEC_PER_SEC 1000L
+#define PR_USEC_PER_SEC 1000000L
+#define PR_NSEC_PER_SEC 1000000000L
+#define PR_USEC_PER_MSEC 1000L
+#define PR_NSEC_PER_MSEC 1000000L
+
+typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus;
+
+namespace pr_starboard {
+
+// Utility function to map true to PR_SUCCESS and false to PR_FAILURE.
+static inline PRStatus ToPRStatus(bool result) {
+  return result ? PR_SUCCESS : PR_FAILURE;
+}
+
+}  // namespace pr_starboard
+
+typedef enum PRThreadPriority {
+  PR_PRIORITY_FIRST = 0,
+  PR_PRIORITY_LOW = 0,
+  PR_PRIORITY_NORMAL = 1,
+  PR_PRIORITY_HIGH = 2,
+  PR_PRIORITY_URGENT = 3,
+  PR_PRIORITY_LAST = 3
+} PRThreadPriority;
+
+typedef enum PRThreadScope {
+  PR_LOCAL_THREAD,
+  PR_GLOBAL_THREAD,
+  PR_GLOBAL_BOUND_THREAD
+} PRThreadScope;
+
+typedef enum PRThreadState {
+  PR_JOINABLE_THREAD,
+  PR_UNJOINABLE_THREAD
+} PRThreadState;
+
+typedef enum PRThreadType { PR_USER_THREAD, PR_SYSTEM_THREAD } PRThreadType;
+
+typedef SbThreadLocalKey PRTLSIndex;
+typedef uint32_t PRIntervalTime;
+
+typedef int32_t PRInt32;
+typedef uint32_t PRUint32;
+
+typedef int64_t PRInt64;
+typedef uint64_t PRUint64;
+
+typedef void(PR_CALLBACK* PRThreadPrivateDTOR)(void* priv);
+
+struct PRThread {
+  explicit PRThread(SbThread sb_thread) : sb_thread(sb_thread) {}
+  SbThread sb_thread;
+};
+
+typedef SbMutex PRLock;
+
+#define PR_INTERVAL_NO_WAIT 0UL
+#define PR_INTERVAL_NO_TIMEOUT 0xffffffffUL
+
+struct PRCondVar {
+  SbConditionVariable sb_condition_variable;
+  SbMutex* lock;
+};
+
+PRLock* PR_NewLock();
+
+static inline void PR_Lock(PRLock* lock) {
+  SbMutexAcquire(lock);
+}
+
+static inline void PR_Unlock(PRLock* lock) {
+  SbMutexRelease(lock);
+}
+
+void PR_DestroyLock(PRLock* lock);
+
+PRCondVar* PR_NewCondVar(PRLock* lock);
+
+void PR_DestroyCondVar(PRCondVar* cvar);
+
+PRStatus PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout);
+
+static inline PRStatus PR_NotifyCondVar(PRCondVar* cvar) {
+  return pr_starboard::ToPRStatus(
+      SbConditionVariableSignal(&cvar->sb_condition_variable));
+}
+
+static inline PRStatus PR_NotifyAllCondVar(PRCondVar* cvar) {
+  return pr_starboard::ToPRStatus(
+      SbConditionVariableBroadcast(&cvar->sb_condition_variable));
+}
+
+typedef void (*PRThreadEntryPoint)(void*);
+
+PRThread* PR_CreateThread(PRThreadType type,
+                          PRThreadEntryPoint start,
+                          void* arg,
+                          PRThreadPriority priority,
+                          PRThreadScope scope,
+                          PRThreadState state,
+                          PRUint32 stackSize);
+
+static inline PRStatus PR_JoinThread(PRThread* pr_thread) {
+  SB_DCHECK(pr_thread);
+  SB_DCHECK(SbThreadIsValid(pr_thread->sb_thread));
+  return pr_starboard::ToPRStatus(SbThreadJoin(pr_thread->sb_thread, NULL));
+}
+
+PRThread* PR_GetCurrentThread();
+
+void PR_DetachThread();
+
+PRStatus PR_NewThreadPrivateIndex(PRTLSIndex* newIndex,
+                                  PRThreadPrivateDTOR destructor);
+
+static inline PRStatus PR_SetThreadPrivate(PRTLSIndex index, void* priv) {
+  return pr_starboard::ToPRStatus(SbThreadSetLocalValue(index, priv));
+}
+
+static inline void* PR_GetThreadPrivate(PRTLSIndex index) {
+  return SbThreadGetLocalValue(index);
+}
+
+static inline void PR_SetCurrentThreadName(const char* name) {
+  SbThreadSetName(name);
+}
+
+static inline PRUint32 PR_vsnprintf(char* out,
+                                    PRUint32 outlen,
+                                    const char* fmt,
+                                    va_list ap) {
+  return static_cast<PRUint32>(SbStringFormat(out, outlen, fmt, ap));
+}
+
+PRUint32 PR_snprintf(char* out, PRUint32 outlen, const char* fmt, ...);
+
+PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli);
+
+static inline PRIntervalTime PR_MicrosecondsToInterval(PRUint32 micro) {
+  return (micro + 999) / 1000;
+}
+
+PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks);
+
+struct PRCallOnceType {};
+typedef PRStatus(PR_CALLBACK* PRCallOnceWithArgFN)(void* arg);
+
+PRStatus PR_CallOnceWithArg(PRCallOnceType* once,
+                            PRCallOnceWithArgFN func,
+                            void* arg);
+
+static inline PRUint32 PR_TicksPerSecond() {
+  return 1000;
+}
+
+#endif  // STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_
diff --git a/src/starboard/client_porting/wrap_main/wrap_main.h b/src/starboard/client_porting/wrap_main/wrap_main.h
index e4879d3..4734772 100644
--- a/src/starboard/client_porting/wrap_main/wrap_main.h
+++ b/src/starboard/client_porting/wrap_main/wrap_main.h
@@ -50,6 +50,11 @@
 }  // namespace client_porting
 }  // namespace starboard
 
+#if defined(_WIN32)
+// Today there is no Starboard win32. Make sure those who create it know
+// the _CrtSet* functions below should be moved to starboard win32 main.
+#error For starboard win32, please move _CrtSet* to main
+#endif
 #define STARBOARD_WRAP_SIMPLE_MAIN(main_function)                \
   void SbEventHandle(const SbEvent* event) {                     \
     ::starboard::client_porting::wrap_main::SimpleEventHandler<  \
@@ -57,10 +62,26 @@
   }
 
 #else
+#if defined(_WIN32)
+#include <windows.h>
+
+// TODO this case should be removed when win32 is starboardized
+#define STARBOARD_WRAP_SIMPLE_MAIN(main_function)                             \
+  int main(int argc, char** argv) {                                           \
+    if (!IsDebuggerPresent()) {                                               \
+      _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); \
+      _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);  \
+      _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);   \
+      _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);                    \
+    }                                                                         \
+    return main_function(argc, argv);                                         \
+  }
+#else  // defined(_WIN32)
 #define STARBOARD_WRAP_SIMPLE_MAIN(main_function) \
   int main(int argc, char** argv) {               \
     return main_function(argc, argv);             \
   }
+#endif
 
 #endif  // STARBOARD
 #endif  // STARBOARD_CLIENT_PORTING_WRAP_MAIN_WRAP_MAIN_H_
diff --git a/src/starboard/common/common.cc b/src/starboard/common/common.cc
new file mode 100644
index 0000000..86c63c2
--- /dev/null
+++ b/src/starboard/common/common.cc
@@ -0,0 +1,21 @@
+// 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"
+
+// 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
new file mode 100644
index 0000000..c6a509b
--- /dev/null
+++ b/src/starboard/common/common.gyp
@@ -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.
+
+# The "common" target contains facilities provided by Starboard that are common
+# to all platforms.
+
+{
+  'targets': [
+    {
+      'target_name': 'common',
+      'type': 'static_library',
+      'variables': {
+        'includes_starboard': 1,
+      },
+      'sources': [
+        'common.cc',
+        'decode_target_provider.cc',
+        'memory.cc',
+        'move.h',
+        'ref_counted.cc',
+        'ref_counted.h',
+        'reset_and_return.h',
+        'scoped_ptr.h',
+        'thread_collision_warner.cc',
+        'thread_collision_warner.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/common/memory.cc b/src/starboard/common/memory.cc
new file mode 100644
index 0000000..4ed3362
--- /dev/null
+++ b/src/starboard/common/memory.cc
@@ -0,0 +1,222 @@
+// 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/atomic.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/memory_reporter.h"
+#include "starboard/shared/starboard/memory_reporter_internal.h"
+
+namespace {
+inline void* SbMemoryAllocateImpl(size_t size);
+inline void* SbMemoryAllocateAlignedImpl(size_t alignment, size_t size);
+inline void* SbMemoryReallocateImpl(void* memory, size_t size);
+inline void SbReportAllocation(const void* memory, size_t size);
+inline void SbReportDeallocation(const void* memory);
+
+SbMemoryReporter* s_memory_reporter = NULL;
+
+bool LeakTraceEnabled();               // True when leak tracing enabled.
+bool StarboardAllowsMemoryTracking();  // True when build enabled.
+}  // namespace.
+
+bool SbMemorySetReporter(SbMemoryReporter* reporter) {
+  // TODO: We should run a runtime test here with a test memory
+  // reporter that determines whether global operator new/delete are properly
+  // overridden. This problem appeared with address sanitizer and given
+  // how tricky operator new/delete are in general (see a google search)
+  // it's reasonable to assume that the likely hood of this happening again
+  // is high. To allow the QA/developer to take corrective action, this
+  // condition needs to be detected at runtime with a simple error message so
+  // that corrective action (i.e. running a different build) can be applied.
+  //
+  // RunOperatorNewDeleteRuntimeTest();  // Implement me.
+
+  // Flush local memory to main so that other threads don't
+  // see a partially constructed reporter due to memory
+  // re-ordering.
+  SbAtomicMemoryBarrier();
+  s_memory_reporter = reporter;
+
+  // These are straight forward error messages. We use the build settings to
+  // predict whether the MemoryReporter is likely to fail.
+  if (!StarboardAllowsMemoryTracking()) {
+    SbLogRaw("\nMemory Reporting is disabled because this build does "
+             "not support it. Try a QA, devel or debug build.\n");
+    return false;
+  } else if (LeakTraceEnabled()) {
+    SbLogRaw("\nMemory Reporting might be disabled because leak trace "
+             "(from address sanitizer?) is active.\n");
+    return false;
+  }
+  return true;
+}
+
+void* SbMemoryAllocate(size_t size) {
+  void* memory = SbMemoryAllocateImpl(size);
+  SbReportAllocation(memory, size);
+  return memory;
+}
+
+void* SbMemoryAllocateAligned(size_t alignment, size_t size) {
+  void* memory = SbMemoryAllocateAlignedImpl(alignment, size);
+  SbReportAllocation(memory, size);
+  return memory;
+}
+
+void* SbMemoryReallocate(void* memory, size_t size) {
+  SbReportDeallocation(memory);
+  void* new_memory = SbMemoryReallocateImpl(memory, size);
+  SbReportAllocation(new_memory, size);
+  return new_memory;
+}
+
+void SbMemoryDeallocate(void* memory) {
+  // Report must happen first or else a race condition allows the memory to
+  // be freed and then reported as allocated, before the allocation is removed.
+  SbReportDeallocation(memory);
+  SbMemoryFree(memory);
+}
+
+void SbMemoryDeallocateAligned(void* memory) {
+  // Report must happen first or else a race condition allows the memory to
+  // be freed and then reported as allocated, before the allocation is removed.
+  SbReportDeallocation(memory);
+  SbMemoryFreeAligned(memory);
+}
+
+// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an
+// allocation failure
+void* SbMemoryReallocateChecked(void* memory, size_t size) {
+  void* address = SbMemoryReallocateUnchecked(memory, size);
+  SbAbortIfAllocationFailed(size, address);
+  return address;
+}
+
+// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an
+// allocation failure
+void* SbMemoryAllocateAlignedChecked(size_t alignment, size_t size) {
+  void* address = SbMemoryAllocateAlignedUnchecked(alignment, size);
+  SbAbortIfAllocationFailed(size, address);
+  return address;
+}
+
+void* SbMemoryAllocateChecked(size_t size) {
+  void* address = SbMemoryAllocateUnchecked(size);
+  SbAbortIfAllocationFailed(size, address);
+  return address;
+}
+
+void SbMemoryReporterReportMappedMemory(const void* memory, size_t size) {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter)) {
+    return;
+  }
+  s_memory_reporter->on_mapmem_cb(
+      s_memory_reporter->context,
+      memory,
+      size);
+#endif  // STARBOARD_ALLOWS_MEMORY_TRACKING
+}
+
+void SbMemoryReporterReportUnmappedMemory(const void* memory, size_t size) {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter)) {
+    return;
+  }
+  s_memory_reporter->on_unmapmem_cb(
+      s_memory_reporter->context,
+      memory,
+      size);
+#endif  // STARBOARD_ALLOWS_MEMORY_TRACKING
+}
+
+namespace {  // anonymous namespace.
+
+inline void SbReportAllocation(const void* memory, size_t size) {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter)) {
+    return;
+  }
+  s_memory_reporter->on_alloc_cb(
+      s_memory_reporter->context,
+      memory,
+      size);
+#endif  // STARBOARD_ALLOWS_MEMORY_TRACKING
+}
+
+inline void SbReportDeallocation(const void* memory) {
+#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return;
+#else
+  if (SB_LIKELY(!s_memory_reporter)) {
+    return;
+  }
+  s_memory_reporter->on_dealloc_cb(
+      s_memory_reporter->context,
+      memory);
+#endif  // STARBOARD_ALLOWS_MEMORY_TRACKING
+}
+
+inline void* SbMemoryAllocateImpl(size_t size) {
+#if SB_ABORT_ON_ALLOCATION_FAILURE
+  return SbMemoryAllocateChecked(size);
+#else
+  return SbMemoryAllocateUnchecked(size);
+#endif
+}
+
+inline void* SbMemoryAllocateAlignedImpl(size_t alignment, size_t size) {
+#if SB_ABORT_ON_ALLOCATION_FAILURE
+  return SbMemoryAllocateAlignedChecked(alignment, size);
+#else
+  return SbMemoryAllocateAlignedUnchecked(alignment, size);
+#endif
+}
+
+inline void* SbMemoryReallocateImpl(void* memory, size_t size) {
+#if SB_ABORT_ON_ALLOCATION_FAILURE
+  return SbMemoryReallocateChecked(memory, size);
+#else
+  return SbMemoryReallocateUnchecked(memory, size);
+#endif
+}
+
+bool LeakTraceEnabled() {
+#if defined(HAS_LEAK_SANITIZER) && (0 == HAS_LEAK_SANITIZER)
+  // In this build the leak tracer is specifically disabled. This
+  // build condition is typical for some builds of address sanitizer.
+  return true;
+#elif defined(ADDRESS_SANITIZER)
+  // Leak tracer is not specifically disabled and address sanitizer is running.
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool StarboardAllowsMemoryTracking() {
+#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+  return true;
+#else
+  return false;
+#endif
+}
+}  // namespace
diff --git a/src/starboard/common/move.h b/src/starboard/common/move.h
new file mode 100644
index 0000000..aacd8e6
--- /dev/null
+++ b/src/starboard/common/move.h
@@ -0,0 +1,209 @@
+// 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 STARBOARD_COMMON_MOVE_H_
+#define STARBOARD_COMMON_MOVE_H_
+
+// Macro with the boilerplate that makes a type move-only in C++03.
+//
+// USAGE
+//
+// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create
+// a "move-only" type.  Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be
+// the first line in a class declaration.
+//
+// A class using this macro must call .Pass() (or somehow be an r-value already)
+// before it can be:
+//
+//   * Passed as a function argument
+//   * Used as the right-hand side of an assignment
+//   * Returned from a function
+//
+// Each class will still need to define their own "move constructor" and "move
+// operator=" to make this useful.  Here's an example of the macro, the move
+// constructor, and the move operator= from the scoped_ptr class:
+//
+//  template <typename T>
+//  class scoped_ptr {
+//     MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue)
+//   public:
+//    scoped_ptr(RValue& other) : ptr_(other.release()) { }
+//    scoped_ptr& operator=(RValue& other) {
+//      swap(other);
+//      return *this;
+//    }
+//  };
+//
+// Note that the constructor must NOT be marked explicit.
+//
+// For consistency, the second parameter to the macro should always be RValue
+// unless you have a strong reason to do otherwise.  It is only exposed as a
+// macro parameter so that the move constructor and move operator= don't look
+// like they're using a phantom type.
+//
+//
+// HOW THIS WORKS
+//
+// For a thorough explanation of this technique, see:
+//
+//   http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor
+//
+// The summary is that we take advantage of 2 properties:
+//
+//   1) non-const references will not bind to r-values.
+//   2) C++ can apply one user-defined conversion when initializing a
+//      variable.
+//
+// The first lets us disable the copy constructor and assignment operator
+// by declaring private version of them with a non-const reference parameter.
+//
+// For l-values, direct initialization still fails like in
+// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment
+// operators are private.
+//
+// For r-values, the situation is different. The copy constructor and
+// assignment operator are not viable due to (1), so we are trying to call
+// a non-existent constructor and non-existing operator= rather than a private
+// one.  Since we have not committed an error quite yet, we can provide an
+// alternate conversion sequence and a constructor.  We add
+//
+//   * a private struct named "RValue"
+//   * a user-defined conversion "operator RValue()"
+//   * a "move constructor" and "move operator=" that take the RValue& as
+//     their sole parameter.
+//
+// Only r-values will trigger this sequence and execute our "move constructor"
+// or "move operator=."  L-values will match the private copy constructor and
+// operator= first giving a "private in this context" error.  This combination
+// gives us a move-only type.
+//
+// For signaling a destructive transfer of data from an l-value, we provide a
+// method named Pass() which creates an r-value for the current instance
+// triggering the move constructor or move operator=.
+//
+// Other ways to get r-values is to use the result of an expression like a
+// function call.
+//
+// Here's an example with comments explaining what gets triggered where:
+//
+//    class Foo {
+//      MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue);
+//
+//     public:
+//       ... API ...
+//       Foo(RValue other);           // Move constructor.
+//       Foo& operator=(RValue rhs);  // Move operator=
+//    };
+//
+//    Foo MakeFoo();  // Function that returns a Foo.
+//
+//    Foo f;
+//    Foo f_copy(f);  // ERROR: Foo(Foo&) is private in this context.
+//    Foo f_assign;
+//    f_assign = f;   // ERROR: operator=(Foo&) is private in this context.
+//
+//
+//    Foo f(MakeFoo());      // R-value so alternate conversion executed.
+//    Foo f_copy(f.Pass());  // R-value so alternate conversion executed.
+//    f = f_copy.Pass();     // R-value so alternate conversion executed.
+//
+//
+// IMPLEMENTATION SUBTLETIES WITH RValue
+//
+// The RValue struct is just a container for a pointer back to the original
+// object. It should only ever be created as a temporary, and no external
+// class should ever declare it or use it in a parameter.
+//
+// It is tempting to want to use the RValue type in function parameters, but
+// excluding the limited usage here for the move constructor and move
+// operator=, doing so would mean that the function could take both r-values
+// and l-values equially which is unexpected.  See COMPARED To Boost.Move for
+// more details.
+//
+// An alternate, and incorrect, implementation of the RValue class used by
+// Boost.Move makes RValue a fieldless child of the move-only type. RValue&
+// is then used in place of RValue in the various operators.  The RValue& is
+// "created" by doing *reinterpret_cast<RValue*>(this).  This has the appeal
+// of never creating a temproary RValue struct even with optimizations
+// disabled.  Also, by virtue of inheritance you can treat the RValue
+// reference as if it were the move-only type itself.  Unfortuantely,
+// using the result of this reinterpret_cast<> is actually undefined behavior
+// due to C++98 5.2.10.7. In certain compilers (eg., NaCl) the optimizer
+// will generate non-working code.
+//
+// In optimized builds, both implementations generate the same assembly so we
+// choose the one that adheres to the standard.
+//
+//
+// COMPARED TO C++11
+//
+// In C++11, you would implement this functionality using an r-value reference
+// and our .Pass() method would be replaced with a call to std::move().
+//
+// This emulation also has a deficiency where it uses up the single
+// user-defined conversion allowed by C++ during initialization.  This can
+// cause problems in some API edge cases.  For instance, in scoped_ptr, it is
+// impossible to make an function "void Foo(scoped_ptr<Parent> p)" accept a
+// value of type scoped_ptr<Child> even if you add a constructor to
+// scoped_ptr<> that would make it look like it should work.  C++11 does not
+// have this deficiency.
+//
+//
+// COMPARED TO Boost.Move
+//
+// Our implementation similar to Boost.Move, but we keep the RValue struct
+// private to the move-only type, and we don't use the reinterpret_cast<> hack.
+//
+// In Boost.Move, RValue is the boost::rv<> template.  This type can be used
+// when writing APIs like:
+//
+//   void MyFunc(boost::rv<Foo>& f)
+//
+// that can take advantage of rv<> to avoid extra copies of a type.  However you
+// would still be able to call this version of MyFunc with an l-value:
+//
+//   Foo f;
+//   MyFunc(f);  // Uh oh, we probably just destroyed |f| w/o calling Pass().
+//
+// unless someone is very careful to also declare a parallel override like:
+//
+//   void MyFunc(const Foo& f)
+//
+// that would catch the l-values first.  This was declared unsafe in C++11 and
+// a C++11 compiler will explicitly fail MyFunc(f).  Unfortunately, we cannot
+// ensure this in C++03.
+//
+// Since we have no need for writing such APIs yet, our implementation keeps
+// RValue private and uses a .Pass() method to do the conversion instead of
+// trying to write a version of "std::move()." Writing an API like std::move()
+// would require the RValue structs to be public.
+//
+//
+// CAVEATS
+//
+// If you include a move-only type as a field inside a class that does not
+// explicitly declare a copy constructor, the containing class's implicit
+// copy constructor will change from Containing(const Containing&) to
+// Containing(Containing&).  This can cause some unexpected errors.
+//
+//   http://llvm.org/bugs/show_bug.cgi?id=11528
+//
+// The workaround is to explicitly declare your copy constructor.
+//
+#define MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type)   \
+ private:                                              \
+  struct rvalue_type {                                 \
+    rvalue_type(type* object) : object(object) {}      \
+    type* object;                                      \
+  };                                                   \
+  type(type&);                                         \
+  void operator=(type&);                               \
+                                                       \
+ public:                                               \
+  operator rvalue_type() { return rvalue_type(this); } \
+  type Pass() { return type(rvalue_type(this)); }      \
+                                                       \
+ private:
+
+#endif  // STARBOARD_COMMON_MOVE_H_
diff --git a/src/starboard/common/ref_counted.cc b/src/starboard/common/ref_counted.cc
new file mode 100644
index 0000000..32f67d5
--- /dev/null
+++ b/src/starboard/common/ref_counted.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 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 "starboard/common/ref_counted.h"
+
+#include "starboard/log.h"
+
+namespace starboard {
+
+namespace subtle {
+
+RefCountedBase::RefCountedBase()
+    : ref_count_(0)
+#ifndef NDEBUG
+      ,
+      in_dtor_(false)
+#endif
+{
+}
+
+RefCountedBase::~RefCountedBase() {
+#ifndef NDEBUG
+  SB_DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()";
+#endif
+}
+
+void RefCountedBase::AddRef() const {
+// TODO(maruel): Add back once it doesn't assert 500 times/sec.
+// Current thread books the critical section "AddRelease" without release it.
+// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_);
+#ifndef NDEBUG
+  SB_DCHECK(!in_dtor_);
+#endif
+  ++ref_count_;
+}
+
+bool RefCountedBase::Release() const {
+// TODO(maruel): Add back once it doesn't assert 500 times/sec.
+// Current thread books the critical section "AddRelease" without release it.
+// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_);
+#ifndef NDEBUG
+  SB_DCHECK(!in_dtor_);
+#endif
+  if (--ref_count_ == 0) {
+#ifndef NDEBUG
+    in_dtor_ = true;
+#endif
+    return true;
+  }
+  return false;
+}
+
+bool RefCountedThreadSafeBase::HasOneRef() const {
+  return (SbAtomicAcquire_Load(
+              &const_cast<RefCountedThreadSafeBase*>(this)->ref_count_) == 1);
+}
+
+RefCountedThreadSafeBase::RefCountedThreadSafeBase() : ref_count_(0) {
+#ifndef NDEBUG
+  in_dtor_ = false;
+#endif
+}
+
+RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
+#ifndef NDEBUG
+  SB_DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without "
+                         "calling Release()";
+#endif
+}
+
+void RefCountedThreadSafeBase::AddRef() const {
+#ifndef NDEBUG
+  SB_DCHECK(!in_dtor_);
+#endif
+  SbAtomicNoBarrier_Increment(&ref_count_, 1);
+}
+
+bool RefCountedThreadSafeBase::Release() const {
+#ifndef NDEBUG
+  SB_DCHECK(!in_dtor_);
+  SB_DCHECK(!(SbAtomicAcquire_Load(&ref_count_) == 0));
+#endif
+  if (SbAtomicBarrier_Increment(&ref_count_, -1) == 0) {
+#ifndef NDEBUG
+    in_dtor_ = true;
+#endif
+    return true;
+  }
+  return false;
+}
+
+}  // namespace subtle
+
+}  // namespace starboard
diff --git a/src/starboard/common/ref_counted.h b/src/starboard/common/ref_counted.h
new file mode 100644
index 0000000..0ab74db
--- /dev/null
+++ b/src/starboard/common/ref_counted.h
@@ -0,0 +1,288 @@
+// 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 STARBOARD_COMMON_REF_COUNTED_H_
+#define STARBOARD_COMMON_REF_COUNTED_H_
+
+#include "starboard/atomic.h"
+#include "starboard/common/thread_collision_warner.h"
+#include "starboard/log.h"
+
+namespace starboard {
+
+namespace subtle {
+
+class RefCountedBase {
+ public:
+  bool HasOneRef() const { return ref_count_ == 1; }
+
+ protected:
+  RefCountedBase();
+  ~RefCountedBase();
+
+  void AddRef() const;
+
+  // Returns true if the object should self-delete.
+  bool Release() const;
+
+ private:
+  mutable int ref_count_;
+#ifndef NDEBUG
+  mutable bool in_dtor_;
+#endif
+
+  DFAKE_MUTEX(add_release_);
+};
+
+class RefCountedThreadSafeBase {
+ public:
+  bool HasOneRef() const;
+
+ protected:
+  RefCountedThreadSafeBase();
+  ~RefCountedThreadSafeBase();
+
+  void AddRef() const;
+
+  // Returns true if the object should self-delete.
+  bool Release() const;
+
+ private:
+  mutable SbAtomic32 ref_count_;
+#ifndef NDEBUG
+  mutable bool in_dtor_;
+#endif
+};
+
+}  // namespace subtle
+
+//
+// A base class for reference counted classes.  Otherwise, known as a cheap
+// knock-off of WebKit's RefCounted<T> class.  To use this guy just extend your
+// class from it like so:
+//
+//   class MyFoo : public starboard::RefCounted<MyFoo> {
+//    ...
+//    private:
+//     friend class starboard::RefCounted<MyFoo>;
+//     ~MyFoo();
+//   };
+//
+// You should always make your destructor private, to avoid any code deleting
+// the object accidently while there are references to it.
+template <class T>
+class RefCounted : public subtle::RefCountedBase {
+ public:
+  RefCounted() {}
+
+  void AddRef() const { subtle::RefCountedBase::AddRef(); }
+
+  void Release() const {
+    if (subtle::RefCountedBase::Release()) {
+      delete static_cast<const T*>(this);
+    }
+  }
+
+ protected:
+  ~RefCounted() {}
+};
+
+// Forward declaration.
+template <class T, typename Traits>
+class RefCountedThreadSafe;
+
+// Default traits for RefCountedThreadSafe<T>.  Deletes the object when its ref
+// count reaches 0.  Overload to delete it on a different thread etc.
+template <typename T>
+struct DefaultRefCountedThreadSafeTraits {
+  static void Destruct(const T* x) {
+    // Delete through RefCountedThreadSafe to make child classes only need to be
+    // friend with RefCountedThreadSafe instead of this struct, which is an
+    // implementation detail.
+    RefCountedThreadSafe<T, DefaultRefCountedThreadSafeTraits>::DeleteInternal(
+        x);
+  }
+};
+
+//
+// A thread-safe variant of RefCounted<T>
+//
+//   class MyFoo : public starboard::RefCountedThreadSafe<MyFoo> {
+//    ...
+//   };
+//
+// If you're using the default trait, then you should add compile time
+// asserts that no one else is deleting your object.  i.e.
+//    private:
+//     friend class starboard::RefCountedThreadSafe<MyFoo>;
+//     ~MyFoo();
+template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T> >
+class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
+ public:
+  RefCountedThreadSafe() {}
+
+  void AddRef() const { subtle::RefCountedThreadSafeBase::AddRef(); }
+
+  void Release() const {
+    if (subtle::RefCountedThreadSafeBase::Release()) {
+      Traits::Destruct(static_cast<const T*>(this));
+    }
+  }
+
+ protected:
+  ~RefCountedThreadSafe() {}
+
+ private:
+  friend struct DefaultRefCountedThreadSafeTraits<T>;
+  static void DeleteInternal(const T* x) { delete x; }
+};
+
+//
+// A thread-safe wrapper for some piece of data so we can place other
+// things in scoped_refptrs<>.
+//
+template <typename T>
+class RefCountedData : public starboard::RefCountedThreadSafe<starboard::RefCountedData<T> > {
+ public:
+  RefCountedData() : data() {}
+  RefCountedData(const T& in_value) : data(in_value) {}
+
+  T data;
+
+ private:
+  friend class starboard::RefCountedThreadSafe<starboard::RefCountedData<T> >;
+  ~RefCountedData() {}
+};
+
+//
+// A smart pointer class for reference counted objects.  Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference.  Sample usage:
+//
+//   class MyFoo : public RefCounted<MyFoo> {
+//    ...
+//   };
+//
+//   void some_function() {
+//     scoped_refptr<MyFoo> foo = new MyFoo();
+//     foo->Method(param);
+//     // |foo| is released when this function returns
+//   }
+//
+//   void some_other_function() {
+//     scoped_refptr<MyFoo> foo = new MyFoo();
+//     ...
+//     foo = NULL;  // explicitly releases |foo|
+//     ...
+//     if (foo)
+//       foo->Method(param);
+//   }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+//   {
+//     scoped_refptr<MyFoo> a = new MyFoo();
+//     scoped_refptr<MyFoo> b;
+//
+//     b.swap(a);
+//     // now, |b| references the MyFoo object, and |a| references NULL.
+//   }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+//   {
+//     scoped_refptr<MyFoo> a = new MyFoo();
+//     scoped_refptr<MyFoo> b;
+//
+//     b = a;
+//     // now, |a| and |b| each own a reference to the same MyFoo object.
+//   }
+//
+template <class T>
+class scoped_refptr {
+ public:
+  typedef T element_type;
+
+  scoped_refptr() : ptr_(NULL) {}
+
+  scoped_refptr(T* p) : ptr_(p) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  template <typename U>
+  scoped_refptr(const scoped_refptr<U>& r)
+      : ptr_(r.get()) {
+    if (ptr_)
+      ptr_->AddRef();
+  }
+
+  ~scoped_refptr() {
+    if (ptr_)
+      ptr_->Release();
+  }
+
+  T* get() const { return ptr_; }
+  operator T*() const { return ptr_; }
+  T* operator->() const {
+    SB_DCHECK(ptr_ != NULL);
+    return ptr_;
+  }
+
+  T& operator*() const {
+    SB_DCHECK(ptr_ != NULL);
+    return *ptr_;
+  }
+
+  scoped_refptr<T>& operator=(T* p) {
+    // AddRef first so that self assignment should work
+    if (p)
+      p->AddRef();
+    T* old_ptr = ptr_;
+    ptr_ = p;
+    if (old_ptr)
+      old_ptr->Release();
+    return *this;
+  }
+
+  scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
+    return * this = r.ptr_;
+  }
+
+  template <typename U>
+  scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
+    return * this = r.get();
+  }
+
+  void swap(T** pp) {
+    T* p = ptr_;
+    ptr_ = *pp;
+    *pp = p;
+  }
+
+  void swap(scoped_refptr<T>& r) { swap(&r.ptr_); }
+
+ protected:
+  T* ptr_;
+};
+
+// Handy utility for creating a scoped_refptr<T> out of a T* explicitly without
+// having to retype all the template arguments
+template <typename T>
+scoped_refptr<T> make_scoped_refptr(T* t) {
+  return scoped_refptr<T>(t);
+}
+
+}  // namespace starboard
+
+#endif  // STARBOARD_COMMON_REF_COUNTED_H_
diff --git a/src/starboard/common/reset_and_return.h b/src/starboard/common/reset_and_return.h
new file mode 100644
index 0000000..c881e95
--- /dev/null
+++ b/src/starboard/common/reset_and_return.h
@@ -0,0 +1,31 @@
+// 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_COMMON_RESET_AND_RETURN_H_
+#define STARBOARD_COMMON_RESET_AND_RETURN_H_
+
+namespace starboard {
+namespace common {
+
+template <typename T>
+T ResetAndReturn(T* t) {
+  T result(*t);
+  *t = T();
+  return result;
+}
+
+}  // namespace common
+}  // namespace starboard
+
+#endif  // STARBOARD_COMMON_RESET_AND_RETURN_H_
diff --git a/src/starboard/common/scoped_ptr.h b/src/starboard/common/scoped_ptr.h
new file mode 100644
index 0000000..b62f1c6
--- /dev/null
+++ b/src/starboard/common/scoped_ptr.h
@@ -0,0 +1,491 @@
+// 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.
+
+// Scopers help you manage ownership of a pointer, helping you easily manage the
+// a pointer within a scope, and automatically destroying the pointer at the
+// end of a scope.  There are two main classes you will use, which correspond
+// to the operators new/delete and new[]/delete[].
+//
+// Example usage (scoped_ptr):
+//   {
+//     scoped_ptr<Foo> foo(new Foo("wee"));
+//   }  // foo goes out of scope, releasing the pointer with it.
+//
+//   {
+//     scoped_ptr<Foo> foo;          // No pointer managed.
+//     foo.reset(new Foo("wee"));    // Now a pointer is managed.
+//     foo.reset(new Foo("wee2"));   // Foo("wee") was destroyed.
+//     foo.reset(new Foo("wee3"));   // Foo("wee2") was destroyed.
+//     foo->Method();                // Foo::Method() called.
+//     foo.get()->Method();          // Foo::Method() called.
+//     SomeFunc(foo.release());      // SomeFunc takes ownership, foo no longer
+//                                   // manages a pointer.
+//     foo.reset(new Foo("wee4"));   // foo manages a pointer again.
+//     foo.reset();                  // Foo("wee4") destroyed, foo no longer
+//                                   // manages a pointer.
+//   }  // foo wasn't managing a pointer, so nothing was destroyed.
+//
+// Example usage (scoped_array):
+//   {
+//     scoped_array<Foo> foo(new Foo[100]);
+//     foo.get()->Method();  // Foo::Method on the 0th element.
+//     foo[10].Method();     // Foo::Method on the 10th element.
+//   }
+//
+// These scopers also implement part of the functionality of C++11 unique_ptr
+// in that they are "movable but not copyable."  You can use the scopers in
+// the parameter and return types of functions to signify ownership transfer
+// in to and out of a function.  When calling a function that has a scoper
+// as the argument type, it must be called with the result of an analogous
+// scoper's Pass() function or another function that generates a temporary;
+// passing by copy will NOT work.  Here is an example using scoped_ptr:
+//
+//   void TakesOwnership(scoped_ptr<Foo> arg) {
+//     // Do something with arg
+//   }
+//   scoped_ptr<Foo> CreateFoo() {
+//     // No need for calling Pass() because we are constructing a temporary
+//     // for the return value.
+//     return scoped_ptr<Foo>(new Foo("new"));
+//   }
+//   scoped_ptr<Foo> PassThru(scoped_ptr<Foo> arg) {
+//     return arg.Pass();
+//   }
+//
+//   {
+//     scoped_ptr<Foo> ptr(new Foo("yay"));  // ptr manages Foo("yay").
+//     TakesOwnership(ptr.Pass());           // ptr no longer owns Foo("yay").
+//     scoped_ptr<Foo> ptr2 = CreateFoo();   // ptr2 owns the return Foo.
+//     scoped_ptr<Foo> ptr3 =                // ptr3 now owns what was in ptr2.
+//         PassThru(ptr2.Pass());            // ptr2 is correspondingly NULL.
+//   }
+//
+// Notice that if you do not call Pass() when returning from PassThru(), or
+// when invoking TakesOwnership(), the code will not compile because scopers
+// are not copyable; they only implement move semantics which require calling
+// the Pass() function to signify a destructive transfer of state. CreateFoo()
+// is different though because we are constructing a temporary on the return
+// line and thus can avoid needing to call Pass().
+//
+// Pass() properly handles upcast in assignment, i.e. you can assign
+// scoped_ptr<Child> to scoped_ptr<Parent>:
+//
+//   scoped_ptr<Foo> foo(new Foo());
+//   scoped_ptr<FooParent> parent = foo.Pass();
+//
+// PassAs<>() should be used to upcast return value in return statement:
+//
+//   scoped_ptr<Foo> CreateFoo() {
+//     scoped_ptr<FooChild> result(new FooChild());
+//     return result.PassAs<Foo>();
+//   }
+//
+// Note that PassAs<>() is implemented only for scoped_ptr, but not for
+// scoped_array. This is because casting array pointers may not be safe.
+
+#ifndef STARBOARD_COMMON_SCOPED_PTR_H_
+#define STARBOARD_COMMON_SCOPED_PTR_H_
+
+// This is an implementation designed to match the anticipated future TR2
+// implementation of the scoped_ptr class, and its closely-related brethren,
+// scoped_array, scoped_ptr_malloc.
+
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/types.h"
+
+#include <algorithm>
+
+#include "starboard/common/move.h"
+
+namespace starboard {
+
+// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T>
+// automatically deletes the pointer it holds (if any).
+// That is, scoped_ptr<T> owns the T object that it points to.
+// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object.
+// Also like T*, scoped_ptr<T> is thread-compatible, and once you
+// dereference it, you get the thread safety guarantees of T.
+//
+// The size of a scoped_ptr is small:
+// sizeof(scoped_ptr<C>) == sizeof(C*)
+template <class C>
+class scoped_ptr {
+  MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue)
+
+ public:
+  // The element type
+  typedef C element_type;
+
+  // Constructor.  Defaults to initializing with NULL.
+  // There is no way to create an uninitialized scoped_ptr.
+  // The input parameter must be allocated with new.
+  explicit scoped_ptr(C* p = NULL) : ptr_(p) {}
+
+// The GHS compiler always chooses this copy constructor over the next one,
+// so disable this to promote the more important and frequently used constr.
+#if !defined(COMPILER_GHS)
+  // Constructor.  Allows construction from a scoped_ptr rvalue for a
+  // convertible type.
+  template <typename U>
+  scoped_ptr(scoped_ptr<U> other)
+      : ptr_(other.release()) {}
+#endif
+
+  // Constructor.  Move constructor for C++03 move emulation of this type.
+  scoped_ptr(RValue rvalue) : ptr_(rvalue.object->release()) {}
+
+  // Destructor.  If there is a C object, delete it.
+  // We don't need to test ptr_ == NULL because C++ does that for us.
+  ~scoped_ptr() {
+    enum { type_must_be_complete = sizeof(C) };
+    delete ptr_;
+  }
+
+  // operator=.  Allows assignment from a scoped_ptr rvalue for a convertible
+  // type.
+  template <typename U>
+  scoped_ptr& operator=(scoped_ptr<U> rhs) {
+    reset(rhs.release());
+    return *this;
+  }
+
+  // operator=.  Move operator= for C++03 move emulation of this type.
+  scoped_ptr& operator=(RValue rhs) {
+    swap(*rhs->object);
+    return *this;
+  }
+
+  // Reset.  Deletes the current owned object, if any.
+  // Then takes ownership of a new object, if given.
+  // this->reset(this->get()) works.
+  void reset(C* p = NULL) {
+    if (p != ptr_) {
+      enum { type_must_be_complete = sizeof(C) };
+      delete ptr_;
+      ptr_ = p;
+    }
+  }
+
+  // Accessors to get the owned object.
+  // operator* and operator-> will SB_DCHECK() if there is no current object.
+  C& operator*() const {
+    SB_DCHECK(ptr_ != NULL);
+    return *ptr_;
+  }
+  C* operator->() const {
+    SB_DCHECK(ptr_ != NULL);
+    return ptr_;
+  }
+  C* get() const { return ptr_; }
+
+  // Allow scoped_ptr<C> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  typedef C* scoped_ptr::*Testable;
+  operator Testable() const { return ptr_ ? &scoped_ptr::ptr_ : NULL; }
+
+  // Comparison operators.
+  // These return whether two scoped_ptr refer to the same object, not just to
+  // two different but equal objects.
+  bool operator==(C* p) const { return ptr_ == p; }
+  bool operator!=(C* p) const { return ptr_ != p; }
+
+  // Swap two scoped pointers.
+  void swap(scoped_ptr& p2) {
+    C* tmp = ptr_;
+    ptr_ = p2.ptr_;
+    p2.ptr_ = tmp;
+  }
+
+  // Release a pointer.
+  // The return value is the current pointer held by this object.
+  // If this object holds a NULL pointer, the return value is NULL.
+  // After this operation, this object will hold a NULL pointer,
+  // and will not own the object any more.
+  C* release() {
+    C* retVal = ptr_;
+    ptr_ = NULL;
+    return retVal;
+  }
+
+  template <typename PassAsType>
+  scoped_ptr<PassAsType> PassAs() {
+    return scoped_ptr<PassAsType>(release());
+  }
+
+ private:
+  C* ptr_;
+
+  // Forbid comparison of scoped_ptr types.  If C2 != C, it totally doesn't
+  // make sense, and if C2 == C, it still doesn't make sense because you should
+  // never have the same object owned by two different scoped_ptrs.
+  template <class C2>
+  bool operator==(scoped_ptr<C2> const& p2) const;
+  template <class C2>
+  bool operator!=(scoped_ptr<C2> const& p2) const;
+};
+
+// Free functions
+template <class C>
+void swap(scoped_ptr<C>& p1, scoped_ptr<C>& p2) {
+  p1.swap(p2);
+}
+
+template <class C>
+bool operator==(C* p1, const scoped_ptr<C>& p2) {
+  return p1 == p2.get();
+}
+
+template <class C>
+bool operator!=(C* p1, const scoped_ptr<C>& p2) {
+  return p1 != p2.get();
+}
+
+// scoped_array<C> is like scoped_ptr<C>, except that the caller must allocate
+// with new [] and the destructor deletes objects with delete [].
+//
+// As with scoped_ptr<C>, a scoped_array<C> either points to an object
+// or is NULL.  A scoped_array<C> owns the object that it points to.
+// scoped_array<T> is thread-compatible, and once you index into it,
+// the returned objects have only the thread safety guarantees of T.
+//
+// Size: sizeof(scoped_array<C>) == sizeof(C*)
+template <class C>
+class scoped_array {
+  MOVE_ONLY_TYPE_FOR_CPP_03(scoped_array, RValue)
+
+ public:
+  // The element type
+  typedef C element_type;
+
+  // Constructor.  Defaults to initializing with NULL.
+  // There is no way to create an uninitialized scoped_array.
+  // The input parameter must be allocated with new [].
+  explicit scoped_array(C* p = NULL) : array_(p) {}
+
+  // Constructor.  Move constructor for C++03 move emulation of this type.
+  scoped_array(RValue rvalue) : array_(rvalue.object->release()) {}
+
+  // Destructor.  If there is a C object, delete it.
+  // We don't need to test ptr_ == NULL because C++ does that for us.
+  ~scoped_array() {
+    enum { type_must_be_complete = sizeof(C) };
+    delete[] array_;
+  }
+
+  // operator=.  Move operator= for C++03 move emulation of this type.
+  scoped_array& operator=(RValue rhs) {
+    swap(*rhs.object);
+    return *this;
+  }
+
+  // Reset.  Deletes the current owned object, if any.
+  // Then takes ownership of a new object, if given.
+  // this->reset(this->get()) works.
+  void reset(C* p = NULL) {
+    if (p != array_) {
+      enum { type_must_be_complete = sizeof(C) };
+      delete[] array_;
+      array_ = p;
+    }
+  }
+
+  // Get one element of the current object.
+  // Will SB_DCHECK() if there is no current object, or index i is negative.
+  C& operator[](ptrdiff_t i) const {
+    SB_DCHECK(i >= 0);
+    SB_DCHECK(array_ != NULL);
+    return array_[i];
+  }
+
+  // Get a pointer to the zeroth element of the current object.
+  // If there is no current object, return NULL.
+  C* get() const { return array_; }
+
+  // Allow scoped_array<C> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  typedef C* scoped_array::*Testable;
+  operator Testable() const { return array_ ? &scoped_array::array_ : NULL; }
+
+  // Comparison operators.
+  // These return whether two scoped_array refer to the same object, not just to
+  // two different but equal objects.
+  bool operator==(C* p) const { return array_ == p; }
+  bool operator!=(C* p) const { return array_ != p; }
+
+  // Swap two scoped arrays.
+  void swap(scoped_array& p2) {
+    C* tmp = array_;
+    array_ = p2.array_;
+    p2.array_ = tmp;
+  }
+
+  // Release an array.
+  // The return value is the current pointer held by this object.
+  // If this object holds a NULL pointer, the return value is NULL.
+  // After this operation, this object will hold a NULL pointer,
+  // and will not own the object any more.
+  C* release() {
+    C* retVal = array_;
+    array_ = NULL;
+    return retVal;
+  }
+
+ private:
+  C* array_;
+
+  // Forbid comparison of different scoped_array types.
+  template <class C2>
+  bool operator==(scoped_array<C2> const& p2) const;
+  template <class C2>
+  bool operator!=(scoped_array<C2> const& p2) const;
+};
+
+// Free functions
+template <class C>
+void swap(scoped_array<C>& p1, scoped_array<C>& p2) {
+  p1.swap(p2);
+}
+
+template <class C>
+bool operator==(C* p1, const scoped_array<C>& p2) {
+  return p1 == p2.get();
+}
+
+template <class C>
+bool operator!=(C* p1, const scoped_array<C>& p2) {
+  return p1 != p2.get();
+}
+
+// This class wraps the c library function free() in a class that can be
+// passed as a template argument to scoped_ptr_malloc below.
+class ScopedPtrMallocFree {
+ public:
+  inline void operator()(void* x) const { SbMemoryDeallocate(x); }
+};
+
+// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a
+// second template argument, the functor used to free the object.
+
+template <class C, class FreeProc = ScopedPtrMallocFree>
+class scoped_ptr_malloc {
+  MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr_malloc, RValue)
+
+ public:
+  // The element type
+  typedef C element_type;
+
+  // Constructor.  Defaults to initializing with NULL.
+  // There is no way to create an uninitialized scoped_ptr.
+  // The input parameter must be allocated with an allocator that matches the
+  // Free functor.  For the default Free functor, this is malloc, calloc, or
+  // realloc.
+  explicit scoped_ptr_malloc(C* p = NULL) : ptr_(p) {}
+
+  // Constructor.  Move constructor for C++03 move emulation of this type.
+  scoped_ptr_malloc(RValue rvalue) : ptr_(rvalue.object->release()) {}
+
+  // Destructor.  If there is a C object, call the Free functor.
+  ~scoped_ptr_malloc() { reset(); }
+
+  // operator=.  Move operator= for C++03 move emulation of this type.
+  scoped_ptr_malloc& operator=(RValue rhs) {
+    swap(*rhs.object);
+    return *this;
+  }
+
+  // Reset.  Calls the Free functor on the current owned object, if any.
+  // Then takes ownership of a new object, if given.
+  // this->reset(this->get()) works.
+  void reset(C* p = NULL) {
+    if (ptr_ != p) {
+      FreeProc free_proc;
+      free_proc(ptr_);
+      ptr_ = p;
+    }
+  }
+
+  // Get the current object.
+  // operator* and operator-> will cause an SB_DCHECK() failure if there is
+  // no current object.
+  C& operator*() const {
+    SB_DCHECK(ptr_ != NULL);
+    return *ptr_;
+  }
+
+  C* operator->() const {
+    SB_DCHECK(ptr_ != NULL);
+    return ptr_;
+  }
+
+  C* get() const { return ptr_; }
+
+  // Allow scoped_ptr_malloc<C> to be used in boolean expressions, but not
+  // implicitly convertible to a real bool (which is dangerous).
+  typedef C* scoped_ptr_malloc::*Testable;
+  operator Testable() const { return ptr_ ? &scoped_ptr_malloc::ptr_ : NULL; }
+
+  // Comparison operators.
+  // These return whether a scoped_ptr_malloc and a plain pointer refer
+  // to the same object, not just to two different but equal objects.
+  // For compatibility with the boost-derived implementation, these
+  // take non-const arguments.
+  bool operator==(C* p) const { return ptr_ == p; }
+
+  bool operator!=(C* p) const { return ptr_ != p; }
+
+  // Swap two scoped pointers.
+  void swap(scoped_ptr_malloc& b) {
+    C* tmp = b.ptr_;
+    b.ptr_ = ptr_;
+    ptr_ = tmp;
+  }
+
+  // Release a pointer.
+  // The return value is the current pointer held by this object.
+  // If this object holds a NULL pointer, the return value is NULL.
+  // After this operation, this object will hold a NULL pointer,
+  // and will not own the object any more.
+  C* release() {
+    C* tmp = ptr_;
+    ptr_ = NULL;
+    return tmp;
+  }
+
+ private:
+  C* ptr_;
+
+  // no reason to use these: each scoped_ptr_malloc should have its own object
+  template <class C2, class GP>
+  bool operator==(scoped_ptr_malloc<C2, GP> const& p) const;
+  template <class C2, class GP>
+  bool operator!=(scoped_ptr_malloc<C2, GP> const& p) const;
+};
+
+template <class C, class FP>
+inline void swap(scoped_ptr_malloc<C, FP>& a, scoped_ptr_malloc<C, FP>& b) {
+  a.swap(b);
+}
+
+template <class C, class FP>
+inline bool operator==(C* p, const scoped_ptr_malloc<C, FP>& b) {
+  return p == b.get();
+}
+
+template <class C, class FP>
+inline bool operator!=(C* p, const scoped_ptr_malloc<C, FP>& b) {
+  return p != b.get();
+}
+
+// A function to convert T* into scoped_ptr<T>
+// Doing e.g. make_scoped_ptr(new FooBarBaz<type>(arg)) is a shorter notation
+// for scoped_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg))
+template <typename T>
+scoped_ptr<T> make_scoped_ptr(T* ptr) {
+  return scoped_ptr<T>(ptr);
+}
+
+}  // namespace starboard
+
+#endif  // STARBOARD_COMMON_SCOPED_PTR_H_
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/common/thread_collision_warner.cc b/src/starboard/common/thread_collision_warner.cc
new file mode 100644
index 0000000..5a9e853
--- /dev/null
+++ b/src/starboard/common/thread_collision_warner.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2010 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 "starboard/common/thread_collision_warner.h"
+
+#include "starboard/atomic.h"
+#include "starboard/log.h"
+#include "starboard/thread.h"
+#include "starboard/types.h"
+
+namespace starboard {
+
+void DCheckAsserter::warn() {
+  SB_NOTREACHED() << "Thread Collision";
+}
+
+static SbAtomic32 CurrentThread() {
+  const SbThreadId current_thread_id = SbThreadGetId();
+  // We need to get the thread id into an atomic data type. This might be a
+  // truncating conversion, but any loss-of-information just increases the
+  // chance of a false negative, not a false positive.
+  const SbAtomic32 atomic_thread_id =
+      static_cast<SbAtomic32>(current_thread_id);
+  return atomic_thread_id;
+}
+
+void ThreadCollisionWarner::EnterSelf() {
+  // If the active thread is 0 then I'll write the current thread ID
+  // if two or more threads arrive here only one will succeed to
+  // write on valid_thread_id_ the current thread ID.
+  SbAtomic32 current_thread_id = CurrentThread();
+
+  int previous_value =
+      SbAtomicNoBarrier_CompareAndSwap(&valid_thread_id_, 0, current_thread_id);
+  if (previous_value != 0 && previous_value != current_thread_id) {
+    // gotcha! a thread is trying to use the same class and that is
+    // not current thread.
+    asserter_->warn();
+  }
+
+  SbAtomicNoBarrier_Increment(&counter_, 1);
+}
+
+void ThreadCollisionWarner::Enter() {
+  SbAtomic32 current_thread_id = CurrentThread();
+
+  if (SbAtomicNoBarrier_CompareAndSwap(&valid_thread_id_, 0,
+                                       current_thread_id) != 0) {
+    // gotcha! another thread is trying to use the same class.
+    asserter_->warn();
+  }
+
+  SbAtomicNoBarrier_Increment(&counter_, 1);
+}
+
+void ThreadCollisionWarner::Leave() {
+  if (SbAtomicBarrier_Increment(&counter_, -1) == 0) {
+    SbAtomicNoBarrier_Store(&valid_thread_id_, 0);
+  }
+}
+
+}  // namespace starboard
diff --git a/src/starboard/common/thread_collision_warner.h b/src/starboard/common/thread_collision_warner.h
new file mode 100644
index 0000000..4aefb21
--- /dev/null
+++ b/src/starboard/common/thread_collision_warner.h
@@ -0,0 +1,220 @@
+// 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 STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_
+#define STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_
+
+#include "starboard/atomic.h"
+
+// A helper class alongside macros to be used to verify assumptions about thread
+// safety of a class.
+//
+// Example: Queue implementation non thread-safe but still usable if clients
+//          are synchronized somehow.
+//
+//          In this case the macro DFAKE_SCOPED_LOCK has to be
+//          used, it checks that if a thread is inside the push/pop then
+//          noone else is still inside the pop/push
+//
+// class NonThreadSafeQueue {
+//  public:
+//   ...
+//   void push(int) { DFAKE_SCOPED_LOCK(push_pop_); ... }
+//   int pop() { DFAKE_SCOPED_LOCK(push_pop_); ... }
+//   ...
+//  private:
+//   DFAKE_MUTEX(push_pop_);
+// };
+//
+//
+// Example: Queue implementation non thread-safe but still usable if clients
+//          are synchronized somehow, it calls a method to "protect" from
+//          a "protected" method
+//
+//          In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK
+//          has to be used, it checks that if a thread is inside the push/pop
+//          then noone else is still inside the pop/push
+//
+// class NonThreadSafeQueue {
+//  public:
+//   void push(int) {
+//     DFAKE_SCOPED_LOCK(push_pop_);
+//     ...
+//   }
+//   int pop() {
+//     DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_);
+//     bar();
+//     ...
+//   }
+//   void bar() { DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); ... }
+//   ...
+//  private:
+//   DFAKE_MUTEX(push_pop_);
+// };
+//
+//
+// Example: Queue implementation not usable even if clients are synchronized,
+//          so only one thread in the class life cycle can use the two members
+//          push/pop.
+//
+//          In this case the macro DFAKE_SCOPED_LOCK_THREAD_LOCKED pins the
+//          specified
+//          critical section the first time a thread enters push or pop, from
+//          that time on only that thread is allowed to execute push or pop.
+//
+// class NonThreadSafeQueue {
+//  public:
+//   ...
+//   void push(int) { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... }
+//   int pop() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... }
+//   ...
+//  private:
+//   DFAKE_MUTEX(push_pop_);
+// };
+//
+//
+// Example: Class that has to be contructed/destroyed on same thread, it has
+//          a "shareable" method (with external synchronization) and a not
+//          shareable method (even with external synchronization).
+//
+//          In this case 3 Critical sections have to be defined
+//
+// class ExoticClass {
+//  public:
+//   ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... }
+//   ~ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... }
+//
+//   void Shareable() { DFAKE_SCOPED_LOCK(shareable_section_); ... }
+//   void NotShareable() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... }
+//   ...
+//  private:
+//   DFAKE_MUTEX(ctor_dtor_);
+//   DFAKE_MUTEX(shareable_section_);
+// };
+
+#if !defined(NDEBUG)
+
+// Defines a class member that acts like a mutex. It is used only as a
+// verification tool.
+#define DFAKE_MUTEX(obj) mutable starboard::ThreadCollisionWarner obj
+// Asserts the call is never called simultaneously in two threads. Used at
+// member function scope.
+#define DFAKE_SCOPED_LOCK(obj) \
+  starboard::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj)
+// Asserts the call is never called simultaneously in two threads. Used at
+// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks.
+#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \
+  starboard::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj)
+// Asserts the code is always executed in the same thread.
+#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \
+  starboard::ThreadCollisionWarner::Check check_##obj(&obj)
+
+#else
+
+#define DFAKE_MUTEX(obj) typedef void InternalFakeMutexType##obj
+#define DFAKE_SCOPED_LOCK(obj) ((void)0)
+#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) ((void)0)
+#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) ((void)0)
+
+#endif
+
+namespace starboard {
+
+// The class ThreadCollisionWarner uses an Asserter to notify the collision
+// AsserterBase is the interfaces and DCheckAsserter is the default asserter
+// used. During the unit tests is used another class that doesn't "DCHECK"
+// in case of collision (check thread_collision_warner_unittests.cc)
+struct AsserterBase {
+  virtual ~AsserterBase() {}
+  virtual void warn() = 0;
+};
+
+struct DCheckAsserter : public AsserterBase {
+  virtual ~DCheckAsserter() {}
+  virtual void warn();
+};
+
+class ThreadCollisionWarner {
+ public:
+  // The parameter asserter is there only for test purpose
+  ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter())
+      : valid_thread_id_(0), counter_(0), asserter_(asserter) {}
+
+  ~ThreadCollisionWarner() { delete asserter_; }
+
+  // This class is meant to be used through the macro
+  // DFAKE_SCOPED_LOCK_THREAD_LOCKED
+  // it doesn't leave the critical section, as opposed to ScopedCheck,
+  // because the critical section being pinned is allowed to be used only
+  // from one thread
+  class Check {
+   public:
+    explicit Check(ThreadCollisionWarner* warner) : warner_(warner) {
+      warner_->EnterSelf();
+    }
+
+    ~Check() {}
+
+   private:
+    ThreadCollisionWarner* warner_;
+  };
+
+  // This class is meant to be used through the macro
+  // DFAKE_SCOPED_LOCK
+  class ScopedCheck {
+   public:
+    explicit ScopedCheck(ThreadCollisionWarner* warner) : warner_(warner) {
+      warner_->Enter();
+    }
+
+    ~ScopedCheck() { warner_->Leave(); }
+
+   private:
+    ThreadCollisionWarner* warner_;
+  };
+
+  // This class is meant to be used through the macro
+  // DFAKE_SCOPED_RECURSIVE_LOCK
+  class ScopedRecursiveCheck {
+   public:
+    explicit ScopedRecursiveCheck(ThreadCollisionWarner* warner)
+        : warner_(warner) {
+      warner_->EnterSelf();
+    }
+
+    ~ScopedRecursiveCheck() { warner_->Leave(); }
+
+   private:
+    ThreadCollisionWarner* warner_;
+  };
+
+ private:
+  // This method stores the current thread identifier and does a DCHECK
+  // if a another thread has already done it, it is safe if same thread
+  // calls this multiple time (recursion allowed).
+  void EnterSelf();
+
+  // Same as EnterSelf but recursion is not allowed.
+  void Enter();
+
+  // Removes the thread_id stored in order to allow other threads to
+  // call EnterSelf or Enter.
+  void Leave();
+
+  // This stores the thread id that is inside the critical section, if the
+  // value is 0 then no thread is inside.
+  volatile SbAtomic32 valid_thread_id_;
+
+  // Counter to trace how many time a critical section was "pinned"
+  // (when allowed) in order to unpin it when counter_ reaches 0.
+  volatile SbAtomic32 counter_;
+
+  // Here only for class unit tests purpose, during the test I need to not
+  // DCHECK but notify the collision with something else.
+  AsserterBase* asserter_;
+};
+
+}  // namespace starboard
+
+#endif  // STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_
diff --git a/src/starboard/condition_variable.h b/src/starboard/condition_variable.h
index 463f523..4c519ba 100644
--- a/src/starboard/condition_variable.h
+++ b/src/starboard/condition_variable.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Condition variables.
+// Module Overview: Starboard Condition Variable module
+//
+// Defines an interface for condition variables.
 
 #ifndef STARBOARD_CONDITION_VARIABLE_H_
 #define STARBOARD_CONDITION_VARIABLE_H_
@@ -51,18 +53,18 @@
 }
 
 // Creates a new condition variable to work with |opt_mutex|, which may be null,
-// placing the newly created condition variable in |out_condition|. Returns
-// whether the condition variable could be created.
-// TODO: It looks like WTF does not have the mutex available when creating
-// the condition variable, and pthreads doesn't appear to require the mutex on
-// condvar creation, so we should just remove the parameter.
+// placing the newly created condition variable in |out_condition|.
+//
+// The return value indicates whether the condition variable could be created.
 SB_EXPORT bool SbConditionVariableCreate(SbConditionVariable* out_condition,
                                          SbMutex* opt_mutex);
 
-// Destroys a condition variable, returning whether the destruction was
-// successful. The condition variable specified by |condition| is
-// invalidated. Behavior is undefined if other threads are currently waiting
-// on this condition variable.
+// Destroys the specified SbConditionVariable. The return value indicates
+// whether the destruction was successful. The behavior is undefined if other
+// threads are currently waiting on this condition variable.
+//
+// |condition|: The SbConditionVariable to be destroyed. This invalidates the
+// condition variable.
 SB_EXPORT bool SbConditionVariableDestroy(SbConditionVariable* condition);
 
 // Waits for |condition|, releasing the held lock |mutex|, blocking
@@ -72,19 +74,30 @@
 SbConditionVariableWait(SbConditionVariable* condition, SbMutex* mutex);
 
 // Waits for |condition|, releasing the held lock |mutex|, blocking up to
-// |timeout_duration|, and returning the acquisition result. If
-// |timeout_duration| is less than or equal to zero, it will return as quickly
-// as possible with a kSbConditionVariableTimedOut result. Behavior is undefined
-// if |mutex| is not held.
+// |timeout_duration|, and returning the acquisition result. Behavior is
+// undefined if |mutex| is not held.
+//
+// |timeout_duration|: The maximum amount of time that function should wait
+// for |condition|. If the |timeout_duration| value is less than or equal to
+// zero, the function returns as quickly as possible with a
+// kSbConditionVariableTimedOut result.
 SB_EXPORT SbConditionVariableResult
 SbConditionVariableWaitTimed(SbConditionVariable* condition,
                              SbMutex* mutex,
                              SbTime timeout_duration);
 
-// Broadcasts to all current waiters of |condition| to stop waiting.
+// Broadcasts to all current waiters of |condition| to stop waiting. This
+// function wakes all of the threads waiting on |condition| while
+// SbConditionVariableSignal wakes a single thread.
+//
+// |condition|: The condition that should no longer be waited for.
 SB_EXPORT bool SbConditionVariableBroadcast(SbConditionVariable* condition);
 
-// Signals the next waiter of |condition| to stop waiting.
+// Signals the next waiter of |condition| to stop waiting. This function wakes
+// a single thread waiting on |condition| while SbConditionVariableBroadcast
+// wakes all threads waiting on it.
+//
+// |condition|: The condition that the waiter should stop waiting for.
 SB_EXPORT bool SbConditionVariableSignal(SbConditionVariable* condition);
 
 #ifdef __cplusplus
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index 94538d7..d21e420 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -12,13 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// A description of the current platform in lurid detail such that common code
-// never needs to actually know what the current operating system and
-// architecture are. It is both very pragmatic and canonical in that if any
-// application code finds itself needing to make a platform decision, it should
-// always define a Starboard Configuration feature instead. This implies the
-// continued existence of very narrowly-defined configuration features, but it
-// retains porting control in Starboard.
+// Module Overview: Starboard Configuration module
+//
+// Provides a description of the current platform in lurid detail so that
+// common code never needs to actually know what the current operating system
+// and architecture are.
+//
+// It is both very pragmatic and canonical in that if any application code
+// finds itself needing to make a platform decision, it should always define
+// a Starboard Configuration feature instead. This implies the continued
+// existence of very narrowly-defined configuration features, but it retains
+// porting control in Starboard.
 
 #ifndef STARBOARD_CONFIGURATION_H_
 #define STARBOARD_CONFIGURATION_H_
@@ -35,7 +39,12 @@
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MAXIMUM_API_VERSION 1
+#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 ----------------------------------------------
 
@@ -186,6 +195,26 @@
 #endif  // SB_IS(COMPILER_MSVC)
 #endif  // !defined(SB_UNLIKELY)
 
+// SB_DEPRECATED(int Foo(int bar));
+//   Annotates the function as deprecated, which will trigger a compiler
+//   warning when referenced.
+#if SB_IS(COMPILER_GCC)
+#define SB_DEPRECATED(FUNC) FUNC __attribute__((deprecated))
+#elif SB_IS(COMPILER_MSVC)
+#define SB_DEPRECATED(FUNC) __declspec(deprecated) FUNC
+#else
+// Empty definition for other compilers.
+#define SB_DEPRECATED(FUNC) FUNC
+#endif
+
+// SB_DEPRECATED_EXTERNAL(...) annotates the function as deprecated for
+// external clients, but not deprecated for starboard.
+#if defined(STARBOARD_IMPLEMENTATION)
+#define SB_DEPRECATED_EXTERNAL(FUNC) FUNC
+#else
+#define SB_DEPRECATED_EXTERNAL(FUNC) SB_DEPRECATED(FUNC)
+#endif
+
 // A macro to disallow the copy constructor and operator= functions
 // This should be used in the private: declarations for a class
 #define SB_DISALLOW_COPY_AND_ASSIGN(TypeName) \
@@ -336,6 +365,14 @@
 #error "Your platform must define SB_MAX_THREAD_NAME_LENGTH."
 #endif
 
+#if SB_VERSION(2) && !defined(SB_HAS_MICROPHONE)
+#error "Your platform must define SB_HAS_MICROPHONE in API versions 2 or later."
+#endif
+
+#if SB_VERSION(3) && !defined(SB_HAS_TIME_THREAD_NOW)
+#error "Your platform must define SB_HAS_TIME_THREAD_NOW in API 3 or later."
+#endif
+
 #if SB_HAS(PLAYER)
 #if !SB_IS(PLAYER_COMPOSITED) && !SB_IS(PLAYER_PUNCHED_OUT) && \
     !SB_IS(PLAYER_PRODUCING_TEXTURE)
@@ -364,6 +401,10 @@
 #error "Only one SB_HAS_{MANY, 1, 2, 4, 6}_CORE[S] can be defined per platform."
 #endif
 
+#if !defined(SB_HAS_THREAD_PRIORITY_SUPPORT)
+#error "Your platform must define SB_HAS_THREAD_PRIORITY_SUPPORT."
+#endif
+
 #if !defined(SB_PREFERRED_RGBA_BYTE_ORDER)
 // Legal values for SB_PREFERRED_RGBA_BYTE_ORDER are defined in this file above
 // as SB_PREFERRED_RGBA_BYTE_ORDER_*.
@@ -416,6 +457,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/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/ci20directfb/atomic_public.h b/src/starboard/creator/ci20directfb/atomic_public.h
new file mode 100644
index 0000000..2a182e7
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/atomic_public.h
@@ -0,0 +1,20 @@
+// 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_ATOMIC_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_
+
+#include "starboard/linux/shared/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/ci20directfb/starboard_platform.gyp b/src/starboard/creator/ci20directfb/starboard_platform.gyp
new file mode 100644
index 0000000..284be6b
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/starboard_platform.gyp
@@ -0,0 +1,335 @@
+# 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_base_symbolize',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/base/third_party/symbolize/demangle.cc',
+        '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
+      ],
+    },
+    {
+      'target_name': 'starboard_platform',
+      'type': 'static_library',
+      'sources': [
+        '<(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/shared/alsa/alsa_audio_sink_type.cc',
+        '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
+        '<(DEPTH)/starboard/shared/alsa/alsa_util.cc',
+        '<(DEPTH)/starboard/shared/alsa/alsa_util.h',
+        '<(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/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',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
+        '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
+        '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
+        '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
+        '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_close.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_get_next.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_open.cc',
+        '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
+        '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
+        '<(DEPTH)/starboard/shared/iso/double_floor.cc',
+        '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
+        '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_move.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_set.cc',
+        '<(DEPTH)/starboard/shared/iso/string_compare.cc',
+        '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
+        '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
+        '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
+        '<(DEPTH)/starboard/shared/iso/string_scan.cc',
+        '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
+        '<(DEPTH)/starboard/shared/iso/system_sort.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
+        '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
+        '<(DEPTH)/starboard/shared/linux/get_home_directory.cc',
+        '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
+        '<(DEPTH)/starboard/shared/linux/page_internal.cc',
+        '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
+        '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
+        '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_get_name.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/directory_create.cc',
+        '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
+        '<(DEPTH)/starboard/shared/posix/file_close.cc',
+        '<(DEPTH)/starboard/shared/posix/file_delete.cc',
+        '<(DEPTH)/starboard/shared/posix/file_exists.cc',
+        '<(DEPTH)/starboard/shared/posix/file_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/file_get_info.cc',
+        '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc',
+        '<(DEPTH)/starboard/shared/posix/file_open.cc',
+        '<(DEPTH)/starboard/shared/posix/file_read.cc',
+        '<(DEPTH)/starboard/shared/posix/file_seek.cc',
+        '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
+        '<(DEPTH)/starboard/shared/posix/file_write.cc',
+        '<(DEPTH)/starboard/shared/posix/log.cc',
+        '<(DEPTH)/starboard/shared/posix/log_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/log_format.cc',
+        '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
+        '<(DEPTH)/starboard/shared/posix/log_raw.cc',
+        '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_create.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
+        '<(DEPTH)/starboard/shared/posix/string_format.cc',
+        '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
+        '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
+        '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
+        '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
+        '<(DEPTH)/starboard/shared/pthread/once.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
+        '<(DEPTH)/starboard/shared/signal/crash_signals.h',
+        '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
+        '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
+        '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
+        '<(DEPTH)/starboard/shared/starboard/application.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
+        '<(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',
+        '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_message.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
+        '<(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/new.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+        '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
+        '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
+        '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
+        '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
+        '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
+        '<(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',
+      ],
+      'include_dirs': [
+        '<(sysroot)/mipsel-r2-hard/usr/include/directfb',
+      ],
+      '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)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+        'starboard_base_symbolize',
+      ],
+    },
+  ],
+}
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..51ca55f
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/system_get_property.cc
@@ -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.
+
+#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:
+    case kSbSystemPropertySpeechApiKey:
+      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/ci20directfb/thread_types_public.h b/src/starboard/creator/ci20directfb/thread_types_public.h
new file mode 100644
index 0000000..b531032
--- /dev/null
+++ b/src/starboard/creator/ci20directfb/thread_types_public.h
@@ -0,0 +1,20 @@
+// 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_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
+
+#include "starboard/linux/shared/thread_types_public.h"
+
+#endif  // STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/atomic_public.h b/src/starboard/creator/ci20x11/atomic_public.h
new file mode 100644
index 0000000..932fe9d
--- /dev/null
+++ b/src/starboard/creator/ci20x11/atomic_public.h
@@ -0,0 +1,20 @@
+// 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_CI20X11_ATOMIC_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
+
+#include "starboard/linux/shared/atomic_public.h"
+
+#endif  // STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/configuration_public.h b/src/starboard/creator/ci20x11/configuration_public.h
new file mode 100644
index 0000000..4c00bad
--- /dev/null
+++ b/src/starboard/creator/ci20x11/configuration_public.h
@@ -0,0 +1,20 @@
+// 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_CI20X11_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_
+
+#include "starboard/creator/shared/configuration_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/ci20x11/main.cc b/src/starboard/creator/ci20x11/main.cc
new file mode 100644
index 0000000..7086895
--- /dev/null
+++ b/src/starboard/creator/ci20x11/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/signal/crash_signals.h"
+#include "starboard/shared/signal/suspend_signals.h"
+#include "starboard/shared/x11/application_x11.h"
+
+extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) {
+  tzset();
+  starboard::shared::signal::InstallCrashSignalHandlers();
+  starboard::shared::signal::InstallSuspendSignalHandlers();
+  starboard::shared::x11::ApplicationX11 application;
+  int result = application.Run(argc, argv);
+  starboard::shared::signal::UninstallSuspendSignalHandlers();
+  starboard::shared::signal::UninstallCrashSignalHandlers();
+  return result;
+}
diff --git a/src/starboard/creator/ci20x11/starboard_platform.gyp b/src/starboard/creator/ci20x11/starboard_platform.gyp
new file mode 100644
index 0000000..d061e46
--- /dev/null
+++ b/src/starboard/creator/ci20x11/starboard_platform.gyp
@@ -0,0 +1,301 @@
+# 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_base_symbolize',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/base/third_party/symbolize/demangle.cc',
+        '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
+      ],
+    },
+    {
+      'target_name': 'starboard_platform',
+      'type': 'static_library',
+      'sources': [
+        '<(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/shared/alsa/alsa_audio_sink_type.cc',
+        '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
+        '<(DEPTH)/starboard/shared/alsa/alsa_util.cc',
+        '<(DEPTH)/starboard/shared/alsa/alsa_util.h',
+        '<(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',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc',
+        '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
+        '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
+        '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
+        '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
+        '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
+        '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_close.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_get_next.cc',
+        '<(DEPTH)/starboard/shared/iso/directory_open.cc',
+        '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
+        '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
+        '<(DEPTH)/starboard/shared/iso/double_floor.cc',
+        '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
+        '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_move.cc',
+        '<(DEPTH)/starboard/shared/iso/memory_set.cc',
+        '<(DEPTH)/starboard/shared/iso/string_compare.cc',
+        '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
+        '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
+        '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
+        '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
+        '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
+        '<(DEPTH)/starboard/shared/iso/string_scan.cc',
+        '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
+        '<(DEPTH)/starboard/shared/iso/system_sort.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
+        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
+        '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
+        '<(DEPTH)/starboard/shared/linux/get_home_directory.cc',
+        '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
+        '<(DEPTH)/starboard/shared/linux/page_internal.cc',
+        '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
+        '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
+        '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
+        '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_get_name.cc',
+        '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
+        '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/directory_create.cc',
+        '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
+        '<(DEPTH)/starboard/shared/posix/file_close.cc',
+        '<(DEPTH)/starboard/shared/posix/file_delete.cc',
+        '<(DEPTH)/starboard/shared/posix/file_exists.cc',
+        '<(DEPTH)/starboard/shared/posix/file_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/file_get_info.cc',
+        '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc',
+        '<(DEPTH)/starboard/shared/posix/file_open.cc',
+        '<(DEPTH)/starboard/shared/posix/file_read.cc',
+        '<(DEPTH)/starboard/shared/posix/file_seek.cc',
+        '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
+        '<(DEPTH)/starboard/shared/posix/file_write.cc',
+        '<(DEPTH)/starboard/shared/posix/log.cc',
+        '<(DEPTH)/starboard/shared/posix/log_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/log_format.cc',
+        '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
+        '<(DEPTH)/starboard/shared/posix/log_raw.cc',
+        '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
+        '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_create.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
+        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
+        '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
+        '<(DEPTH)/starboard/shared/posix/string_format.cc',
+        '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
+        '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
+        '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc',
+        '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
+        '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
+        '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
+        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
+        '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
+        '<(DEPTH)/starboard/shared/pthread/once.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h',
+        '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
+        '<(DEPTH)/starboard/shared/signal/crash_signals.h',
+        '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
+        '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
+        '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
+        '<(DEPTH)/starboard/shared/starboard/application.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
+        '<(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/directory_can_open.cc',
+        '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
+        '<(DEPTH)/starboard/shared/starboard/event_schedule.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_message.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
+        '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
+        '<(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_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',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
+        '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
+        '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+        '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
+        '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
+        '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
+        '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
+        '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
+        '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
+        '<(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',
+      ],
+      '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)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+        'starboard_base_symbolize',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/creator/ci20x11/system_get_property.cc b/src/starboard/creator/ci20x11/system_get_property.cc
new file mode 100644
index 0000000..9d71165
--- /dev/null
+++ b/src/starboard/creator/ci20x11/system_get_property.cc
@@ -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.
+
+#include "starboard/system.h"
+
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+namespace {
+
+const char* kFriendlyName = "Creator Ci20";
+const char* kPlatformName = "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:
+    case kSbSystemPropertySpeechApiKey:
+      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/ci20x11/thread_types_public.h b/src/starboard/creator/ci20x11/thread_types_public.h
new file mode 100644
index 0000000..7437a70
--- /dev/null
+++ b/src/starboard/creator/ci20x11/thread_types_public.h
@@ -0,0 +1,20 @@
+// 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_CI20X11_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_
+
+#include "starboard/linux/shared/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
new file mode 100644
index 0000000..4e572ea
--- /dev/null
+++ b/src/starboard/creator/shared/configuration_public.h
@@ -0,0 +1,460 @@
+// 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 shared Starboard configuration for Creator devices.
+
+#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 2
+
+// --- System Header Configuration -------------------------------------------
+
+// Any system headers listed here that are not provided by the platform will be
+// emulated in starboard/types.h.
+
+// Whether the current platform provides the standard header stdarg.h.
+#define SB_HAS_STDARG_H 1
+
+// Whether the current platform provides the standard header stdbool.h.
+#define SB_HAS_STDBOOL_H 1
+
+// Whether the current platform provides the standard header stddef.h.
+#define SB_HAS_STDDEF_H 1
+
+// Whether the current platform provides the standard header stdint.h.
+#define SB_HAS_STDINT_H 1
+
+// Whether the current platform provides the standard header inttypes.h.
+#define SB_HAS_INTTYPES_H 1
+
+// Whether the current platform provides the standard header wchar.h.
+#define SB_HAS_WCHAR_H 1
+
+// Whether the current platform provides the standard header limits.h.
+#define SB_HAS_LIMITS_H 1
+
+// Whether the current platform provides the standard header float.h.
+#define SB_HAS_FLOAT_H 1
+
+// Whether the current platform has microphone supported.
+#define SB_HAS_MICROPHONE 0
+
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 0
+
+// Type detection for wchar_t.
+#if defined(__WCHAR_MAX__) && \
+    (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
+#define SB_IS_WCHAR_T_UTF32 1
+#elif defined(__WCHAR_MAX__) && \
+    (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
+#define SB_IS_WCHAR_T_UTF16 1
+#endif
+
+// Chrome only defines these two if ARMEL or MIPSEL are defined.
+#if defined(__ARMEL__)
+// Chrome has an exclusion for iOS here, we should too when we support iOS.
+#define SB_IS_WCHAR_T_UNSIGNED 1
+#elif defined(__MIPSEL__)
+#define SB_IS_WCHAR_T_SIGNED 1
+#endif
+
+// --- Architecture Configuration --------------------------------------------
+
+// On default Linux, you must be a superuser in order to set real time
+// scheduling on threads.
+#define SB_HAS_THREAD_PRIORITY_SUPPORT 0
+
+// --- Compiler Configuration ------------------------------------------------
+
+// The platform's annotation for forcing a C function to be inlined.
+#define SB_C_FORCE_INLINE __inline__ __attribute__((always_inline))
+
+// The platform's annotation for marking a C function as suggested to be
+// inlined.
+#define SB_C_INLINE inline
+
+// The platform's annotation for marking a C function as forcibly not
+// inlined.
+#define SB_C_NOINLINE __attribute__((noinline))
+
+// The platform's annotation for marking a symbol as exported outside of the
+// current shared library.
+#define SB_EXPORT_PLATFORM __attribute__((visibility("default")))
+
+// The platform's annotation for marking a symbol as imported from outside of
+// the current linking unit.
+#define SB_IMPORT_PLATFORM
+
+// --- Extensions Configuration ----------------------------------------------
+
+// GCC/Clang doesn't define a long long hash function, except for Android and
+// Game consoles.
+#define SB_HAS_LONG_LONG_HASH 0
+
+// GCC/Clang doesn't define a string hash function, except for Game Consoles.
+#define SB_HAS_STRING_HASH 0
+
+// Desktop Linux needs a using statement for the hash functions.
+#define SB_HAS_HASH_USING 0
+
+// Set this to 1 if hash functions for custom types can be defined as a
+// hash_value() function. Otherwise, they need to be placed inside a
+// partially-specified hash struct template with an operator().
+#define SB_HAS_HASH_VALUE 0
+
+// Set this to 1 if use of hash_map or hash_set causes a deprecation warning
+// (which then breaks the build).
+#define SB_HAS_HASH_WARNING 1
+
+// The location to include hash_map on this platform.
+#define SB_HASH_MAP_INCLUDE <ext/hash_map>
+
+// C++'s hash_map and hash_set are often found in different namespaces depending
+// on the compiler.
+#define SB_HASH_NAMESPACE __gnu_cxx
+
+// The location to include hash_set on this platform.
+#define SB_HASH_SET_INCLUDE <ext/hash_set>
+
+// Define this to how this platform copies varargs blocks.
+#define SB_VA_COPY(dest, source) va_copy(dest, source)
+
+// --- Filesystem Configuration ----------------------------------------------
+
+// The current platform's maximum length of the name of a single directory
+// entry, not including the absolute path.
+#define SB_FILE_MAX_NAME 64
+
+// The current platform's maximum length of an absolute path.
+#define SB_FILE_MAX_PATH 4096
+
+// The current platform's maximum number of files that can be opened at the
+// same time by one process.
+#define SB_FILE_MAX_OPEN 256
+
+// The current platform's file path component separator character. This is the
+// character that appears after a directory in a file path. For example, the
+// absolute canonical path of the file "/path/to/a/file.txt" uses '/' as a path
+// component separator character.
+#define SB_FILE_SEP_CHAR '/'
+
+// The current platform's alternate file path component separator character.
+// This is like SB_FILE_SEP_CHAR, except if your platform supports an alternate
+// character, then you can place that here. For example, on windows machines,
+// the primary separator character is probably '\', but the alternate is '/'.
+#define SB_FILE_ALT_SEP_CHAR '/'
+
+// The current platform's search path component separator character. When
+// specifying an ordered list of absolute paths of directories to search for a
+// given reason, this is the character that appears between entries. For
+// example, the search path of "/etc/search/first:/etc/search/second" uses ':'
+// as a search path component separator character.
+#define SB_PATH_SEP_CHAR ':'
+
+// The string form of SB_FILE_SEP_CHAR.
+#define SB_FILE_SEP_STRING "/"
+
+// The string form of SB_FILE_ALT_SEP_CHAR.
+#define SB_FILE_ALT_SEP_STRING "/"
+
+// The string form of SB_PATH_SEP_CHAR.
+#define SB_PATH_SEP_STRING ":"
+
+// --- Memory Configuration --------------------------------------------------
+
+// The memory page size, which controls the size of chunks on memory that
+// allocators deal with, and the alignment of those chunks. This doesn't have to
+// be the hardware-defined physical page size, but it should be a multiple of
+// it.
+#define SB_MEMORY_PAGE_SIZE 4096
+
+// Whether this platform has and should use an MMAP function to map physical
+// memory to the virtual address space.
+#define SB_HAS_MMAP 1
+
+// Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is
+// required for platforms that want to JIT.
+#define SB_CAN_MAP_EXECUTABLE_MEMORY 1
+
+// Whether this platform has and should use an growable heap (e.g. with sbrk())
+// to map physical memory to the virtual address space.
+#define SB_HAS_VIRTUAL_REGIONS 0
+
+// Specifies the alignment for IO Buffers, in bytes. Some low-level network APIs
+// may require buffers to have a specific alignment, and this is the place to
+// specify that.
+#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
+
+// Determines the alignment that allocations should have on this platform.
+#define SB_MALLOC_ALIGNMENT ((size_t)16U)
+
+// Determines the threshhold of allocation size that should be done with mmap
+// (if available), rather than allocated within the core heap.
+#define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
+
+// Defines the path where memory debugging logs should be written to.
+#define SB_MEMORY_LOG_PATH "/tmp/starboard"
+
+// --- Thread Configuration --------------------------------------------------
+
+// Defines the maximum number of simultaneous threads for this platform. Some
+// platforms require sharing thread handles with other kinds of system handles,
+// like mutexes, so we want to keep this managable.
+#define SB_MAX_THREADS 90
+
+// The maximum number of thread local storage keys supported by this platform.
+#define SB_MAX_THREAD_LOCAL_KEYS 512
+
+// The maximum length of the name for a thread, including the NULL-terminator.
+#define SB_MAX_THREAD_NAME_LENGTH 16;
+
+// --- Graphics Configuration ------------------------------------------------
+
+// Specifies whether this platform supports a performant accelerated blitter
+// API. The basic requirement is a scaled, clipped, alpha-blended blit.
+#define SB_HAS_BLITTER 0
+
+// Specifies the preferred byte order of color channels in a pixel. Refer to
+// starboard/configuration.h for the possible values. EGL/GLES platforms should
+// generally prefer a byte order of RGBA, regardless of endianness.
+#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_RGBA
+
+// Indicates whether or not the given platform supports bilinear filtering.
+// This can be checked to enable/disable renderer tests that verify that this is
+// working properly.
+#define SB_HAS_BILINEAR_FILTERING_SUPPORT 1
+
+// Indicates whether or not the given platform supports rendering of NV12
+// textures. These textures typically originate from video decoders.
+#define SB_HAS_NV12_TEXTURE_SUPPORT 1
+
+// Whether the current platform should frequently flip their display buffer.
+// If this is not required (e.g. SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER is set
+// to 0), then optimizations where the display buffer is not flipped if the
+// scene hasn't changed are enabled.
+#define SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER 0
+
+// --- Media Configuration ---------------------------------------------------
+
+// Specifies whether this platform has support for a possibly-decrypting
+// elementary stream player for at least H.264/AAC (and AES-128-CTR, if
+// decrypting). A player is responsible for ingesting an audio and video
+// elementary stream, optionally-encrypted, and ultimately producing
+// synchronized audio/video. If a player is defined, it must choose one of the
+// supported composition methods below.
+#define SB_HAS_PLAYER 1
+
+// Specifies whether this platform's player will produce an OpenGL texture that
+// the client must draw every frame with its graphics rendering. It may be that
+// we get a texture handle, but cannot perform operations like GlReadPixels on
+// it if it is DRM-protected.
+#define SB_IS_PLAYER_PRODUCING_TEXTURE 0
+
+// Specifies whether this platform's player is composited with a formal
+// compositor, where the client must specify how video is to be composited into
+// the graphicals scene.
+#define SB_IS_PLAYER_COMPOSITED 0
+
+// Specifies whether this platform's player uses a "punch-out" model, where
+// video is rendered to the far background, and the graphics plane is
+// automatically composited on top of the video by the platform. The client must
+// punch an alpha hole out of the graphics plane for video to show through.  In
+// this case, changing the video bounds must be tightly synchronized between the
+// player and the graphics plane.
+#define SB_IS_PLAYER_PUNCHED_OUT 1
+
+// Specifies the maximum amount of memory used by audio buffers of media source
+// before triggering a garbage collection.  A large value will cause more memory
+// being used by audio buffers but will also make JavaScript app less likely to
+// re-download audio data.  Note that the JavaScript app may experience
+// significant difficulty if this value is too low.
+#define SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT (3U * 1024U * 1024U)
+
+// Specifies the maximum amount of memory used by video buffers of media source
+// before triggering a garbage collection.  A large value will cause more memory
+// being used by video buffers but will also make JavaScript app less likely to
+// re-download video data.  Note that the JavaScript app may experience
+// significant difficulty if this value is too low.
+#define SB_MEDIA_SOURCE_BUFFER_STREAM_VIDEO_MEMORY_LIMIT (16U * 1024U * 1024U)
+
+// Specifies how much memory to reserve up-front for the main media buffer
+// (usually resides inside the CPU memory) used by media source and demuxers.
+// The main media buffer can work in one of the following two ways:
+// 1. If GPU buffer is used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is non-zero), the
+//    main buffer will be used as a cache so a media buffer will be copied from
+//    GPU memory to main memory before sending to the decoder for further
+//    processing.  In this case this macro should be set to a value that is
+//    large enough to hold all media buffers being decoded.
+// 2. If GPU buffer is not used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is zero) all
+//    media buffers will reside in the main memory buffer.  In this case the
+//    macro should be set to a value that is greater than the sum of the above
+//    source buffer stream memory limits with extra room to take account of
+//    fragmentations and memory used by demuxers.
+#define SB_MEDIA_MAIN_BUFFER_BUDGET (32U * 1024U * 1024U)
+
+// Specifies how much GPU memory to reserve up-front for media source buffers.
+// This should only be set to non-zero on system with limited CPU memory and
+// excess GPU memory so the app can store media buffer in GPU memory.
+// SB_MEDIA_MAIN_BUFFER_BUDGET has to be set to a non-zero value to avoid
+// media buffers being decoded when being stored in GPU.
+#define SB_MEDIA_GPU_BUFFER_BUDGET 0U
+
+// Specifies whether this platform has webm/vp9 support.  This should be set to
+// non-zero on platforms with webm/vp9 support.
+#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0
+
+// Specifies the stack size for threads created inside media stack.  Set to 0 to
+// use the default thread stack size.  Set to non-zero to explicitly set the
+// stack size for media stack threads.
+#define SB_MEDIA_THREAD_STACK_SIZE 0U
+
+// --- Decoder-only Params ---
+
+// Specifies how media buffers must be aligned on this platform as some
+// decoders may have special requirement on the alignment of buffers being
+// decoded.
+#define SB_MEDIA_BUFFER_ALIGNMENT 128U
+
+// Specifies how video frame buffers must be aligned on this platform.
+#define SB_MEDIA_VIDEO_FRAME_ALIGNMENT 256U
+
+// The encoded video frames are compressed in different ways, their decoding
+// time can vary a lot.  Occasionally a single frame can take longer time to
+// decode than the average time per frame.  The player has to cache some frames
+// to account for such inconsistency.  The number of frames being cached are
+// controlled by the following two macros.
+//
+// Specify the number of video frames to be cached before the playback starts.
+// Note that set this value too large may increase the playback start delay.
+#define SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES 4
+
+// Specify the number of video frames to be cached during playback.  A large
+// value leads to more stable fps but also causes the app to use more memory.
+#define SB_MEDIA_MAXIMUM_VIDEO_FRAMES 12
+
+// --- Network Configuration -------------------------------------------------
+
+// Specifies whether this platform supports IPV6.
+#define SB_HAS_IPV6 1
+
+// Specifies whether this platform supports pipe.
+#define SB_HAS_PIPE 1
+
+// --- Tuneable Parameters ---------------------------------------------------
+
+// Specifies the network receive buffer size in bytes, set via
+// SbSocketSetReceiveBufferSize().
+//
+// Setting this to 0 indicates that SbSocketSetReceiveBufferSize() should
+// not be called. Use this for OSs (such as Linux) where receive buffer
+// auto-tuning is better.
+//
+// On some platforms, this may affect max TCP window size which may
+// dramatically affect throughput in the presence of latency.
+//
+// If your platform does not have a good TCP auto-tuning mechanism,
+// a setting of (128 * 1024) here is recommended.
+#define SB_NETWORK_RECEIVE_BUFFER_SIZE (0)
+
+// --- User Configuration ----------------------------------------------------
+
+// The maximum number of users that can be signed in at the same time.
+#define SB_USER_MAX_SIGNED_IN 1
+
+// --- Timing API ------------------------------------------------------------
+
+// Whether this platform has an API to retrieve how long the current thread
+// has spent in the executing state.
+#define SB_HAS_TIME_THREAD_NOW 1
+
+// --- Platform Specific Audits ----------------------------------------------
+
+#if !defined(__GNUC__)
+#error "CREATOR_SHARED builds need a GCC-like compiler (for the moment)."
+#endif
+
+#endif  // STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/shared/gyp_configuration.gypi b/src/starboard/creator/shared/gyp_configuration.gypi
new file mode 100644
index 0000000..c6cb2f2
--- /dev/null
+++ b/src/starboard/creator/shared/gyp_configuration.gypi
@@ -0,0 +1,153 @@
+# 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': {
+    'target_arch': 'mips',
+    'target_os': 'linux',
+
+    'enable_webdriver': 0,
+    'in_app_dial%': 0,
+    'gl_type%': 'system_gles3',
+    'image_cache_size_in_bytes': 32 * 1024 * 1024,
+
+    'scratch_surface_cache_size_in_bytes' : 0,
+
+    # This should have a default value in cobalt/base.gypi. See the comment
+    # there for acceptable values for this variable.
+    'javascript_engine': 'mozjs',
+    'cobalt_enable_jit': 0,
+
+    # Define platform specific compiler and linker flags.
+    # Refer to base.gypi for a list of all available variables.
+    'compiler_flags_host': [
+      '-O2',
+    ],
+    'compiler_flags': [
+      # We'll pretend not to be Linux, but Starboard instead.
+      '-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)',
+      '-EL',
+
+      # We don't wrap these symbols, but this ensures that they aren't
+      # linked in.
+      '-Wl,--wrap=malloc',
+      '-Wl,--wrap=calloc',
+      '-Wl,--wrap=realloc',
+      '-Wl,--wrap=memalign',
+      '-Wl,--wrap=reallocalign',
+      '-Wl,--wrap=free',
+      '-Wl,--wrap=strdup',
+      '-Wl,--wrap=malloc_usable_size',
+      '-Wl,--wrap=malloc_stats_fast',
+      '-Wl,--wrap=__cxa_demangle',
+    ],
+    'compiler_flags_debug': [
+      '-O0',
+    ],
+    'compiler_flags_cc_debug': [
+      '-frtti',
+    ],
+    'compiler_flags_devel': [
+      '-O2',
+    ],
+    'compiler_flags_cc_devel': [
+      '-frtti',
+    ],
+    'compiler_flags_qa': [
+      '-O2',
+    ],
+    'compiler_flags_cc_qa': [
+      '-fno-rtti',
+    ],
+    'compiler_flags_gold': [
+      '-O2',
+    ],
+    'compiler_flags_cc_gold': [
+      '-fno-rtti',
+    ],
+    'platform_libraries': [
+      '-lasound',
+      '-lavcodec',
+      '-lavformat',
+      '-lavresample',
+      '-lavutil',
+      '-lm',
+      '-lpthread',
+      '-lpulse',
+      '-lrt',
+    ],
+    'conditions': [
+      ['cobalt_fastbuild==0', {
+        'compiler_flags_debug': [
+          '-g',
+        ],
+        'compiler_flags_devel': [
+          '-g',
+        ],
+        'compiler_flags_qa': [
+        ],
+        'compiler_flags_gold': [
+        ],
+      }],
+    ],
+  },
+
+  'target_defaults': {
+    'defines': [
+      # Cobalt on Linux flag
+      'COBALT_LINUX',
+      '__STDC_FORMAT_MACROS', # so that we get PRI*
+      # Enable GNU extensions to get prototypes like ffsl.
+      '_GNU_SOURCE=1',
+    ],
+    'cflags_c': [
+      '-std=c11',
+    ],
+    'cflags_cc': [
+      '-std=gnu++11',
+      '-Wno-literal-suffix',
+    ],
+    'target_conditions': [
+      ['cobalt_code==1', {
+        'cflags': [
+          '-Wall',
+          '-Wextra',
+          '-Wunreachable-code',
+        ],
+      },{
+        'cflags': [
+          # Do not warn about unused function params.
+          '-Wno-unused-parameter',
+          # Do not warn for implicit type conversions that may change a value.
+          '-Wno-conversion',
+        ],
+      }],
+    ],
+  }, # end of target_defaults
+}
diff --git a/src/starboard/creator/shared/gyp_configuration.py b/src/starboard/creator/shared/gyp_configuration.py
new file mode 100644
index 0000000..373a70e
--- /dev/null
+++ b/src/starboard/creator/shared/gyp_configuration.py
@@ -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.
+"""Starboard Creator Ci20 platform configuration for gyp_cobalt."""
+
+import logging
+import os
+import sys
+
+import config.starboard
+
+
+class PlatformConfig(config.starboard.PlatformConfigStarboard):
+  """Starboard ci20 platform configuration."""
+
+  def __init__(self, platform):
+    super(PlatformConfig, self).__init__(platform)
+
+  def _GetCi20Home(self):
+    try:
+      ci20_home = os.environ['CI20_HOME']
+    except KeyError:
+      logging.critical('ci20 builds require the `CI20_HOME\' '
+                       'environment variable to be set.')
+      sys.exit(1)
+    return ci20_home
+
+  def GetVariables(self, configuration):
+    ci20_home = self._GetCi20Home()
+
+    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/%s to be a valid directory.',
+          relative_sysroot)
+      sys.exit(1)
+    variables = super(PlatformConfig, self).GetVariables(configuration)
+    variables.update({
+        'clang': 0,
+        'sysroot': sysroot,
+    })
+
+    return variables
+
+  def GetEnvironmentVariables(self):
+    ci20_home = self._GetCi20Home()
+
+    toolchain_bin_dir = os.path.join(ci20_home, 'mips-mti-linux-gnu',
+                                     '2016.05-03', 'bin')
+
+    env_variables = {
+        'CC': os.path.join(toolchain_bin_dir, 'mips-mti-linux-gnu-gcc'),
+        'CXX': os.path.join(toolchain_bin_dir, 'mips-mti-linux-gnu-g++'),
+        'CC_host': 'gcc',
+        'CXX_host': 'g++',
+        'LD_host': 'g++',
+        'ARFLAGS_host': 'rcs',
+        'ARTHINFLAGS_host': 'rcsT',
+    }
+    return env_variables
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
new file mode 100644
index 0000000..557c511
--- /dev/null
+++ b/src/starboard/decode_target.h
@@ -0,0 +1,307 @@
+// 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);
+//     SbDecodeTarget SbImageDecodeFoo(void* data, int data_size,
+//                                     SbDecodeTargetFormat format);
+//
+// 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, that we are on an EGL/GLES2 platform.
+// Also, we won't do any error checking, to keep things even simpler.
+//
+//     SbDecodeTarget target = SbImageDecodeFoo(encoded_foo_data,
+//                                              encoded_foo_data_size, format);
+//
+//     // 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)
+
+#if SB_HAS(BLITTER)
+#include "starboard/blitter.h"
+#elif SB_HAS(GLES2)  // SB_HAS(BLITTER)
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#endif  // SB_HAS(BLITTER)
+
+#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;
+
+  // |context| will be passed into every call to |acquire| and |release|.
+  void* context;
+} 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;
+}
+
+// Returns whether a given format is valid.
+static SB_C_INLINE bool SbDecodeTargetIsFormatValid(
+    SbDecodeTargetFormat format) {
+  return format != kSbDecodeTargetFormatInvalid;
+}
+
+#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);
+#elif SB_HAS(GLES2)  // 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);
+
+#else  // SB_HAS(BLITTER)
+
+// Stub function for when graphics aren't enabled.  Always creates
+// kSbDecodeTargetInvalid.
+static SB_C_INLINE SbDecodeTarget
+SbDecodeTargetCreate(SbDecodeTargetFormat format) {
+  SB_UNREFERENCED_PARAMETER(format);
+  return kSbDecodeTargetInvalid;
+}
+
+// Stub function for when graphics aren't enabled.  There is no concept of a
+// plane, and |NULL| is always returned.
+static SB_C_INLINE void* SbDecodeTargetGetPlane(SbDecodeTarget decode_target,
+                                                SbDecodeTargetPlane plane) {
+  SB_UNREFERENCED_PARAMETER(decode_target);
+  SB_UNREFERENCED_PARAMETER(plane);
+  return NULL;
+}
+
+#endif  // SB_HAS(BLITTER)
+
+// Destroys the given SbDecodeTarget. Note that calling this function does NOT
+// destroy the associated surfaces with it, in order to accommodate the case
+// in which it is desirable for the lifetime of the surface to outlive the
+// SbDecodeTarget that contains it. If the surfaces should be destroyed along
+// with the SbDecodeTarget, then they should be extracted with
+// |SbDecodeTargetGetPlane| and destroyed manually by the client.
+SB_EXPORT void SbDecodeTargetDestroy(SbDecodeTarget decode_target);
+
+// Gets the format that |decode_target| was created with.
+SB_EXPORT SbDecodeTargetFormat
+SbDecodeTargetGetFormat(SbDecodeTarget decode_target);
+
+// Gets whether |decode_target| is opaque or not.  The underlying source of
+// this value is expected to be properly maintained by the Starboard
+// implementation.  So, for example, if an opaque only image type were decoded
+// into an SbDecodeTarget, then the implementation would configure things in
+// such a way that this function would return true.  By opaque, it is meant
+// that all alpha values are guaranteed to be 255, if |decode_target| is of a
+// format that has alpha values.  If |decode_target| is of a format that does
+// not have alpha values, then this function should return |true|.
+SB_EXPORT bool SbDecodeTargetIsOpaque(SbDecodeTarget decode_target);
+
+// Inline convenience function to acquire an SbDecodeTarget of type |format|,
+// |width|, and |height| from |provider|.
+static SB_C_INLINE SbDecodeTarget
+SbDecodeTargetAcquireFromProvider(SbDecodeTargetProvider* provider,
+                                  SbDecodeTargetFormat format,
+                                  int width,
+                                  int height) {
+  return provider->acquire(provider->context, format, width, height);
+}
+
+// Inline convenience function to release |decode_target| back to |provider|.
+static SB_C_INLINE void SbDecodeTargetReleaseToProvider(
+    SbDecodeTargetProvider* provider,
+    SbDecodeTarget decode_target) {
+  provider->release(provider->context, decode_target);
+}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // SB_VERSION(3)
+
+#endif  // STARBOARD_DECODE_TARGET_H_
diff --git a/src/starboard/directory.h b/src/starboard/directory.h
index 728b875..0147306 100644
--- a/src/starboard/directory.h
+++ b/src/starboard/directory.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Directory listing API.
+// Module Overview: Starboard Directory module
+//
+// Provides directory listing functions.
 
 #ifndef STARBOARD_DIRECTORY_H_
 #define STARBOARD_DIRECTORY_H_
@@ -46,26 +48,43 @@
   return directory != kSbDirectoryInvalid;
 }
 
-// Opens the given existing directory for listing. Will return
-// kSbDirectoryInvalidHandle if not successful. If |out_error| is provided by
-// the caller, it will be set to the appropriate SbFileError code on failure.
+// Opens the given existing directory for listing. This function returns
+// kSbDirectoryInvalidHandle if it is not successful.
+//
+// If |out_error| is provided by the caller, it will be set to the appropriate
+// SbFileError code on failure.
+//
+// |out_error|: An output parameter that, in case of an error, is set to the
+// reason that the directory could not be opened.
 SB_EXPORT SbDirectory SbDirectoryOpen(const char* path, SbFileError* out_error);
 
-// Closes an open directory stream handle. Returns whether the close was
-// successful.
+// Closes an open directory stream handle. The return value indicates whether
+// the directory was closed successfully.
+//
+// |directory|: The directory stream handle to close.
 SB_EXPORT bool SbDirectoryClose(SbDirectory directory);
 
-// Populates |out_entry| with the next entry in that directory stream, and moves
-// the stream forward by one entry. Returns |true| if there was a next
-// directory, and |false| at the end of the directory stream.
+// Populates |out_entry| with the next entry in the specified directory stream,
+// and moves the stream forward by one entry.
+//
+// This function returns |true| if there was a next directory, and |false|
+// at the end of the directory stream.
+//
+// |directory|: The directory stream from which to retrieve the next directory.
+// |out_entry|: The variable to be populated with the next directory entry.
 SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory,
                                   SbDirectoryEntry* out_entry);
 
-// Returns whether SbDirectoryOpen is allowed for the given |path|.
+// Indicates whether SbDirectoryOpen is allowed for the given |path|.
+//
+// |path|: The path to be checked.
 SB_EXPORT bool SbDirectoryCanOpen(const char* path);
 
 // Creates the directory |path|, assuming the parent directory already exists.
-// Returns whether the directory now exists (even if it existed before).
+// This function returns |true| if the directory now exists (even if it existed
+// before) and returns |false| if the directory does not exist.
+//
+// |path|: The path to be created.
 SB_EXPORT bool SbDirectoryCreate(const char* path);
 
 #ifdef __cplusplus
diff --git a/src/starboard/double.h b/src/starboard/double.h
index bde6acc..8a28bbc 100644
--- a/src/starboard/double.h
+++ b/src/starboard/double.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Double-precision floating point helper functions.
+// Module Overview: Starboard Double module
+//
+// Provides double-precision floating point helper functions.
 
 #ifndef STARBOARD_DOUBLE_H_
 #define STARBOARD_DOUBLE_H_
@@ -25,21 +27,32 @@
 #endif
 
 // Floors double-precision floating-point number |d| to the nearest integer.
+//
+// |d|: The number to be floored.
 SB_EXPORT double SbDoubleFloor(const double d);
 
 // Returns the absolute value of the given double-precision floating-point
-// number |d|, preserving NaN and Infinity.
+// number |d|, preserving |NaN| and infinity.
+//
+// |d|: The number to be adjusted.
 SB_EXPORT double SbDoubleAbsolute(const double d);
 
 // Returns |base| taken to the power of |exponent|.
+//
+// |base|: The number to be adjusted.
+// |exponent|: The power to which the |base| number should be raised.
 SB_EXPORT double SbDoubleExponent(const double base, const double exponent);
 
 // Determines whether double-precision floating-point number |d| represents a
-// fininte number.
+// finite number.
+//
+// |d|: The number to be evaluated.
 SB_EXPORT bool SbDoubleIsFinite(const double d);
 
-// Determines whether double-precision floating-point number |d| represents "Not
-// a Number."
+// Determines whether double-precision floating-point number |d| represents
+// "Not a Number."
+//
+// |d|: The number to be evaluated.
 SB_EXPORT bool SbDoubleIsNan(const double d);
 
 #ifdef __cplusplus
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/event.h b/src/starboard/event.h
index cec1884..44cdd79 100644
--- a/src/starboard/event.h
+++ b/src/starboard/event.h
@@ -12,7 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// The event system that wraps the Starboard main loop and entry point.
+// Module Overview: Starboard Event module
+//
+// Defines the event system that wraps the Starboard main loop and entry point.
+//
+// ## The Starboard Application life cycle
+//
+// |          *
+// |          |                      _________________________
+// |        Start                   |                         |
+// |          |                     |                       Resume
+// |          V                     V                         |
+// |     [ STARTED ] --Pause--> [ PAUSED ] --Suspend--> [ SUSPENDED ]
+// |          ^                     |                         |
+// |          |                  Unpause                     Stop
+// |          |_____________________|                         |
+// |                                                          V
+// |                                                     [ STOPPED ]
+//
+// The first event that a Starboard application receives is Start
+// (kSbEventTypeStart). This puts the application in the |STARTED| state.
+// The application is in the foreground and can expect to do all of the normal
+// things it might want to do. Once in the |STARTED| state, it may receive a
+// |Pause| event, putting the application into the |PAUSED| state.
+//
+// In the |PAUSED| state, the application is still visible, but has lost
+// focus, or it is partially obscured by a modal dialog, or it is on its way
+// to being shut down. The application should pause activity in this state.
+// In this state, it can receive |Unpause| to be brought back to the foreground
+// state (|STARTED|), or |Suspend| to be pushed further in the background
+// to the |SUSPENDED| state.
+//
+// In the |SUSPENDED| state, the application is generally not visible. It
+// should immediately release all graphics and video resources, and shut down
+// all background activity (timers, rendering, etc). Additionally, the
+// application should flush storage to ensure that if the application is
+// killed, the storage will be up-to-date. The application may be killed at
+// this point, but will ideally receive a |Stop| event for a more graceful
+// shutdown.
+//
+// Note that the application is always expected to transition through |PAUSED|
+// to |SUSPENDED| before receiving |Stop| or being killed.
 
 #ifndef STARBOARD_EVENT_H_
 #define STARBOARD_EVENT_H_
@@ -26,43 +66,6 @@
 extern "C" {
 #endif
 
-// The Starboard Application life cycle
-// ------------------------------------
-//
-//           *
-//           |                      _________________________
-//         Start                   |                         |
-//           |                     |                       Resume
-//           V                     V                         |
-//      [ STARTED ] --Pause--> [ PAUSED ] --Suspend--> [ SUSPENDED ]
-//           ^                     |                         |
-//           |                  Unpause                     Stop
-//           |_____________________|                         |
-//                                                           V
-//                                                      [ STOPPED ]
-//
-// The first event that a Starboard application will receive is Start
-// (kSbEventTypeStart). This puts the application in the STARTED state; it is in
-// the foreground and can expect to do all the normal things it might want to
-// do. Once in the STARTED state, it may receive a Pause event, putting the
-// application into the PAUSED state.
-//
-// In the PAUSED state, the application is still visible, but has lost focus, or
-// it is partially obscured by a modal dialog, or it is on its way to being shut
-// down. The application should pause activity in this state. In this state, it
-// can receive Unpause to be brought back to the foreground state, STARTED, or
-// Suspend to be pushed further in the background to the SUSPENDED state.
-//
-// In the SUSPENDED state, the application is generally not visible. It should
-// immediately release all graphics and video resources, and shut down all
-// background activity (timers, rendering, etc). Additionally, the application
-// should flush storage to ensure that if the application is killed, the storage
-// will be up-to-date. The application may be killed at this point, but will
-// ideally receive a Stop event for a more graceful shutdown.
-//
-// Note that the application is always expected to transition through PAUSED to
-// SUSPENDED before receiving Stop or being killed.
-
 // An enumeration of all possible event types dispatched directly by the
 // system. Each event is accompanied by a void* data argument, and each event
 // must define the type of the value pointed to by that data argument, if any.
@@ -185,31 +188,36 @@
   return handle != kSbEventIdInvalid;
 }
 
-// Declaration of the entry point that Starboard applications MUST implement.
-// Any memory pointed at by |event| or the |data| field inside |event| is owned
-// by the system, and will be reclaimed after this function returns, so the
-// implementation must copy this data to extend its life.  This should also be
-// assumed of all fields within the data object, unless otherwise explicitly
-// specified. This function will only be called from the main Starboard thread.
-// There is no specification about what other work might happen on this thread,
-// so the application should generally do as little work as possible on this
-// thread, and just dispatch it over to another thread.
+// The entry point that Starboard applications MUST implement. Any memory
+// pointed at by |event| or the |data| field inside |event| is owned by the
+// system, and that memory is reclaimed after this function returns, so the
+// implementation must copy this data to extend its life. This behavior should
+// also be assumed of all fields within the |data| object, unless otherwise
+// explicitly specified.
+//
+// This function is only called from the main Starboard thread. There is no
+// specification about what other work might happen on this thread, so the
+// application should generally do as little work as possible on this thread,
+// and just dispatch it over to another thread.
 SB_IMPORT void SbEventHandle(const SbEvent* event);
 
-// Schedules an event |callback| into the main Starboard event loop, with
-// accompanying |context|. This function may be called from any thread, but
-// |callback| will always be called from the main Starboard thread, queued with
-// other pending events. |callback| will not be called any earlier than |delay|
-// microseconds from the time SbEventInject is called. Set |delay| to 0 to call
-// the event as soon as possible.
+// Schedules an event |callback| into the main Starboard event loop.
+// This function may be called from any thread, but |callback| is always
+// called from the main Starboard thread, queued with other pending events.
+//
+// |callback|: The callback function to be called.
+// |context|: The context that is passed to the |callback| function.
+// |delay|: The minimum number of microseconds to wait before calling the
+// |callback| function. Set |delay| to |0| to call the callback as soon as
+// possible.
 SB_EXPORT SbEventId SbEventSchedule(SbEventCallback callback,
                                     void* context,
                                     SbTime delay);
 
-// Cancels the injected |event_id|. Does nothing if the event already fired. Can
-// be safely called from any thread, but there is no guarantee that the event
-// will not run anyway unless it is called from the main Starboard event loop
-// thread.
+// Cancels the specified |event_id|. Note that this function is a no-op
+// if the event already fired. This function can be safely called from any
+// thread, but the only way to guarantee that the event does not run anyway
+// is to call it from the main Starboard event loop thread.
 SB_EXPORT void SbEventCancel(SbEventId event_id);
 
 #ifdef __cplusplus
diff --git a/src/starboard/examples/glclear/main.cc b/src/starboard/examples/glclear/main.cc
index 7d520ec..129c06c 100644
--- a/src/starboard/examples/glclear/main.cc
+++ b/src/starboard/examples/glclear/main.cc
@@ -125,7 +125,7 @@
   }
   SB_DCHECK(surface_ != EGL_NO_SURFACE);
 
-  SbMemoryFree(configs);
+  SbMemoryDeallocate(configs);
 
   eglQuerySurface(display_, surface_, EGL_WIDTH, &egl_surface_width_);
   eglQuerySurface(display_, surface_, EGL_HEIGHT, &egl_surface_height_);
diff --git a/src/starboard/examples/window/main.cc b/src/starboard/examples/window/main.cc
index 381d428..bb5bc83 100644
--- a/src/starboard/examples/window/main.cc
+++ b/src/starboard/examples/window/main.cc
@@ -35,7 +35,7 @@
                     << ", window=" << data->window
                     << ", device_type=" << data->device_type
                     << ", device_id=" << data->device_id
-                    << ", key=" << data->key
+                    << ", key=0x" << std::hex << data->key
                     << ", character=" << data->character
                     << ", modifiers=0x" << std::hex << data->key_modifiers
                     << ", location=" << std::dec << data->key_location;
diff --git a/src/starboard/export.h b/src/starboard/export.h
index af2303f..b19c1c7 100644
--- a/src/starboard/export.h
+++ b/src/starboard/export.h
@@ -12,7 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Macros to properly export or import symbols from shared libraries.
+// Module Overview: Starboard Export module
+//
+// Provides macros for properly exporting or importing symbols from shared
+// libraries.
 
 #ifndef STARBOARD_EXPORT_H_
 #define STARBOARD_EXPORT_H_
diff --git a/src/starboard/file.h b/src/starboard/file.h
index d1ecc11..0e92907 100644
--- a/src/starboard/file.h
+++ b/src/starboard/file.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// File system Input/Output
+// Module Overview: Starboard File module
+//
+// Defines file system input/output functions.
 
 #ifndef STARBOARD_FILE_H_
 #define STARBOARD_FILE_H_
@@ -32,17 +34,33 @@
 // A handle to an open file.
 typedef SbFilePrivate* SbFile;
 
-// kSbFile(Open|Create)(Only|Always|Truncated) are mutually exclusive. You
-// should specify exactly one of the five (possibly combining with other flags)
-// when opening or creating a file.
+// Flags that define how a file is used in the application. These flags should
+// be or'd together when passed to SbFileOpen to open or create a file.
+//
+// The following five flags are mutually exclusive. You must specify exactly one
+// of them:
+// - |kSbFileOpenAlways|
+// - |kSbFileOpenOnly|
+// - |kSbFileOpenTruncated|
+// - |kSbFileCreateAlways|
+// - |kSbFileCreateOnly|
+//
+// In addition, one or more of the following flags must be specified:
+// - |kSbFileRead|
+// - |kSbFileWrite|
+//
+// The |kSbFileAsync| flag is optional.
 typedef enum SbFileFlags {
   kSbFileOpenOnly = 1 << 0,       // Opens a file, only if it exists.
   kSbFileCreateOnly = 1 << 1,     // Creates a new file, only if it
                                   //   does not already exist.
-  kSbFileOpenAlways = 1 << 2,     // May create a new file.
-  kSbFileCreateAlways = 1 << 3,   // May overwrite an old file.
-  kSbFileOpenTruncated = 1 << 4,  // Opens a file and truncates it
-                                  //   to zero, only if it exists.
+  kSbFileOpenAlways = 1 << 2,     // Opens an existing file at the specified
+                                  // path or creates a new file at that path.
+  kSbFileCreateAlways = 1 << 3,   // Creates a new file at the specified path
+                                  // or overwrites an existing file at that
+                                  // path.
+  kSbFileOpenTruncated = 1 << 4,  // Opens a file and truncates it to zero,
+                                  // only if it exists.
   kSbFileRead = 1 << 5,
   kSbFileWrite = 1 << 6,
   kSbFileAsync = 1 << 7,  // May allow asynchronous I/O on some
@@ -112,72 +130,183 @@
 }
 
 // Opens the file at |path|, which must be absolute, creating it if specified by
-// |flags|. |out_created|, if provided, will be set to true if a new file was
-// created (or an old one truncated to zero length to simulate a new file, which
-// can happen with kSbFileCreateAlways), and false otherwise.  |out_error| can
-// be NULL. If creation failed, it will return kSbFileInvalid. The read/write
-// position is at the beginning of the file.
+// |flags|. The read/write position is at the beginning of the file.
+//
+// Note that this function only guarantees the correct behavior when |path|
+// points to a file. The behavior is undefined if |path| points to a directory.
+//
+// |path|: The absolute path of the file to be opened.
+// |flags|: |SbFileFlags| that determine how the file is used in the
+//   application. Among other things, |flags| can indicate whether the
+//   application should create |path| if |path| does not already exist.
+// |out_created|: Starboard sets this value to |true| if a new file is created
+//   or if an old file is truncated to zero length to simulate a new file,
+//   which can happen if the |kSbFileCreateAlways| flag is set. Otherwise,
+//   Starboard sets this value to |false|.
+// |out_error|: If |path| cannot be created, this is set to |kSbFileInvalid|.
+//   Otherwise, it is |NULL|.
 SB_EXPORT SbFile SbFileOpen(const char* path,
                             int flags,
                             bool* out_created,
                             SbFileError* out_error);
 
-// Closes |file|. Returns whether the close was successful.
+// Closes |file|. The return value indicates whether the file was closed
+// successfully.
+//
+// |file|: The absolute path of the file to be closed.
 SB_EXPORT bool SbFileClose(SbFile file);
 
-// Changes current read/write position in |file| to |offset| relative to the
-// origin defined by |whence|. Returns the resultant current read/write position
-// in the file (relative to the start) or -1 in case of error. May not support
+// Changes the current read/write position in |file|. The return value
+// identifies the resultant current read/write position in the file (relative
+// to the start) or |-1| in case of an error. This function might not support
 // seeking past the end of the file on some platforms.
+//
+// |file|: The SbFile in which the read/write position will be changed.
+// |whence|: The starting read/write position. The position is modified relative
+//   to this value.
+// |offset|: The amount that the read/write position is changed, relative to
+//   |whence|.
 SB_EXPORT int64_t SbFileSeek(SbFile file, SbFileWhence whence, int64_t offset);
 
 // Reads |size| bytes (or until EOF is reached) from |file| into |data|,
-// starting at the file's current position. Returns the number of bytes read, or
-// -1 on error. Note that this function does NOT make a best effort to read all
-// data on all platforms, it just reads however many bytes are quickly
-// available. Can be run in a loop to make it a best-effort read.
+// starting at the file's current position.
+//
+// The return value specifies the number of bytes read or |-1| if there was
+// an error. Note that this function does NOT make a best effort to read all
+// data on all platforms; rather, it just reads however many bytes are quickly
+// available. However, this function can be run in a loop to make it a
+// best-effort read.
+//
+// |file|: The SbFile from which to read data.
+// |data|: The variable to which data is read.
+// |size|: The amount of data (in bytes) to read.
 SB_EXPORT int SbFileRead(SbFile file, char* data, int size);
 
 // Writes the given buffer into |file| at the file's current position,
-// overwritting any data that was previously there. Returns the number of bytes
-// written, or -1 on error. Note that this function does NOT make a best effort
-// to write all data, it writes however many bytes can be written quickly. It
-// should be run in a loop to ensure all data is written.
+// overwriting any data that was previously there.
+//
+// The return value identifies the number of bytes written, or |-1| on error.
+// Note that this function does NOT make a best effort to write all data;
+// rather, it writes however many bytes can be written quickly. Generally, this
+// means that it writes however many bytes as possible without blocking on IO.
+// Run this function in a loop to ensure that all data is written.
+//
+// |file|: The SbFile to which data will be written.
+// |data|: The data to be written.
+// |size|: The amount of data (in bytes) to write.
 SB_EXPORT int SbFileWrite(SbFile file, const char* data, int size);
 
-// Truncates the given |file| to the given |length|. If length is greater than
-// the current size of the file, the file is extended with zeros. Negative
-// |length| will do nothing and return false.
+// Truncates the given |file| to the given |length|. The return value indicates
+// whether the file was truncated successfully.
+//
+// |file|: The file to be truncated.
+// |length|: The expected length of the file after it is truncated. If |length|
+//   is greater than the current size of the file, then the file is extended
+//   with zeros. If |length| is negative, then the function is a no-op and
+//   returns |false|.
 SB_EXPORT bool SbFileTruncate(SbFile file, int64_t length);
 
-// Flushes the write buffer to |file|. Data written via SbFileWrite isn't
-// necessarily comitted right away until the file is flushed or closed.
+// Flushes the write buffer to |file|. Data written via SbFileWrite is not
+// necessarily committed until the SbFile is flushed or closed.
+//
+// |file|: The SbFile to which the write buffer is flushed.
 SB_EXPORT bool SbFileFlush(SbFile file);
 
-// Places some information about |file|, an open SbFile, in |out_info|. Returns
-// |true| if successful. If unsuccessful, |out_info| is untouched.
+// Retrieves information about |file|. The return value indicates whether the
+// file information was retrieved successfully.
+//
+// |file|: The SbFile for which information is retrieved.
+// |out_info|: The variable into which the retrieved data is placed. This
+//   variable is not touched if the operation is not successful.
 SB_EXPORT bool SbFileGetInfo(SbFile file, SbFileInfo* out_info);
 
-// Places information about the file or directory at |path|, which must be
-// absolute, into |out_info|. Returns |true| if successful. If unsuccessful,
-// |out_info| is untouched.
+// Retrieves information about the file at |path|. The return value indicates
+// whether the file information was retrieved successfully.
+//
+// |file|: The absolute path of the file for which information is retrieved.
+// |out_info|: The variable into which the retrieved data is placed. This
+//   variable is not touched if the operation is not successful.
 SB_EXPORT bool SbFileGetPathInfo(const char* path, SbFileInfo* out_info);
 
-// Deletes the regular file, symlink, or empty directory at |path|, which must
-// be absolute. Used mainly to clean up after unit tests. May fail on some
-// platforms if the file in question is being held open.
+// Deletes the regular file, symlink, or empty directory at |path|. This
+// function is used primarily to clean up after unit tests. On some platforms,
+// this function fails if the file in question is being held open.
+//
+// |path|: The absolute path fo the file, symlink, or directory to be deleted.
 SB_EXPORT bool SbFileDelete(const char* path);
 
-// Returns whether a file or directory exists at |path|, which must be absolute.
+// Indicates whether a file or directory exists at |path|.
+//
+// |path|: The absolute path of the file or directory being checked.
 SB_EXPORT bool SbFileExists(const char* path);
 
-// Returns whether SbFileOpen() with the given |flags| is allowed for |path|,
-// which must be absolute.
+// Indicates whether SbFileOpen() with the given |flags| is allowed for |path|.
+//
+// |path|: The absolute path to be checked.
+// |flags|: The flags that are being evaluated for the given |path|.
 SB_EXPORT bool SbFileCanOpen(const char* path, int flags);
 
-// Converts an ISO fopen() mode string into flags that can be equivalently
+// Converts an ISO |fopen()| mode string into flags that can be equivalently
 // passed into SbFileOpen().
+//
+// |mode|: The mode string to be converted into flags.
 SB_EXPORT int SbFileModeStringToFlags(const char* mode);
+
+// Reads |size| bytes (or until EOF is reached) from |file| into |data|,
+// starting from the beginning of the file.
+//
+// The return value specifies the number of bytes read or |-1| if there was
+// an error. Note that, unlike |SbFileRead|, this function does make a best
+// effort to read all data on all platforms. As such, it is not intended for
+// stream-oriented files but instead for cases when the normal expectation is
+// that |size| bytes can be read unless there is an error.
+//
+// |file|: The SbFile from which to read data.
+// |data|: The variable to which data is read.
+// |size|: The amount of data (in bytes) to read.
+static inline int SbFileReadAll(SbFile file, char* data, int size) {
+  if (!SbFileIsValid(file) || size < 0) {
+    return -1;
+  }
+  int bytes_read = 0;
+  int rv;
+  do {
+    rv = SbFileRead(file, data + bytes_read, size - bytes_read);
+    if (bytes_read <= 0) {
+      break;
+    }
+    bytes_read += rv;
+  } while (bytes_read < size);
+
+  return bytes_read ? bytes_read : rv;
+}
+
+// Writes the given buffer into |file|, starting at the beginning of the file,
+// and overwriting any data that was previously there. Unlike SbFileWrite, this
+// function does make a best effort to write all data on all platforms.
+//
+// The return value identifies the number of bytes written, or |-1| on error.
+//
+// |file|: The file to which data will be written.
+// |data|: The data to be written.
+// |size|: The amount of data (in bytes) to write.
+static inline int SbFileWriteAll(SbFile file, const char* data, int size) {
+  if (!SbFileIsValid(file) || size < 0) {
+    return -1;
+  }
+  int bytes_written = 0;
+  int rv;
+  do {
+    rv = SbFileWrite(file, data + bytes_written, size - bytes_written);
+    if (bytes_written <= 0) {
+      break;
+    }
+    bytes_written += rv;
+  } while (bytes_written < size);
+
+  return bytes_written ? bytes_written : rv;
+}
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
@@ -219,10 +348,18 @@
 
   int Read(char* data, int size) const { return SbFileRead(file_, data, size); }
 
+  int ReadAll(char* data, int size) const {
+    return SbFileReadAll(file_, data, size);
+  }
+
   int Write(const char* data, int size) const {
     return SbFileWrite(file_, data, size);
   }
 
+  int WriteAll(const char* data, int size) const {
+    return SbFileWriteAll(file_, data, size);
+  }
+
   bool Truncate(int64_t length) const { return SbFileTruncate(file_, length); }
 
   bool Flush() const { return SbFileFlush(file_); }
@@ -231,6 +368,12 @@
     return SbFileGetInfo(file_, out_info);
   }
 
+  int64_t GetSize() const {
+    SbFileInfo file_info;
+    bool success = GetInfo(&file_info);
+    return (success ? file_info.size : -1);
+  }
+
  private:
   SbFile file_;
 };
diff --git a/src/starboard/image.h b/src/starboard/image.h
new file mode 100644
index 0000000..5ff6e3f
--- /dev/null
+++ b/src/starboard/image.h
@@ -0,0 +1,102 @@
+// 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 Image Decoding Module
+//
+// API for hardware accelerated image decoding. This module allows for the
+// client to feed in raw, encoded data to be decoded directly into an
+// SbDecodeTarget.  It also provides an interface for the client to query what
+// combinations of encoded image formats and SbDecodeTargetFormats are
+// supported or not.
+//
+// All functions in this module are safe to call from any thread at any point
+// in time.
+//
+// #### SbImageIsDecodeSupported and SbImageDecode Example
+//
+// Let's assume that we're on a Blitter platform.
+//
+//     SbDecodeTargetProvider* provider = GetProviderFromSomewhere();
+//     void* data = GetCompressedJPEGFromSomewhere();
+//     int data_size = GetCompressedJPEGSizeFromSomewhere();
+//     const char* mime_type = "image/jpeg";
+//     SbDecodeTargetFormat format = kSbDecodeTargetFormat1PlaneRGBA;
+//
+//     if (!SbImageIsDecodeSupported(mime_type, format)) {
+//       return;
+//     }
+//
+//     SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size,
+//                                                  mime_type, format);
+//     SbBlitterSurface surface =
+//         SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+//     // Do stuff with surface...
+//
+
+#ifndef STARBOARD_IMAGE_H_
+#define STARBOARD_IMAGE_H_
+
+#include "starboard/configuration.h"
+#include "starboard/decode_target.h"
+#include "starboard/export.h"
+#include "starboard/types.h"
+
+#if SB_VERSION(3)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Whether the current platform supports hardware accelerated decoding an
+// image of mime type |mime_type| into SbDecodeTargetFormat |format|.  The
+// result of this function must not change over the course of the program,
+// which means that the results of this function may be cached indefinitely.
+SB_EXPORT bool SbImageIsDecodeSupported(const char* mime_type,
+                                        SbDecodeTargetFormat format);
+
+// Attempt to decode encoded |mime_type| image data |data| of size |data_size|
+// into an SbDecodeTarget of SbDecodeFormatType |format|, possibly using
+// SbDecodeTargetProvider |provider|, if it is non-null.  Thus, four following
+// scenarios regarding the provider may happen:
+//
+//   1. The provider is required by the |SbImageDecode| implementation and no
+//      provider is given.  The implementation should gracefully fail by
+//      immediately returning kSbDecodeTargetInvalid.
+//   2. The provider is required and is passed in.  The implementation will
+//      proceed forward, using the SbDecodeTarget from the provider.
+//   3. The provider is not required and is passed in.  The provider will NOT be
+//      called, and the implementation will proceed to decoding however it
+//      desires.
+//   4. The provider is not required and is not passed in.  The implementation
+//      will proceed forward.
+//
+// Thus, it is NOT safe for clients of this API to assume that the |provider|
+// it passes in will be called.  Finally, if the decode succeeds, a new
+// SbDecodeTarget will be allocated. If |mime_type| image decoding for the
+// requested format is not supported or the decode fails,
+// kSbDecodeTargetInvalid will be returned, with any intermediate allocations
+// being cleaned up in the implementation.
+SB_EXPORT SbDecodeTarget SbImageDecode(SbDecodeTargetProvider* provider,
+                                       void* data,
+                                       int data_size,
+                                       const char* mime_type,
+                                       SbDecodeTargetFormat format);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // SB_VERSION(3)
+
+#endif  // STARBOARD_IMAGE_H_
diff --git a/src/starboard/input.h b/src/starboard/input.h
index dec4b29..e8f98b2 100644
--- a/src/starboard/input.h
+++ b/src/starboard/input.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Definition of input events and associated data types.
+// Module Overview: Starboard Input module
+//
+// Defines input events and associated data types.
 
 #ifndef STARBOARD_INPUT_H_
 #define STARBOARD_INPUT_H_
@@ -27,59 +29,60 @@
 extern "C" {
 #endif
 
-// All possible input subsystem types.
+// Identifies possible input subsystem types. The types of events that each
+// device type produces correspond to |SbInputEventType| values.
 typedef enum SbInputDeviceType {
-  // Input from some gesture-detection mechanism. Examples include Kinect,
+  // Input from a gesture-detection mechanism. Examples include Kinect,
   // Wiimotes, LG Magic Remotes, etc...
   //
-  // Produces Move, Grab, Ungrab, Press and Unpress events.
+  // Produces |Move|, |Grab|, |Ungrab|, |Press| and |Unpress| events.
   kSbInputDeviceTypeGesture,
 
   // Input from a gamepad, following the layout provided in the W3C Web Gamepad
-  // API.
+  // API. [https://www.w3.org/TR/gamepad/]
   //
-  // Produces Move, Press and Unpress events.
+  // Produces |Move|, |Press| and |Unpress| events.
   kSbInputDeviceTypeGamepad,
 
-  // Keyboard input from a traditional keyboard, or game controller chatpad.
+  // Keyboard input from a traditional keyboard or game controller chatpad.
   //
-  // Produces Press and Unpress events.
+  // Produces |Press| and |Unpress| events.
   kSbInputDeviceTypeKeyboard,
 
   // Input from a microphone that would provide audio data to the caller, who
   // may then find some way to detect speech or other sounds within it. It may
   // have processed or filtered the audio in some way before it arrives.
   //
-  // Produces Audio events.
+  // Produces |Audio| events.
   kSbInputDeviceTypeMicrophone,
 
   // Input from a traditional mouse.
   //
-  // Produces Move, Press, and Unpress events.
+  // Produces |Move|, |Press|, and |Unpress| events.
   kSbInputDeviceTypeMouse,
 
-  // Input from a TV remote control-style device.
+  // Input from a TV remote-control-style device.
   //
-  // Produces Press and Unpress events.
+  // Produces |Press| and |Unpress| events.
   kSbInputDeviceTypeRemote,
 
   // Input from a speech command analyzer, which is some hardware or software
   // that, given a set of known phrases, activates when one of the registered
   // phrases is heard.
   //
-  // Produces Command events.
+  // Produces |Command| events.
   kSbInputDeviceTypeSpeechCommand,
 
   // Input from a single- or multi-touchscreen.
   //
-  // Produces Move, Press, and Unpress events.
+  // Produces |Move|, |Press|, and |Unpress| events.
   kSbInputDeviceTypeTouchScreen,
 
-  // Input from a touchpad that isn't masquerading as a mouse.
+  // Input from a touchpad that is not masquerading as a mouse.
   //
-  // Produces Move, Press, and Unpress events.
+  // Produces |Move|, |Press|, and |Unpress| events.
   kSbInputDeviceTypeTouchPad,
-} SbInputType;
+} SbInputDeviceType;
 
 // The action that an input event represents.
 typedef enum SbInputEventType {
@@ -90,28 +93,26 @@
   // like a speech recognizer.
   kSbInputEventTypeCommand,
 
-  // Grab activation. In some gesture systems a grab gesture is dfferent from an
-  // activate gesture. An Ungrab event will follow when the grab gesture is
-  // terminated.
+  // Grab activation. This event type is deprecated.
   kSbInputEventTypeGrab,
 
-  // Device Movement. In the case of Mouse, and perhaps Gesture, the movement
-  // tracks an absolute cursor position. In the case of TouchPad, only relative
-  // movements are provided.
+  // Device Movement. In the case of |Mouse|, and perhaps |Gesture|, the
+  // movement tracks an absolute cursor position. In the case of |TouchPad|,
+  // only relative movements are provided.
   kSbInputEventTypeMove,
 
-  // Key or button press activation. This could be a key on a keyboard, a button
-  // on a mouse or game controller, or a push from a touch screen or gesture. An
-  // Unpress event will be delivered once the key/button/finger is
-  // raised. Injecting repeat presses is up to the client.
+  // Key or button press activation. This could be a key on a keyboard, a
+  // button on a mouse or game controller, a push from a touch screen, or
+  // a gesture. An |Unpress| event is subsequently delivered when the
+  // |Press| event terminates, such as when the key/button/finger is raised.
+  // Injecting repeat presses is up to the client.
   kSbInputEventTypePress,
 
-  // Grab deactivation. An Ungrab event will be sent when a grab gesture is
-  // terminated.
+  // Grab deactivation. This event type is deprecated.
   kSbInputEventTypeUngrab,
 
-  // Key or button deactivation. The counterpart to the Press event, this event
-  // is sent when the key or button being pressed is released.
+  // Key or button deactivation. The counterpart to the |Press| event, this
+  // event is sent when the key or button being pressed is released.
   kSbInputEventTypeUnpress,
 } SbInputEventType;
 
@@ -121,42 +122,44 @@
   int y;
 } SbInputVector;
 
-// Event data for kSbEventTypeInput events.
+// Event data for |kSbEventTypeInput| events.
 typedef struct SbInputData {
-  // The window that this input was generated at.
+  // The window in which the input was generated.
   SbWindow window;
 
-  // The type of input event this represents.
+  // The type of input event that this represents.
   SbInputEventType type;
 
   // The type of device that generated this input event.
   SbInputDeviceType device_type;
 
-  // An identifier unique amongst all connected devices.
+  // An identifier that is unique among all connected devices.
   int device_id;
 
   // An identifier that indicates which keyboard key or mouse button was
   // involved in this event, if any. All known keys for all devices are mapped
-  // to a single ID space, defined by the enum SbKey in key.h.
+  // to a single ID space, defined by the |SbKey| enum in |key.h|.
   SbKey key;
 
   // The character that corresponds to the key. For an external keyboard, this
-  // character also depends on the language of keyboard type. Will be 0 if there
+  // character also depends on the keyboard language. The value is |0| if there
   // is no corresponding character.
   wchar_t character;
 
   // The location of the specified key, in cases where there are multiple
-  // instances of the button on the keyboard. The "shift" key, for example.
+  // instances of the button on the keyboard. For example, some keyboards
+  // have more than one "shift" key.
   SbKeyLocation key_location;
 
-  // Key modifiers (e.g. Ctrl, Chift) held down during this input event.
+  // Key modifiers (e.g. |Ctrl|, |Shift|) held down during this input event.
   unsigned int key_modifiers;
 
   // The (x, y) coordinates of the persistent cursor controlled by this
-  // device. Will be 0 if not applicable.
+  // device. The value is |0| if this data is not applicable.
   SbInputVector position;
 
-  // The relative motion vector of this input. Will be 0 if not applicable.
+  // The relative motion vector of this input. The value is |0| if this data
+  // is not applicable.
   SbInputVector delta;
 } SbInputData;
 
diff --git a/src/starboard/key.h b/src/starboard/key.h
index 05f05d9..b50608f 100644
--- a/src/starboard/key.h
+++ b/src/starboard/key.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// The canonical set of Starboard key codes.
+// Module Overview: Starboard Key module
+//
+// Defines the canonical set of Starboard key codes.
 
 #ifndef STARBOARD_KEY_H_
 #define STARBOARD_KEY_H_
@@ -21,17 +23,16 @@
 extern "C" {
 #endif
 
-// A standard set of key codes representing each possible input key across all
-// kinds of devices, we use the semi-standard Windows virtual key codes.
-//
-// Windows virtual key codes doc:
+// A standard set of key codes, ordered by value, that represent each possible
+// input key across all kinds of devices. Starboard uses the semi-standard
+// Windows virtual key codes documented at:
 //   https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
-//
-// The order here is by value.
 typedef enum SbKey {
   kSbKeyUnknown = 0,
   kSbKeyCancel = 0x03,
-  kSbKeyBack = 0x08,
+  kSbKeyBackspace = 0x08,
+  kSbKeyBack = kSbKeyBackspace,  // You probably want kSbKeyEscape for a
+                                 // semantic "back".
   kSbKeyTab = 0x09,
   kSbKeyBacktab = 0x0A,
   kSbKeyClear = 0x0C,
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index 6d8d416..afd4574 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.
-#define SB_API_VERSION 1
+#ifndef SB_API_VERSION
+#define SB_API_VERSION 2
+#endif
 
 // --- System Header Configuration -------------------------------------------
 
@@ -52,6 +53,15 @@
 // Whether the current platform provides the standard header limits.h.
 #define SB_HAS_LIMITS_H 1
 
+// Whether the current platform provides the standard header float.h.
+#define SB_HAS_FLOAT_H 1
+
+// Whether the current platform has microphone supported.
+#define SB_HAS_MICROPHONE 0
+
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 0
+
 // Type detection for wchar_t.
 #if defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
@@ -69,6 +79,12 @@
 #define SB_IS_WCHAR_T_SIGNED 1
 #endif
 
+// --- Architecture Configuration --------------------------------------------
+
+// On default Linux desktop, you must be a superuser in order to set real time
+// scheduling on threads.
+#define SB_HAS_THREAD_PRIORITY_SUPPORT 0
+
 // --- Attribute Configuration -----------------------------------------------
 
 // The platform's annotation for forcing a C function to be inlined.
@@ -365,6 +381,12 @@
 // The maximum number of users that can be signed in at the same time.
 #define SB_USER_MAX_SIGNED_IN 1
 
+// --- Timing API ------------------------------------------------------------
+
+// Whether this platform has an API to retrieve how long the current thread
+// has spent in the executing state.
+#define SB_HAS_TIME_THREAD_NOW 1
+
 // --- Platform Specific Audits ----------------------------------------------
 
 #if !defined(__GNUC__)
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi
index 703b774..af152fe 100644
--- a/src/starboard/linux/shared/gyp_configuration.gypi
+++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -21,15 +21,13 @@
     'target_os': 'linux',
     #'starboard_path%': 'starboard/linux/x64x11',
 
-    'enable_webdriver': '1',
     'in_app_dial%': 1,
     'gl_type%': 'system_gles3',
 
     # This should have a default value in cobalt/base.gypi. See the comment
     # there for acceptable values for this variable.
     'javascript_engine': 'mozjs',
-    # JIT is temporarily disabled for spidermonkey.
-    'cobalt_enable_jit': 0,
+    'cobalt_enable_jit': 1,
 
     # Define platform specific compiler and linker flags.
     # Refer to base.gypi for a list of all available variables.
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index cb6edb4..0baa281 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -49,10 +49,18 @@
     # set to 1 in the environment.
     use_asan_default = self.asan_default if not use_tsan and configuration in (
         Configs.DEBUG, Configs.DEVEL) else 0
+
+    # Set environmental variable to enable_mtm: 'USE_MTM'
+    # Terminal: `export {varname}={value}`
+    # Note: must also edit gyp_configuration.gypi per internal instructions.
+    mtm_on_by_default = 0
+    mtm_enabled = int(os.environ.get('USE_MTM', mtm_on_by_default))
+
     variables.update({
         'clang': 1,
         'use_asan': int(os.environ.get('USE_ASAN', use_asan_default)),
         'use_tsan': use_tsan,
+        'enable_mtm': mtm_enabled
     })
 
     if variables.get('use_asan') == 1 and variables.get('use_tsan') == 1:
diff --git a/src/starboard/linux/shared/starboard_base_symbolize.gyp b/src/starboard/linux/shared/starboard_base_symbolize.gyp
new file mode 100644
index 0000000..5cd3440
--- /dev/null
+++ b/src/starboard/linux/shared/starboard_base_symbolize.gyp
@@ -0,0 +1,25 @@
+# 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_base_symbolize',
+      'type': 'static_library',
+      'sources': [
+        '<(DEPTH)/base/third_party/symbolize/demangle.cc',
+        '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
new file mode 100644
index 0000000..ff6149d
--- /dev/null
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -0,0 +1,282 @@
+# 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': {
+    'starboard_platform_sources': [
+      '<(DEPTH)/starboard/linux/shared/atomic_public.h',
+      '<(DEPTH)/starboard/linux/shared/configuration_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/shared/alsa/alsa_audio_sink_type.cc',
+      '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h',
+      '<(DEPTH)/starboard/shared/alsa/alsa_util.cc',
+      '<(DEPTH)/starboard/shared/alsa/alsa_util.h',
+      '<(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',
+      '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc',
+      '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
+      '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc',
+      '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
+      '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
+      '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
+      '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
+      '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
+      '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
+      '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
+      '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
+      '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
+      '<(DEPTH)/starboard/shared/iso/directory_close.cc',
+      '<(DEPTH)/starboard/shared/iso/directory_get_next.cc',
+      '<(DEPTH)/starboard/shared/iso/directory_open.cc',
+      '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
+      '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
+      '<(DEPTH)/starboard/shared/iso/double_floor.cc',
+      '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
+      '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
+      '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
+      '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
+      '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
+      '<(DEPTH)/starboard/shared/iso/memory_move.cc',
+      '<(DEPTH)/starboard/shared/iso/memory_set.cc',
+      '<(DEPTH)/starboard/shared/iso/string_compare.cc',
+      '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
+      '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
+      '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
+      '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
+      '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
+      '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
+      '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
+      '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
+      '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
+      '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
+      '<(DEPTH)/starboard/shared/iso/string_scan.cc',
+      '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
+      '<(DEPTH)/starboard/shared/iso/system_sort.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
+      '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
+      '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
+      '<(DEPTH)/starboard/shared/linux/get_home_directory.cc',
+      '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
+      '<(DEPTH)/starboard/shared/linux/page_internal.cc',
+      '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
+      '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
+      '<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
+      '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
+      '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
+      '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
+      '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
+      '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
+      '<(DEPTH)/starboard/shared/linux/thread_get_name.cc',
+      '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
+      '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
+      '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
+      '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
+      '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+      '<(DEPTH)/starboard/shared/posix/directory_create.cc',
+      '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
+      '<(DEPTH)/starboard/shared/posix/file_close.cc',
+      '<(DEPTH)/starboard/shared/posix/file_delete.cc',
+      '<(DEPTH)/starboard/shared/posix/file_exists.cc',
+      '<(DEPTH)/starboard/shared/posix/file_flush.cc',
+      '<(DEPTH)/starboard/shared/posix/file_get_info.cc',
+      '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc',
+      '<(DEPTH)/starboard/shared/posix/file_open.cc',
+      '<(DEPTH)/starboard/shared/posix/file_read.cc',
+      '<(DEPTH)/starboard/shared/posix/file_seek.cc',
+      '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
+      '<(DEPTH)/starboard/shared/posix/file_write.cc',
+      '<(DEPTH)/starboard/shared/posix/log.cc',
+      '<(DEPTH)/starboard/shared/posix/log_flush.cc',
+      '<(DEPTH)/starboard/shared/posix/log_format.cc',
+      '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
+      '<(DEPTH)/starboard/shared/posix/log_raw.cc',
+      '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
+      '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_create.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
+      '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
+      '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
+      '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
+      '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
+      '<(DEPTH)/starboard/shared/posix/string_format.cc',
+      '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
+      '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
+      '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
+      '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
+      '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
+      '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc',
+      '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
+      '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
+      '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
+      '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc',
+      '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
+      '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
+      '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
+      '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
+      '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
+      '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
+      '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
+      '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
+      '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
+      '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
+      '<(DEPTH)/starboard/shared/pthread/once.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h',
+      '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
+      '<(DEPTH)/starboard/shared/signal/crash_signals.h',
+      '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
+      '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
+      '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
+      '<(DEPTH)/starboard/shared/starboard/application.cc',
+      '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
+      '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
+      '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
+      '<(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/directory_can_open.cc',
+      '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
+      '<(DEPTH)/starboard/shared/starboard/event_schedule.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
+      '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc',
+      '<(DEPTH)/starboard/shared/starboard/log_message.cc',
+      '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
+      '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
+      '<(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_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',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
+      '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+      '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
+      '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
+      '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
+      '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
+      '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
+      '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
+      '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+      '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
+      '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
+      '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
+      '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
+      '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
+      '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
+      '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
+      '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_close.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_create.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_destroy.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_get_available.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_is_sample_rate_supported.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_open.cc',
+      '<(DEPTH)/starboard/shared/stub/microphone_read.cc',
+      '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
+      '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
+      '<(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',
+    ],
+    'starboard_platform_dependencies': [
+      '<(DEPTH)/starboard/common/common.gyp:common',
+      '<(DEPTH)/starboard/linux/shared/starboard_base_symbolize.gyp:starboard_base_symbolize',
+      '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+      '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+    ],
+  },
+}
diff --git a/src/starboard/linux/x64directfb/configuration_public.h b/src/starboard/linux/x64directfb/configuration_public.h
index c891232..6f91914 100644
--- a/src/starboard/linux/x64directfb/configuration_public.h
+++ b/src/starboard/linux/x64directfb/configuration_public.h
@@ -52,4 +52,8 @@
 #define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_BGRA
 #endif
 
+// The current platform has microphone supported.
+#undef SB_HAS_MICROPHONE
+#define SB_HAS_MICROPHONE 1
+
 #endif  // STARBOARD_LINUX_X64DIRECTFB_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/atomic_public.h b/src/starboard/linux/x64directfb/future/atomic_public.h
new file mode 100644
index 0000000..e4ef8fd
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/atomic_public.h
@@ -0,0 +1,20 @@
+// 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_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
+#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
+
+#include "starboard/linux/shared/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..94f15db
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/starboard_platform.gyp
@@ -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.
+{
+  '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',
+        '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc',
+        '<(DEPTH)/starboard/shared/stub/image_decode.cc',
+        '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.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)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
+        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/linux/x64directfb/future/thread_types_public.h b/src/starboard/linux/x64directfb/future/thread_types_public.h
new file mode 100644
index 0000000..19803be
--- /dev/null
+++ b/src/starboard/linux/x64directfb/future/thread_types_public.h
@@ -0,0 +1,20 @@
+// 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_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_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/sanitizer_options.cc b/src/starboard/linux/x64directfb/sanitizer_options.cc
new file mode 100644
index 0000000..e85d451
--- /dev/null
+++ b/src/starboard/linux/x64directfb/sanitizer_options.cc
@@ -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.
+
+// 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))
+
+// Newline separated list of issues to suppress, see
+// http://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression
+// http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_suppressions.cc
+SANITIZER_HOOK_ATTRIBUTE const char* __lsan_default_suppressions() {
+  return "leak:*eglibc-2.19*\n";
+}
+
+#endif  // defined(ADDRESS_SANITIZER)
diff --git a/src/starboard/linux/x64directfb/starboard_platform.gyp b/src/starboard/linux/x64directfb/starboard_platform.gyp
index 331fd6c..c45f24a 100644
--- a/src/starboard/linux/x64directfb/starboard_platform.gyp
+++ b/src/starboard/linux/x64directfb/starboard_platform.gyp
@@ -12,307 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
+  'includes': [
+    'starboard_platform.gypi'
+  ],
   'targets': [
     {
-      'target_name': 'starboard_base_symbolize',
-      'type': 'static_library',
-      'sources': [
-        '<(DEPTH)/base/third_party/symbolize/demangle.cc',
-        '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
-      ],
-    },
-    {
       'target_name': 'starboard_platform',
       'type': 'static_library',
-      'sources': [
-        '<(DEPTH)/starboard/linux/shared/atomic_public.h',
-        '<(DEPTH)/starboard/linux/shared/configuration_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/x64directfb/main.cc',
-        '<(DEPTH)/starboard/linux/x64directfb/system_get_property.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',
-        '<(DEPTH)/starboard/shared/alsa/alsa_util.h',
-        '<(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/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',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
-        '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
-        '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
-        '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
-        '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_close.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_get_next.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_open.cc',
-        '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
-        '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
-        '<(DEPTH)/starboard/shared/iso/double_floor.cc',
-        '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
-        '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_move.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_set.cc',
-        '<(DEPTH)/starboard/shared/iso/string_compare.cc',
-        '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
-        '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
-        '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
-        '<(DEPTH)/starboard/shared/iso/string_scan.cc',
-        '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
-        '<(DEPTH)/starboard/shared/iso/system_sort.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
-        '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
-        '<(DEPTH)/starboard/shared/linux/get_home_directory.cc',
-        '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
-        '<(DEPTH)/starboard/shared/linux/page_internal.cc',
-        '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
-        '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
-        '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_get_name.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/directory_create.cc',
-        '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
-        '<(DEPTH)/starboard/shared/posix/file_close.cc',
-        '<(DEPTH)/starboard/shared/posix/file_delete.cc',
-        '<(DEPTH)/starboard/shared/posix/file_exists.cc',
-        '<(DEPTH)/starboard/shared/posix/file_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/file_get_info.cc',
-        '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc',
-        '<(DEPTH)/starboard/shared/posix/file_open.cc',
-        '<(DEPTH)/starboard/shared/posix/file_read.cc',
-        '<(DEPTH)/starboard/shared/posix/file_seek.cc',
-        '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
-        '<(DEPTH)/starboard/shared/posix/file_write.cc',
-        '<(DEPTH)/starboard/shared/posix/log.cc',
-        '<(DEPTH)/starboard/shared/posix/log_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/log_format.cc',
-        '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
-        '<(DEPTH)/starboard/shared/posix/log_raw.cc',
-        '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_create.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
-        '<(DEPTH)/starboard/shared/posix/string_format.cc',
-        '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
-        '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
-        '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
-        '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
-        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
-        '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
-        '<(DEPTH)/starboard/shared/pthread/once.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
-        '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
-        '<(DEPTH)/starboard/shared/signal/crash_signals.h',
-        '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
-        '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
-        '<(DEPTH)/starboard/shared/starboard/application.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
-        '<(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',
-        '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_message.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
-        '<(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/new.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_decoder_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
-        '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
-        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
-        '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
-        '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
-        '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
-        '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc',
-        '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
-      ],
+      'sources': ['<@(starboard_platform_sources)'],
       'include_dirs': [
         '/usr/include/directfb',
       ],
@@ -322,9 +29,7 @@
         'STARBOARD_IMPLEMENTATION',
       ],
       'dependencies': [
-        '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
-        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
-        'starboard_base_symbolize',
+        '<@(starboard_platform_dependencies)',
       ],
     },
   ],
diff --git a/src/starboard/linux/x64directfb/starboard_platform.gypi b/src/starboard/linux/x64directfb/starboard_platform.gypi
new file mode 100644
index 0000000..d38e23c
--- /dev/null
+++ b/src/starboard/linux/x64directfb/starboard_platform.gypi
@@ -0,0 +1,63 @@
+# 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.
+{
+  'includes': ['../shared/starboard_platform.gypi'],
+
+  'variables': {
+    'starboard_platform_sources': [
+        '<(DEPTH)/starboard/linux/x64directfb/main.cc',
+        '<(DEPTH)/starboard/linux/x64directfb/sanitizer_options.cc',
+        '<(DEPTH)/starboard/linux/x64directfb/system_get_property.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/starboard/blitter_blit_rect_to_rect_tiled.cc',
+        '<(DEPTH)/starboard/shared/starboard/blitter_blit_rects_to_rects.cc',
+    ],
+  },
+}
diff --git a/src/starboard/linux/x64directfb/system_get_property.cc b/src/starboard/linux/x64directfb/system_get_property.cc
index b1529fe..1d20f7f 100644
--- a/src/starboard/linux/x64directfb/system_get_property.cc
+++ b/src/starboard/linux/x64directfb/system_get_property.cc
@@ -47,6 +47,7 @@
     case kSbSystemPropertyModelName:
     case kSbSystemPropertyModelYear:
     case kSbSystemPropertyNetworkOperatorName:
+    case kSbSystemPropertySpeechApiKey:
       return false;
 
     case kSbSystemPropertyFriendlyName:
diff --git a/src/starboard/linux/x64directfb/xephyr_run.sh b/src/starboard/linux/x64directfb/xephyr_run.sh
index dc3181c..77bf969 100755
--- a/src/starboard/linux/x64directfb/xephyr_run.sh
+++ b/src/starboard/linux/x64directfb/xephyr_run.sh
@@ -13,44 +13,123 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This script can be used as a convenience to launch an app under a Xephyr
-# X server.  It will first launch Xephyr, and then launch the executable given
-# on the command line such that it targets and will appear within the Xephyr
-# window.  It shuts down Xephyr after the main executable finishes.
+# This script can be used as a convenience to launch an app under a new
+# X server.  Xephyr requires a base X server, so will be launched if there is a
+# DISPLAY environment variable set. Xvfb does not, so it will be launched if
+# there is no DISPLAY variable set, which is likely to come from a remote shell
+# or background process. It will first launch the X server, and then launch the
+# executable given on the command line such that it targets the newly-launched
+# X server. It shuts down the X server after the main executable finishes.
 
-if [[ $# = 0 ]]; then
-  echo
-  echo "$0 will launch a given executable file under a Xephyr X server."
-  echo
-  echo "Usage:"
-  echo "  $0 PATH_TO_EXECUTABLE_FILE  "
-  echo
-  exit 0
+# Standard stuff to get the real script name.
+script_file="$(readlink -f "${BASH_SOURCE[0]}")"
+script_name="$(basename "${script_file}")"
+
+if [[ -n "$DISPLAY" ]]; then
+  # The binary to run to start the X Server.
+  xserver_bin=Xephyr
+  # The command-line args to set the screen dimensions.
+  declare -a xserver_screen=(-screen 1920x1080)
+  # The package to install for this X Server.
+  xserver_package=xserver-xephyr
+else
+  xserver_bin=Xvfb
+  declare -a xserver_screen=(-screen 0 1920x1080x24)
+  xserver_package=xvfb
 fi
 
-if ! hash Xephyr 2>/dev/null ; then
-  echo
-  echo "Xephyr is not installed.  Please run:"
-  echo "  sudo apt-get install xserver-xephyr"
-  echo
-  exit 0
-fi
+# Use display 42 as it is unlikely to be used by another process on this host.
+# TODO: Scan displays for a free one.
+xserver_display=":42"
 
-# Open up a Xephyr display.
-Xephyr -screen 1920x1080 :10 2> /dev/null &
-xephyr_pid=$!
+function log() {
+  echo "${script_name}: $@"
+}
 
-# Setup a trap to clean up Xephyr upon termination of this script
-function kill1() {
+function deleteTempTrap() {
+  # If something interrupted the script, it might have printed a partial line.
+  echo
+  deleteDirectory "${temp_dir}"
+}
+
+function killServerTrap() {
+  echo
   # Kill the Xephyr process.
-  kill $xephyr_pid 2> /dev/null
+  kill "${xserver_pid}" &> /dev/null
   # Waits for the kill to finish.
   wait
+  log "${xserver_bin} (pid ${xserver_pid}) terminated."
+  deleteDirectory "${temp_dir}"
 }
-trap kill1 EXIT
 
-# Give Xephyr some time to setup.
-sleep 0.5
+function deleteDirectory() {
+  if [[ -z "$1" ]]; then
+    log "deleteDirectory with no argument"
+    exit 1
+  fi
 
-# Launch the executable passed as a parameter on the Xephyr display.
-DISPLAY=:10 $@
\ No newline at end of file
+  # Only delete target if it is an existing directory.
+  if [[ -d "$1" ]]; then
+    rm -rf "$1"
+  fi
+}
+
+function main() {
+  if [[ "$#" = "0" ]]; then
+    echo
+    echo "${script_name}: Launches a given executable file under a ${xserver_bin} X server."
+    echo
+    echo "Usage:"
+    echo "  ${script_name} PATH_TO_EXECUTABLE_FILE [ARGS...] "
+    echo
+    exit 1
+  fi
+
+  if ! hash "${xserver_bin}" 2>/dev/null ; then
+    log "${xserver_bin} is not installed.  Please run:"
+    log "  sudo apt-get install ${xserver_package}"
+    exit 1
+  fi
+
+  # Create an auth file that will allow the current user to access the display.
+  temp_dir="$(mktemp -dt "${script_name}.XXXXXXXXXX")"
+
+  # Delete the temporary directory on exit.
+  trap deleteTempTrap EXIT
+
+  # In this case, we don't want to try to tunnel authority through to remote
+  # connections, we want to use this X server with the client. So we create
+  # our own auth and forcibly pass it through both sides of the client and
+  # server. Apologies for the X11 magic here.
+  xserver_auth="${temp_dir}/XAuthority"
+  touch "${xserver_auth}"
+  token="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | cut -f1 -d' ')"
+  xauth -qf "${xserver_auth}" add "${xserver_display}" . "${token}"
+
+  xserver_log="${temp_dir}/${xserver_bin}.txt"
+  # Launch an X Server in the background at a new display.
+  "${xserver_bin}" "${xserver_display}" \
+    -auth "${xserver_auth}" \
+    "${xserver_screen[@]}" \
+    &> "${xserver_log}" &
+  xserver_pid=$!
+  log "${xserver_bin} (pid ${xserver_pid}) running on ${xserver_display}."
+
+  # Setup a trap to clean up Xephyr upon termination of this script
+  trap killServerTrap EXIT
+
+  # Give Xephyr some time to setup.
+  sleep 0.5
+
+  # Launch the executable passed as a parameter on the new display.
+  DISPLAY="${xserver_display}" XAUTHORITY="${xserver_auth}" "$@"
+  result=$?
+  if [[ "${result}" != "0" ]]; then
+    log "$1 result: ${result}"
+    log "--- ${xserver_bin} log ---------------------------------------"
+    cat "${xserver_log}"
+  fi
+  return ${result}
+}
+
+main "$@"
diff --git a/src/starboard/linux/x64x11/configuration_public.h b/src/starboard/linux/x64x11/configuration_public.h
index 2291350..8022c88 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,7 +109,14 @@
 // textures. These textures typically originate from video decoders.
 #define SB_HAS_NV12_TEXTURE_SUPPORT 1
 
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 0
+
 // Include the Linux configuration that's common between all Desktop Linuxes.
 #include "starboard/linux/shared/configuration_public.h"
 
+// The current platform has microphone supported.
+#undef SB_HAS_MICROPHONE
+#define SB_HAS_MICROPHONE 1
+
 #endif  // STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/atomic_public.h b/src/starboard/linux/x64x11/future/atomic_public.h
new file mode 100644
index 0000000..e908f7d
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/atomic_public.h
@@ -0,0 +1,20 @@
+// 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_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
+#define STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
+
+#include "starboard/linux/shared/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..0bd1f09
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/gyp_configuration.gypi
@@ -0,0 +1,44 @@
+# 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': {
+    # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some
+    # nasty macros (|Status|, for example) that conflict with Chromium base.
+    # Since certain Cobalt headers now depend on EGL through SbDecodeTarget,
+    # we define this macro to configure EGL to not pull in these headers.
+    'defines': [
+      'MESA_EGL_NO_X11_HEADERS'
+    ],
+    '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..31eaa25
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/starboard_platform.gyp
@@ -0,0 +1,43 @@
+# 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.
+{
+  'includes': [
+    '../starboard_platform.gypi'
+  ],
+  'targets': [
+    {
+      'target_name': 'starboard_platform',
+      'product_name': 'starboard_platform_future',
+      'type': 'static_library',
+      'sources': [
+        '<@(starboard_platform_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',
+        '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc',
+        '<(DEPTH)/starboard/shared/stub/image_decode.cc',
+        '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc',
+      ],
+      'defines': [
+        # This must be defined when building Starboard, and must not when
+        # building Starboard client code.
+        'STARBOARD_IMPLEMENTATION',
+      ],
+      'dependencies': [
+        '<@(starboard_platform_dependencies)',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/linux/x64x11/future/thread_types_public.h b/src/starboard/linux/x64x11/future/thread_types_public.h
new file mode 100644
index 0000000..9ae5da8
--- /dev/null
+++ b/src/starboard/linux/x64x11/future/thread_types_public.h
@@ -0,0 +1,20 @@
+// 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_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_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/main.cc b/src/starboard/linux/x64x11/main.cc
index 7feb245..12cbce1 100644
--- a/src/starboard/linux/x64x11/main.cc
+++ b/src/starboard/linux/x64x11/main.cc
@@ -19,7 +19,7 @@
 #include "starboard/shared/signal/suspend_signals.h"
 #include "starboard/shared/x11/application_x11.h"
 
-int main(int argc, char** argv) {
+extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) {
   tzset();
   starboard::shared::signal::InstallCrashSignalHandlers();
   starboard::shared::signal::InstallSuspendSignalHandlers();
diff --git a/src/starboard/linux/x64x11/sanitizer_options.cc b/src/starboard/linux/x64x11/sanitizer_options.cc
new file mode 100644
index 0000000..2ac2410
--- /dev/null
+++ b/src/starboard/linux/x64x11/sanitizer_options.cc
@@ -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.
+
+// 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))
+
+// Newline separated list of issues to suppress, see
+// http://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression
+// http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_suppressions.cc
+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 761a55d..56e5781 100644
--- a/src/starboard/linux/x64x11/starboard_platform.gyp
+++ b/src/starboard/linux/x64x11/starboard_platform.gyp
@@ -12,283 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
+  'includes': [
+    'starboard_platform.gypi'
+  ],
   'targets': [
     {
-      'target_name': 'starboard_base_symbolize',
-      'type': 'static_library',
-      'sources': [
-        '<(DEPTH)/base/third_party/symbolize/demangle.cc',
-        '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
-      ],
-    },
-    {
       'target_name': 'starboard_platform',
       'type': 'static_library',
-      'sources': [
-        '<(DEPTH)/starboard/linux/shared/atomic_public.h',
-        '<(DEPTH)/starboard/linux/shared/configuration_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/linux/x64x11/system_get_property.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',
-        '<(DEPTH)/starboard/shared/alsa/alsa_util.h',
-        '<(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',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc',
-        '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
-        '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
-        '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
-        '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
-        '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
-        '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_close.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_get_next.cc',
-        '<(DEPTH)/starboard/shared/iso/directory_open.cc',
-        '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
-        '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
-        '<(DEPTH)/starboard/shared/iso/double_floor.cc',
-        '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
-        '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_move.cc',
-        '<(DEPTH)/starboard/shared/iso/memory_set.cc',
-        '<(DEPTH)/starboard/shared/iso/string_compare.cc',
-        '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
-        '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
-        '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
-        '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
-        '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
-        '<(DEPTH)/starboard/shared/iso/string_scan.cc',
-        '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
-        '<(DEPTH)/starboard/shared/iso/system_sort.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
-        '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
-        '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
-        '<(DEPTH)/starboard/shared/linux/get_home_directory.cc',
-        '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
-        '<(DEPTH)/starboard/shared/linux/page_internal.cc',
-        '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_stack.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
-        '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
-        '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
-        '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_get_name.cc',
-        '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
-        '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/directory_create.cc',
-        '<(DEPTH)/starboard/shared/posix/file_can_open.cc',
-        '<(DEPTH)/starboard/shared/posix/file_close.cc',
-        '<(DEPTH)/starboard/shared/posix/file_delete.cc',
-        '<(DEPTH)/starboard/shared/posix/file_exists.cc',
-        '<(DEPTH)/starboard/shared/posix/file_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/file_get_info.cc',
-        '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc',
-        '<(DEPTH)/starboard/shared/posix/file_open.cc',
-        '<(DEPTH)/starboard/shared/posix/file_read.cc',
-        '<(DEPTH)/starboard/shared/posix/file_seek.cc',
-        '<(DEPTH)/starboard/shared/posix/file_truncate.cc',
-        '<(DEPTH)/starboard/shared/posix/file_write.cc',
-        '<(DEPTH)/starboard/shared/posix/log.cc',
-        '<(DEPTH)/starboard/shared/posix/log_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/log_format.cc',
-        '<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
-        '<(DEPTH)/starboard/shared/posix/log_raw.cc',
-        '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
-        '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_create.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
-        '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
-        '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
-        '<(DEPTH)/starboard/shared/posix/string_format.cc',
-        '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
-        '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
-        '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc',
-        '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
-        '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
-        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
-        '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
-        '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
-        '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
-        '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
-        '<(DEPTH)/starboard/shared/pthread/once.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
-        '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
-        '<(DEPTH)/starboard/shared/signal/crash_signals.h',
-        '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
-        '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
-        '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
-        '<(DEPTH)/starboard/shared/starboard/application.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
-        '<(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/directory_can_open.cc',
-        '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
-        '<(DEPTH)/starboard/shared/starboard/event_schedule.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_message.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
-        '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
-        '<(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/new.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_decoder_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
-        '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
-        '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
-        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
-        '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
-        '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
-        '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
-        '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
-        '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
-        '<(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',
-      ],
+      'sources': ['<@(starboard_platform_sources)'],
       'defines': [
         # This must be defined when building Starboard, and must not when
         # building Starboard client code.
         'STARBOARD_IMPLEMENTATION',
       ],
       'dependencies': [
-        '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
-        '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
-        'starboard_base_symbolize',
+        '<@(starboard_platform_dependencies)',
       ],
     },
   ],
diff --git a/src/starboard/linux/x64x11/starboard_platform.gypi b/src/starboard/linux/x64x11/starboard_platform.gypi
new file mode 100644
index 0000000..05f1e12
--- /dev/null
+++ b/src/starboard/linux/x64x11/starboard_platform.gypi
@@ -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.
+{
+  'includes': ['../shared/starboard_platform.gypi'],
+
+  'variables': {
+    'starboard_platform_sources': [
+      '<(DEPTH)/starboard/linux/x64x11/main.cc',
+      '<(DEPTH)/starboard/linux/x64x11/sanitizer_options.cc',
+      '<(DEPTH)/starboard/linux/x64x11/system_get_property.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',
+    ],
+  },
+}
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/linux/x64x11/system_get_property.cc b/src/starboard/linux/x64x11/system_get_property.cc
index cffaa35..b72747d 100644
--- a/src/starboard/linux/x64x11/system_get_property.cc
+++ b/src/starboard/linux/x64x11/system_get_property.cc
@@ -47,6 +47,7 @@
     case kSbSystemPropertyModelName:
     case kSbSystemPropertyModelYear:
     case kSbSystemPropertyNetworkOperatorName:
+    case kSbSystemPropertySpeechApiKey:
       return false;
 
     case kSbSystemPropertyFriendlyName:
diff --git a/src/starboard/log.h b/src/starboard/log.h
index 3693925..8e86cd8 100644
--- a/src/starboard/log.h
+++ b/src/starboard/log.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Debug logging.
+// Module Overview: Starboard Logging module
+//
+// Defines debug logging functions.
 
 #ifndef STARBOARD_LOG_H_
 #define STARBOARD_LOG_H_
@@ -41,28 +43,38 @@
   kSbLogPriorityFatal,
 } SbLogPriority;
 
-// Writes |message| at |priority| to the debug output log for this platform.
-// Passing kSbLogPriorityFatal will not terminate the program, such policy must
-// be enforced at the application level. |priority| may, in fact, be completely
-// ignored on many platforms. No formatting is required to be done on the
-// message, not even newline termination. That said, platforms may wish to
-// adjust the message to be more suitable for their output method. This could be
-// wrapping the text, or stripping unprintable characters.
+// Writes |message| to the platform's debug output log.
+//
+// |priority|: The SbLogPriority at which the message should be logged. Note
+//   that passing |kSbLogPriorityFatal| does not terminate the program. Such a
+//   policy must be enforced at the application level. In fact, |priority| may
+//   be completely ignored on many platforms.
+// |message|: The message to be logged. No formatting is required to be done
+//   on the value, including newline termination. That said, platforms can
+//   adjust the message to be more suitable for their output method by
+//   wrapping the text, stripping unprintable characters, etc.
 SB_EXPORT void SbLog(SbLogPriority priority, const char* message);
 
 // A bare-bones log output method that is async-signal-safe, i.e. safe to call
-// from an asynchronous signal handler (e.g. a SIGSEGV handler). It should do no
-// additional formatting.
+// from an asynchronous signal handler (e.g. a |SIGSEGV| handler). It should not
+// do any additional formatting.
+//
+// |message|: The message to be logged.
 SB_EXPORT void SbLogRaw(const char* message);
 
 // Dumps the stack of the current thread to the log in an async-signal-safe
-// manner, i.e. safe to call from an asynchronous signal handler (e.g. a SIGSEGV
-// handler). Does not include SbLogRawDumpStack itself, and will additionally
-// skip |frames_to_skip| frames from the top of the stack.
+// manner, i.e. safe to call from an asynchronous signal handler (e.g. a
+// |SIGSEGV| handler). Does not include SbLogRawDumpStack itself.
+//
+// |frames_to_skip|: The number of frames to skip from the top of the stack
+//   when dumping the current thread to the log. This parameter lets you remove
+//   noise from helper functions that might end up on top of every stack dump
+//   so that the stack dump is just the relevant function stack where the
+//   problem occurred.
 SB_EXPORT void SbLogRawDumpStack(int frames_to_skip);
 
 // A formatted log output method that is async-signal-safe, i.e. safe to call
-// from an asynchronous signal handler (e.g. a SIGSEGV handler).
+// from an asynchronous signal handler (e.g. a |SIGSEGV| handler).
 SB_EXPORT void SbLogRawFormat(const char* format, va_list args)
     SB_PRINTF_FORMAT(1, 0);
 
@@ -76,12 +88,12 @@
   va_end(args);
 }
 
-// A log output method, that additionally performs a string format on the way
-// out.
+// A log output method that additionally performs a string format on the
+// data being logged.
 SB_EXPORT void SbLogFormat(const char* format, va_list args)
     SB_PRINTF_FORMAT(1, 0);
 
-// Inline wrapper of SbLogFormat to convert from ellipsis to va_args.
+// Inline wrapper of SbLogFormat that converts from ellipsis to va_args.
 static SB_C_INLINE void SbLogFormatF(const char* format, ...)
     SB_PRINTF_FORMAT(1, 2);
 void SbLogFormatF(const char* format, ...) {
@@ -91,10 +103,10 @@
   va_end(args);
 }
 
-// Flushes the log buffer, on some platforms.
+// Flushes the log buffer on some platforms.
 SB_EXPORT void SbLogFlush();
 
-// Returns whether the log output goes to a TTY or is being redirected.
+// Indicates whether the log output goes to a TTY or is being redirected.
 SB_EXPORT bool SbLogIsTty();
 
 #ifdef __cplusplus
diff --git a/src/starboard/media.h b/src/starboard/media.h
index 89f2608..2c800ba 100644
--- a/src/starboard/media.h
+++ b/src/starboard/media.h
@@ -12,7 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Media definitions that are common between the Decoder and Player interfaces.
+// Module Overview: Starboard Media module
+//
+// Provides media definitions that are common between the Decoder and Player
+// interfaces.
 
 #ifndef STARBOARD_MEDIA_H_
 #define STARBOARD_MEDIA_H_
@@ -40,7 +43,7 @@
   kSbMediaTypeVideo,
 } SbMediaType;
 
-// Possibly supported types of video elementary streams.
+// Types of video elementary streams that could be supported.
 typedef enum SbMediaVideoCodec {
   kSbMediaVideoCodecNone,
 
@@ -54,7 +57,7 @@
   kSbMediaVideoCodecVp9,
 } SbMediaVideoCodec;
 
-// Possibly supported types of audio elementary streams.
+// Types of audio elementary streams that can be supported.
 typedef enum SbMediaAudioCodec {
   kSbMediaAudioCodecNone,
 
@@ -63,8 +66,9 @@
   kSbMediaAudioCodecVorbis,
 } SbMediaAudioCodec;
 
-// Types of media support which is a direct map as the result of canPlayType()
-// specified in the following link:
+// Indicates how confident the device is that it can play media resources
+// of the given type. The values are a direct map of the canPlayType() method
+// specified at the following link:
 // https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
 typedef enum SbMediaSupportType {
   // The media type cannot be played.
@@ -111,24 +115,27 @@
   kSbMediaAudioSampleTypeFloat32,
 } SbMediaAudioSampleType;
 
-// Possible audio frame storage types.  Interleaved means the samples of a
-// multi-channel audio stream are stored in one continuous buffer, samples at
-// the same timestamp are stored one after another.  Planar means the samples
-// of each channel are stored in their own continuous buffer. For example, for
-// stereo stream with channels L and R that contains samples with timestamp
-// 0, 1, ..., interleaved means the samples are stored in one buffer as
-// "L0 R0 L1 R1 L2 R2 ...".  Planar means the samples are stored in two buffers
-// "L0 L1 L2 ..." and "R0 R1 R2 ...".
+// Possible audio frame storage types.
 typedef enum SbMediaAudioFrameStorageType {
+  // The samples of a multi-channel audio stream are stored in one continuous
+  // buffer. Samples at the same timestamp are stored one after another. For
+  // example, for a stereo stream with channels L and R that contains samples
+  // with timestamps 0, 1, 2, etc., the samples are stored in one buffer as
+  // "L0 R0 L1 R1 L2 R2 ...".
   kSbMediaAudioFrameStorageTypeInterleaved,
+
+  // The samples of each channel are stored in their own continuous buffer.
+  // For example, for a stereo stream with channels L and R that contains
+  // samples with timestamps 0, 1, 2, etc., the samples are stored in two
+  // buffers "L0 L1 L2 ..." and "R0 R1 R2 ...".
   kSbMediaAudioFrameStorageTypePlanar,
 } SbMediaAudioFrameStorageType;
 
 // The set of information required by the decoder or player for each video
 // sample.
 typedef struct SbMediaVideoSampleInfo {
-  // Whether the associated sample is a key frame (I-frame). Video key frames
-  // must always start with SPS and PPS NAL units.
+  // Indicates whether the associated sample is a key frame (I-frame).
+  // Video key frames must always start with SPS and PPS NAL units.
   bool is_key_frame;
 
   // The frame width of this sample, in pixels. Also could be parsed from the
@@ -148,11 +155,11 @@
   // The platform-defined index of the associated audio output.
   int index;
 
-  // The type of audio connector. Will be the empty kSbMediaAudioConnectorNone
+  // The type of audio connector. Will be the empty |kSbMediaAudioConnectorNone|
   // if this device cannot provide this information.
   SbMediaAudioConnector connector;
 
-  // The expected latency of audio over this output, in microseconds, or 0 if
+  // The expected latency of audio over this output, in microseconds, or |0| if
   // this device cannot provide this information.
   SbTime latency;
 
@@ -160,8 +167,8 @@
   SbMediaAudioCodingType coding_type;
 
   // The number of audio channels currently supported by this device output, or
-  // 0 if this device cannot provide this information, in which case the caller
-  // can probably assume stereo output.
+  // |0| if this device cannot provide this information, in which case the
+  // caller can probably assume stereo output.
   int number_of_channels;
 } SbMediaAudioConfiguration;
 
@@ -170,18 +177,14 @@
 // decoder.
 //
 // The Sequence Header consists of a little-endian hexadecimal encoded
-// WAVEFORMATEX structure followed by an Audio-specific configuration field.
-//
-// Specification of WAVEFORMATEX structure:
+// |WAVEFORMATEX| structure followed by an Audio-specific configuration field.
+// The |WAVEFORMATEX| structure is specified at:
 // http://msdn.microsoft.com/en-us/library/dd390970(v=vs.85).aspx
-//
-// AudioSpecificConfig defined here in section 1.6.2.1:
-// http://read.pudn.com/downloads98/doc/comm/401153/14496/ISO_IEC_14496-3%20Part%203%20Audio/C036083E_SUB1.PDF
 typedef struct SbMediaAudioHeader {
   // The waveform-audio format type code.
   uint16_t format_tag;
 
-  // The number of audio channels in this format. 1 for mono, 2 for stereo.
+  // The number of audio channels in this format. |1| for mono, |2| for stereo.
   uint16_t number_of_channels;
 
   // The sampling rate.
@@ -193,13 +196,14 @@
   // Byte block alignment, e.g, 4.
   uint16_t block_alignment;
 
-  // The bit depth for the stream this represents, e.g. 8 or 16.
+  // The bit depth for the stream this represents, e.g. |8| or |16|.
   uint16_t bits_per_sample;
 
   // The size, in bytes, of the audio_specific_config.
   uint16_t audio_specific_config_size;
 
-  // The AudioSpecificConfig, as specified in ISO/IEC-14496-3.
+  // The AudioSpecificConfig, as specified in ISO/IEC-14496-3, section 1.6.2.1:
+  // http://read.pudn.com/downloads98/doc/comm/401153/14496/ISO_IEC_14496-3%20Part%203%20Audio/C036083E_SUB1.PDF
   int8_t audio_specific_config[8];
 } SbMediaAudioHeader;
 
@@ -209,73 +213,99 @@
 
 // --- Functions -------------------------------------------------------------
 
-// Returns whether decoding |video_codec|, |audio_codec|, and decrypting using
-// |key_system| is supported together by this platform.  If |video_codec| is
-// kSbMediaVideoCodecNone or |audio_codec| is kSbMediaAudioCodecNone, this
-// function should return true if |key_system| is supported on the platform to
-// decode any supported input formats.
+// Indicates whether this platform supports decoding |video_codec| and
+// |audio_codec| along with decrypting using |key_system|. If |video_codec| is
+// |kSbMediaVideoCodecNone| or if |audio_codec| is |kSbMediaAudioCodecNone|,
+// this function should return |true| as long as |key_system| is supported on
+// the platform to decode any supported input formats.
+//
+// |video_codec|: The |SbMediaVideoCodec| being checked for platform
+//   compatibility.
+// |audio_codec|: The |SbMediaAudioCodec| being checked for platform
+//   compatibility.
+// |key_system|: The key system being checked for platform compatibility.
 SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec,
                                   SbMediaAudioCodec audio_codec,
                                   const char* key_system);
 
-// Returns whether a given combination of |frame_width| x |frame_height| frames
-// at |bitrate| and |fps| is supported on this platform with |video_codec|. If
-// |video_codec| is not supported under any condition, this function will always
-// return false.
+// Indicates whether a given combination of
+// (|frame_width| x |frame_height|) frames at |bitrate| and |fps| is supported
+// on this platform with |video_codec|. If |video_codec| is not supported under
+// any condition, this function returns |false|.
 //
-// Any of the parameters may be set to 0 to mean that they shouldn't be
+// Setting any of the parameters to |0| indicates that they shouldn't be
 // considered.
+//
+// |video_codec|: The video codec used in the media content.
+// |frame_width|: The frame width of the media content.
+// |frame_height|: The frame height of the media content.
+// |bitrate|: The bitrate of the media content.
+// |fps|: The number of frames per second in the media content.
 SB_EXPORT bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
                                        int frame_width,
                                        int frame_height,
                                        int64_t bitrate,
                                        int fps);
 
-// Returns whether |audio_codec| is supported on this platform at |bitrate|. If
-// |audio_codec| is not supported under any condition, this function will always
-// return false.
+// Indicates whether this platform supports |audio_codec| at |bitrate|.
+// If |audio_codec| is not supported under any condition, this function
+// returns |false|.
+//
+// |audio_codec|: The media's audio codec (|SbMediaAudioCodec|).
+// |bitrate|: The media's bitrate.
 SB_EXPORT bool SbMediaIsAudioSupported(SbMediaVideoCodec audio_codec,
                                        int64_t bitrate);
 
-// Returns information on whether the playback of the specific media described
-// by |mime| and encrypted using |key_system| can be played.
-// |mime| contains the mime information of the media in the form of 'video/webm'
-// or 'video/mp4; codecs="avc1.42001E"'.  It may include arbitrary parameters
-// like "codecs", "channels", etc..
-// |key_system| should be a lower case in fhe form of
-// "com.example.somesystem" as suggested by
-// https://w3c.github.io/encrypted-media/#key-system
+// Returns information about whether the playback of the specific media
+// described by |mime| and encrypted using |key_system| can be played.
+//
+// Note that neither |mime| nor |key_system| can be NULL. This function returns
+// |kSbMediaSupportNotSupported| if either is NULL.
+//
+// |mime|: The mime information of the media in the form of |video/webm|
+//   or |video/mp4; codecs="avc1.42001E"|. It may include arbitrary parameters
+//   like "codecs", "channels", etc.
+// |key_system|: A lowercase value in fhe form of "com.example.somesystem"
+// as suggested by https://w3c.github.io/encrypted-media/#key-system
 // that can be matched exactly with known DRM key systems of the platform.
 // When |key_system| is an empty string, the return value is an indication for
 // non-encrypted media.
-// Note that both |mime| and |key_system| cannot be NULL.  This function returns
-// kSbMediaSupportNotSupported if any of them is NULL.
 SB_EXPORT SbMediaSupportType
 SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system);
 
-// Returns the number of audio outputs currently available on this device.  It
-// is expected that, even if the number of outputs or their audio configurations
-// can't be determined, the platform will at least return a single output that
-// supports at least stereo.
+// Returns the number of audio outputs currently available on this device.
+// Even if the number of outputs or their audio configurations can't be
+// determined, it is expected that the platform will at least return a single
+// output that supports at least stereo.
 SB_EXPORT int SbMediaGetAudioOutputCount();
 
-// Places the current physical audio configuration of audio output
-// |output_index| on this device into |out_configuration|, which must not be
-// NULL, or returns false if nothing could be determined on this platform, or if
-// |output_index| doesn't exist on this device.
+// Retrieves the current physical audio configuration of audio output
+// |output_index| on this device and places it in |out_configuration|,
+// which must not be NULL.
+//
+// This function returns |false| if nothing could be determined on this
+// platform or if |output_index| does not exist on this device.
+//
+// |out_configuration|: The variable that holds the audio configuration
+//   information.
 SB_EXPORT bool SbMediaGetAudioConfiguration(
     int output_index,
     SbMediaAudioConfiguration* out_configuration);
 
-// Returns whether output copy protection is currently enabled on all capable
-// outputs. If true, then non-protection-capable outputs are expected to be
+// Indicates whether output copy protection is currently enabled on all capable
+// outputs. If |true|, then non-protection-capable outputs are expected to be
 // blanked.
 SB_EXPORT bool SbMediaIsOutputProtected();
 
 // Enables or disables output copy protection on all capable outputs. If
 // enabled, then non-protection-capable outputs are expected to be blanked.
-// Returns whether the operation was successful. Returns a success even if the
-// call was redundant.
+//
+// The return value indicates whether the operation was successful, and the
+// function returns a success even if the call is redundant in that it doesn't
+// change the current value.
+//
+// |enabled|: Indicates whether output protection is enabled (|true|) or
+//   disabled.
 SB_EXPORT bool SbMediaSetOutputProtection(bool enabled);
 
 #ifdef __cplusplus
diff --git a/src/starboard/memory.h b/src/starboard/memory.h
index f418c30..51102d6 100644
--- a/src/starboard/memory.h
+++ b/src/starboard/memory.h
@@ -12,11 +12,33 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Memory allocation, alignment, copying, and comparing.
+// Module Overview: Starboard Memory module
+//
+// Defines functions for memory allocation, alignment, copying, and comparing.
+//
+// #### Porters
+//
+// All of the "Unchecked" and "Free" functions must be implemented, but they
+// should not be called directly. The Starboard platform wraps them with extra
+// accounting under certain circumstances.
+//
+// #### Porters and Cobalt developers
+//
+// Nobody should call the "Checked", "Unchecked" or "Free" functions directly
+// because that evades Starboard's memory tracking. In both port implementations
+// and Cobalt code, you should always call SbMemoryAllocate and
+// SbMemoryDeallocate rather than SbMemoryAllocateUnchecked and SbMemoryFree.
+//
+// - The "checked" functions are SbMemoryAllocateChecked(),
+//   SbMemoryReallocateChecked(), and SbMemoryAllocateAlignedChecked().
+// - The "unchecked" functions are SbMemoryAllocateUnchecked(),
+//   SbMemoryReallocateUnchecked(), and SbMemoryAllocateAlignedUnchecked().
+// - The "free" functions are SbMemoryFree() and SbMemoryFreeAligned().
 
 #ifndef STARBOARD_MEMORY_H_
 #define STARBOARD_MEMORY_H_
 
+#include "starboard/configuration.h"
 #include "starboard/export.h"
 #include "starboard/system.h"
 #include "starboard/types.h"
@@ -27,16 +49,6 @@
 
 #define SB_MEMORY_MAP_FAILED ((void*)-1)  // NOLINT(readability/casting)
 
-#if defined(SB_ABORT_ON_ALLOCATION_FAILURE)
-#define SbMemoryAllocate SbMemoryAllocateChecked
-#define SbMemoryReallocate SbMemoryReallocateChecked
-#define SbMemoryAllocateAligned SbMemoryAllocateAlignedChecked
-#else
-#define SbMemoryAllocate SbMemoryAllocateUnchecked
-#define SbMemoryReallocate SbMemoryReallocateUnchecked
-#define SbMemoryAllocateAligned SbMemoryAllocateAlignedUnchecked
-#endif
-
 // The bitwise OR of these flags should be passed to SbMemoryMap to indicate
 // how the mapped memory can be used.
 typedef enum SbMemoryMapFlags {
@@ -68,79 +80,125 @@
   }
 }
 
-// Allocates a chunk of memory of at least |size| bytes, returning it. If unable
-// to allocate the memory, it returns NULL. If |size| is 0, it may return NULL
-// or it may return a unique pointer value that can be passed to
-// SbMemoryFree. Meant to be a drop-in replacement for malloc.
-SB_EXPORT void* SbMemoryAllocateUnchecked(size_t size);
+// Allocates and returns a chunk of memory of at least |size| bytes. This
+// function should be called from the Cobalt codebase. It is intended to
+// be a drop-in replacement for |malloc|.
+//
+// Note that this function returns |NULL| if it is unable to allocate the
+// memory.
+//
+// |size|: The amount of memory to be allocated. If |size| is 0, the function
+//   may return |NULL| or it may return a unique pointer value that can be
+//   passed to SbMemoryDeallocate.
+SB_EXPORT void* SbMemoryAllocate(size_t size);
 
-// Same as SbMemoryAllocateUnchecked, but will abort() in the case of an
-// allocation failure.
-static SB_C_FORCE_INLINE void* SbMemoryAllocateChecked(size_t size) {
-  void* address = SbMemoryAllocateUnchecked(size);
-  SbAbortIfAllocationFailed(size, address);
-  return address;
-}
+// Attempts to resize |memory| to be at least |size| bytes, without touching
+// the contents of memory.
+// - If the function cannot perform the fast resize, it allocates a new chunk
+//   of memory, copies the contents over, and frees the previous chunk,
+//   returning a pointer to the new chunk.
+// - If the function cannot perform the slow resize, it returns |NULL|,
+//   leaving the given memory chunk unchanged.
+//
+// This function should be called from the Cobalt codebase. It is meant
+// to be a drop-in replacement for |realloc|.
+//
+// |memory|: The chunk of memory to be resized. |memory| may be NULL, in which
+//   case it behaves exactly like SbMemoryAllocateUnchecked.
+// |size|: The size to which |memory| will be resized. If |size| is |0|,
+//   the function may return |NULL| or it may return a unique pointer value
+//   that can be passed to SbMemoryDeallocate.
+SB_EXPORT void* SbMemoryReallocate(void* memory, size_t size);
 
-// Attempts to resize |memory| to be at least |size| bytes, without touching the
-// contents. If it cannot perform the fast resize, it will allocate a new chunk
-// of memory, copy the contents over, and free the previous chunk, returning a
-// pointer to the new chunk. If it cannot perform the slow resize, it will
-// return NULL, leaving the given memory chunk unchanged. |memory| may be NULL,
-// in which case it behaves exactly like SbMemoryAllocateUnchecked.  If |size|
-// is 0, it may return NULL or it may return a unique pointer value that can be
-// passed to SbMemoryFree. Meant to be a drop-in replacement for realloc.
-SB_EXPORT void* SbMemoryReallocateUnchecked(void* memory, size_t size);
+// Allocates and returns a chunk of memory of at least |size| bytes, aligned
+// to |alignment|. This function should be called from the Cobalt codebase.
+// It is meant to be a drop-in replacement for |memalign|.
+//
+// The function returns |NULL| if it cannot allocate the memory. In addition,
+// the function's behavior is undefined if |alignment| is not a power of two.
+//
+// |alignment|: The way that data is arranged and accessed in memory. The value
+//   must be a power of two.
+// |size|: The size of the memory to be allocated. If |size| is |0|, the
+//   function may return |NULL| or it may return a unique aligned pointer value
+//   that can be passed to SbMemoryDeallocateAligned.
+SB_EXPORT void* SbMemoryAllocateAligned(size_t alignment, size_t size);
 
-// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an
-// allocation failure
-static SB_C_FORCE_INLINE void* SbMemoryReallocateChecked(void* memory,
-                                                         size_t size) {
-  void* address = SbMemoryReallocateUnchecked(memory, size);
-  SbAbortIfAllocationFailed(size, address);
-  return address;
-}
+// Frees a previously allocated chunk of memory. If |memory| is NULL, then the
+// operation is a no-op. This function should be called from the Cobalt
+// codebase. It is meant to be a drop-in replacement for |free|.
+//
+// |memory|: The chunk of memory to be freed.
+SB_EXPORT void SbMemoryDeallocate(void* memory);
 
-// Frees a previously allocated chunk of memory. If |memory| is NULL, then it
-// does nothing.  Meant to be a drop-in replacement for free.
-SB_EXPORT void SbMemoryFree(void* memory);
+// Frees a previously allocated chunk of aligned memory. This function should
+// be called from the Cobalt codebase. It is meant to be a drop-in replacement
+// for |_aligned_free|.
 
-// Allocates a chunk of memory of at least |size| bytes, aligned to |alignment|,
-// returning it. |alignment| must be a power of two, otherwise behavior is
-// undefined. If unable to allocate the memory, it returns NULL. If |size| is 0,
-// it may return NULL or it may return a unique aligned pointer value that can
-// be passed to SbMemoryFreeAligned. Meant to be a drop-in replacement for
-// memalign.
-SB_EXPORT void* SbMemoryAllocateAlignedUnchecked(size_t alignment, size_t size);
+// |memory|: The chunk of memory to be freed. If |memory| is NULL, then the
+//   function is a no-op.
+SB_EXPORT void SbMemoryDeallocateAligned(void* memory);
 
-// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an
-// allocation failure
-static SB_C_FORCE_INLINE void* SbMemoryAllocateAlignedChecked(size_t alignment,
-                                                              size_t size) {
-  void* address = SbMemoryAllocateAlignedUnchecked(alignment, size);
-  SbAbortIfAllocationFailed(size, address);
-  return address;
-}
+/////////////////////////////////////////////////////////////////
+// The following functions must be provided by Starboard ports.
+/////////////////////////////////////////////////////////////////
 
-// Frees a previously allocated chunk of aligned memory. If |memory| is NULL,
-// then it does nothing.  Meant to be a drop-in replacement for _aligned_free.
-SB_EXPORT void SbMemoryFreeAligned(void* memory);
+// This is the implementation of SbMemoryAllocate that must be
+// provided by Starboard ports.
+//
+// DO NOT CALL. Call SbMemoryAllocate(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryAllocateUnchecked(size_t size));
+
+// This is the implementation of SbMemoryReallocate that must be
+// provided by Starboard ports.
+//
+// DO NOT CALL. Call SbMemoryReallocate(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryReallocateUnchecked(void* memory, size_t size));
+
+// This is the implementation of SbMemoryAllocateAligned that must be
+// provided by Starboard ports.
+//
+// DO NOT CALL. Call SbMemoryAllocateAligned(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryAllocateAlignedUnchecked(size_t alignment,
+                                                     size_t size));
+
+// This is the implementation of SbMemoryDeallocate that must be provided by
+// Starboard ports.
+//
+// DO NOT CALL. Call SbMemoryDeallocate(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void SbMemoryFree(void* memory));
+
+// This is the implementation of SbMemoryFreeAligned that must be provided by
+// Starboard ports.
+//
+// DO NOT CALL. Call SbMemoryDeallocateAligned(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void SbMemoryFreeAligned(void* memory));
 
 #if SB_HAS(MMAP)
 // Allocates |size_bytes| worth of physical memory pages and maps them into an
-// available virtual region. |flags| is the bitwise or of the protection flags
-// for the mapped memory as specified in SbMemoryMapFlags. On some platforms,
-// |name| appears in the debugger and can be up to 32 bytes. Returns
-// SB_MEMORY_MAP_FAILED on failure, as NULL is a valid return value.
+// available virtual region. This function returns |SB_MEMORY_MAP_FAILED| on
+// failure. |NULL| is a valid return value.
+//
+// |size_bytes|: The amount of physical memory pages to be allocated.
+// |flags|: The bitwise OR of the protection flags for the mapped memory
+//   as specified in |SbMemoryMapFlags|.
+// |name|: A value that appears in the debugger on some platforms. The value
+//   can be up to 32 bytes.
 SB_EXPORT void* SbMemoryMap(int64_t size_bytes, int flags, const char* name);
 
 // Unmap |size_bytes| of physical pages starting from |virtual_address|,
-// returning true on success. After this, [virtual_address, virtual_address +
-// size_bytes) will not be read/writable. SbMemoryUnmap() can unmap multiple
-// contiguous regions that were mapped with separate calls to
-// SbMemoryMap(). E.g. if one call to SbMemoryMap(0x1000) returns (void*)0xA000
-// and another call to SbMemoryMap(0x1000) returns (void*)0xB000,
-// SbMemoryUnmap(0xA000, 0x2000) should free both.
+// returning |true| on success. After this function completes,
+// [virtual_address, virtual_address + size_bytes) will not be read/writable.
+// This function can unmap multiple contiguous regions that were mapped with
+// separate calls to SbMemoryMap(). For example, if one call to
+// |SbMemoryMap(0x1000)| returns |(void*)0xA000|, and another call to
+// |SbMemoryMap(0x1000)| returns |(void*)0xB000|,
+// |SbMemoryUnmap(0xA000, 0x2000)| should free both regions.
 SB_EXPORT bool SbMemoryUnmap(void* virtual_address, int64_t size_bytes);
 
 #if SB_CAN(MAP_EXECUTABLE_MEMORY)
@@ -152,51 +210,78 @@
 #endif
 #endif  // SB_HAS(MMAP)
 
-// Gets the stack bounds for the current thread, placing the highest addressable
-// byte + 1 in |out_high|, and the lowest addressable byte in |out_low|.
+// Gets the stack bounds for the current thread.
+//
+// |out_high|: The highest addressable byte + 1 for the current thread.
+// |out_low|: The lowest addressable byte for the current thread.
 SB_EXPORT void SbMemoryGetStackBounds(void** out_high, void** out_low);
 
 // Copies |count| sequential bytes from |source| to |destination|, without
-// support for the |source| and |destination| regions overlapping.  Behavior is
-// undefined if |destination| or |source| are NULL. Does nothing if |count| is
-// 0. Returns |destination|, for some reason. Meant to be a drop-in replacement
-// for memcpy.
+// support for the |source| and |destination| regions overlapping. This
+// function is meant to be a drop-in replacement for |memcpy|.
+//
+// The function's behavior is undefined if |destination| or |source| are NULL,
+// and the function is a no-op if |count| is 0. The return value is
+// |destination|.
+//
+// |destination|: The destination of the copied memory.
+// |source|: The source of the copied memory.
+// |count|: The number of sequential bytes to be copied.
 SB_EXPORT void* SbMemoryCopy(void* destination,
                              const void* source,
                              size_t count);
 
 // Copies |count| sequential bytes from |source| to |destination|, with support
-// for the |source| and |destination| regions overlapping.  Behavior is
-// undefined if |destination| or |source| are NULL. Does nothing if |count| is
-// 0. Returns |destination|, for some reason. Meant to be a drop-in replacement
-// for memmove.
+// for the |source| and |destination| regions overlapping. This function is
+// meant to be a drop-in replacement for |memmove|.
+//
+// The function's behavior is undefined if |destination| or |source| are NULL,
+// and the function is a no-op if |count| is 0. The return value is
+// |destination|.
+//
+// |destination|: The destination of the copied memory.
+// |source|: The source of the copied memory.
+// |count|: The number of sequential bytes to be copied.
 SB_EXPORT void* SbMemoryMove(void* destination,
                              const void* source,
                              size_t count);
 
 // Fills |count| sequential bytes starting at |destination|, with the unsigned
-// char coercion of |byte_value|. Behavior is undefined if |destination| is
-// NULL. Returns |destination|, for some reason. Does nothing if |count| is 0.
-// Meant to be a drop-in replacement for memset.
+// char coercion of |byte_value|. This function is meant to be a drop-in
+// replacement for |memset|.
+//
+// The function's behavior is undefined if |destination| is NULL, and the
+// function is a no-op if |count| is 0. The return value is |destination|.
+//
+// |destination|: The destination of the copied memory.
+// |count|: The number of sequential bytes to be set.
 SB_EXPORT void* SbMemorySet(void* destination, int byte_value, size_t count);
 
 // Compares the contents of the first |count| bytes of |buffer1| and |buffer2|.
-// returns -1 if |buffer1| is "less-than" |buffer2|, 0 if they are equal, or 1
-// if |buffer1| is "greater-than" |buffer2|.  Meant to be a drop-in replacement
-// for memcmp.
+// This function returns:
+// - |-1| if |buffer1| is "less-than" |buffer2|
+// - |0| if |buffer1| and |buffer2| are equal
+// - |1| if |buffer1| is "greater-than" |buffer2|.
+//
+// This function is meant to be a drop-in replacement for |memcmp|.
+//
+// |buffer1|: The first buffer to be compared.
+// |buffer2|: The second buffer to be compared.
+// |count|: The number of bytes to be compared.
 SB_EXPORT int SbMemoryCompare(const void* buffer1,
                               const void* buffer2,
                               size_t count);
 
-// Finds the lower 8-bits of |value| in the first |count| bytes of |buffer|,
-// returning a pointer to the first found occurrence, or NULL if not
-// found. Meant to be a drop-in replacement for memchr.
+// Finds the lower 8-bits of |value| in the first |count| bytes of |buffer|
+// and returns either a pointer to the first found occurrence or |NULL| if
+// the value is not found. This function is meant to be a drop-in replacement
+// for |memchr|.
 SB_EXPORT const void* SbMemoryFindByte(const void* buffer,
                                        int value,
                                        size_t count);
 
-// A wrapper that implements a drop-in replacement for calloc, since some
-// packages actually seem to use it.
+// A wrapper that implements a drop-in replacement for |calloc|, which is used
+// in some packages.
 static SB_C_INLINE void* SbMemoryCalloc(size_t count, size_t size) {
   size_t total = count * size;
   void* result = SbMemoryAllocate(total);
@@ -206,6 +291,32 @@
   return result;
 }
 
+/////////////////////////////////////////////////////////////////
+// Deprecated. Do not use.
+/////////////////////////////////////////////////////////////////
+
+// Same as SbMemoryAllocateUnchecked, but will abort() in the case of an
+// allocation failure.
+//
+// DO NOT CALL. Call SbMemoryAllocate(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryAllocateChecked(size_t size));
+
+// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an
+// allocation failure.
+//
+// DO NOT CALL. Call SbMemoryReallocate(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryReallocateChecked(void* memory, size_t size));
+
+// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an
+// allocation failure.
+//
+// DO NOT CALL. Call SbMemoryAllocateAligned(...) instead.
+SB_DEPRECATED_EXTERNAL(
+    SB_EXPORT void* SbMemoryAllocateAlignedChecked(size_t alignment,
+                                                   size_t size));
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/src/starboard/memory_reporter.h b/src/starboard/memory_reporter.h
new file mode 100644
index 0000000..1a17c38
--- /dev/null
+++ b/src/starboard/memory_reporter.h
@@ -0,0 +1,105 @@
+// 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: Memory Reporting API.
+//
+// Provides an interface for memory reporting.
+
+#ifndef STARBOARD_MEMORY_REPORTER_H_
+#define STARBOARD_MEMORY_REPORTER_H_
+
+#include "starboard/configuration.h"
+#include "starboard/export.h"
+#include "starboard/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// These are callbacks used by SbMemoryReporter to report memory
+// allocation and deallocation.
+///////////////////////////////////////////////////////////////////////////////
+
+// A function to report a memory allocation from SbMemoryAllocate(). Note that
+// operator new calls SbMemoryAllocate which will delegate to this callback.
+typedef void (*SbMemoryReporterOnAlloc)(void* context, const void* memory,
+                                        size_t size);
+
+// A function to report a memory deallocation from SbMemoryDeallcoate(). Note
+// that operator delete calls SbMemoryDeallocate which will delegate to this
+// callback.
+typedef void (*SbMemoryReporterOnDealloc)(void* context, const void* memory);
+
+// A function to report a memory mapping from SbMemoryMap().
+typedef void (*SbMemoryReporterOnMapMemory)(void* context, const void* memory,
+                                            size_t size);
+
+// A function to report a memory unmapping from SbMemoryUnmap().
+typedef void (*SbMemoryReporterOnUnMapMemory)(void* context,
+                                              const void* memory,
+                                              size_t size);
+
+// SbMemoryReporter allows memory reporting via user-supplied functions.
+// The void* context is passed to every call back.
+// It's strongly recommended that C-Style struct initialization is used
+// so that the arguments can be typed check by the compiler.
+// For example,
+//  SbMemoryReporter mem_reporter = { MallocCallback, .... context };
+struct SbMemoryReporter {
+  // Callback to report allocations.
+  SbMemoryReporterOnAlloc on_alloc_cb;
+
+  // Callback to report deallocations.
+  SbMemoryReporterOnDealloc on_dealloc_cb;
+
+  // Callback to report memory map.
+  SbMemoryReporterOnMapMemory on_mapmem_cb;
+
+  // Callback to report memory unmap.
+  SbMemoryReporterOnUnMapMemory on_unmapmem_cb;
+
+  // Optional, is passed to callbacks as first argument.
+  void* context;
+};
+
+// Sets the MemoryReporter. Any previous memory reporter is unset. No lifetime
+// management is done internally on input pointer.
+//
+// Returns true if the memory reporter was set with no errors. If an error
+// was reported then check the log for why it failed.
+//
+// Note that other than a thread-barrier-write of the input pointer, there is
+// no thread safety guarantees with this function due to performance
+// considerations. It's recommended that this be called once during the
+// lifetime of the program, or not at all. Do not delete the supplied pointer,
+// ever.
+// Example (Good):
+//    SbMemoryReporter* mem_reporter = new ...;
+//    SbMemorySetReporter(&mem_reporter);
+//    ...
+//    SbMemorySetReporter(NULL);  // allow value to leak.
+// Example (Bad):
+//    SbMemoryReporter* mem_reporter = new ...;
+//    SbMemorySetReporter(&mem_reporter);
+//    ...
+//    SbMemorySetReporter(NULL);
+//    delete mem_reporter;        // May crash.
+SB_EXPORT bool SbMemorySetReporter(struct SbMemoryReporter* tracker);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_MEMORY_REPORTER_H_
diff --git a/src/starboard/microphone.h b/src/starboard/microphone.h
new file mode 100644
index 0000000..52464c3
--- /dev/null
+++ b/src/starboard/microphone.h
@@ -0,0 +1,199 @@
+// 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 Microphone module
+//
+// Defines functions for microphone creation, control, audio data fetching,
+// and destruction. This module supports multiple calls to |SbMicrophoneOpen|
+// and |SbMicrophoneClose|, and the implementation should handle multiple calls
+// to one of those functions on the same microphone. For example, your
+// implementation should handle cases where |SbMicrophoneOpen| is called twice
+// on the same microphone without a call to |SbMicrophoneClose| in between.
+//
+// This API is not thread-safe and must be called from a single thread.
+//
+// How to use this API:
+// 1. Call |SbMicrophoneGetAvailableInfos| to get a list of available microphone
+//    information.
+// 2. Create a supported microphone, using |SbMicrophoneCreate|, with enough
+//    buffer size and sample rate. Use |SbMicrophoneIsSampleRateSupported| to
+//    verify the sample rate.
+// 3. Use |SbMicrophoneOpen| to open the microphone port and start recording
+//    audio data.
+// 4. Periodically read out the data from microphone with |SbMicrophoneRead|.
+// 5. Call |SbMicrophoneClose| to close the microphone port and stop recording
+//    audio data.
+// 6. Destroy the microphone with |SbMicrophoneDestroy|.
+
+#ifndef STARBOARD_MICROPHONE_H_
+#define STARBOARD_MICROPHONE_H_
+
+#include "starboard/configuration.h"
+#include "starboard/export.h"
+#include "starboard/types.h"
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// All possible microphone types.
+typedef enum SbMicrophoneType {
+  // Built-in microphone in camera.
+  kSbMicrophoneCamera,
+
+  // Microphone in the headset that can be a wired or wireless USB headset.
+  kSbMicrophoneUSBHeadset,
+
+  // Microphone in the VR headset.
+  kSbMicrophoneVRHeadset,
+
+  // Microphone in the analog headset.
+  kSBMicrophoneAnalogHeadset,
+
+  // Unknown microphone type. The microphone could be different than the other
+  // enum descriptions or could fall under one of those descriptions.
+  kSbMicrophoneUnknown,
+} SbMicrophoneType;
+
+// An opaque handle to an implementation-private structure that represents a
+// microphone ID.
+typedef struct SbMicrophoneIdPrivate* SbMicrophoneId;
+
+// Well-defined value for an invalid microphone ID handle.
+#define kSbMicrophoneIdInvalid ((SbMicrophoneId)NULL)
+
+// Indicates whether the given microphone ID is valid.
+static SB_C_INLINE bool SbMicrophoneIdIsValid(SbMicrophoneId id) {
+  return id != kSbMicrophoneIdInvalid;
+}
+
+// Microphone information.
+typedef struct SbMicrophoneInfo {
+  // Microphone id.
+  SbMicrophoneId id;
+
+  // Microphone type.
+  SbMicrophoneType type;
+
+  // The microphone's maximum 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 that represents a
+// microphone.
+typedef struct SbMicrophonePrivate* SbMicrophone;
+
+// Well-defined value for an invalid microphone handle.
+#define kSbMicrophoneInvalid ((SbMicrophone)NULL)
+
+// Indicates whether the given microphone is valid.
+static SB_C_INLINE bool SbMicrophoneIsValid(SbMicrophone microphone) {
+  return microphone != kSbMicrophoneInvalid;
+}
+
+// Retrieves all currently available microphone information and stores it in
+// |out_info_array|. The return value is the number of the available
+// microphones. If the number of available microphones is larger than
+// |info_array_size|, then |out_info_array| is filled up with as many available
+// microphones as possible and the actual number of available microphones is
+// returned. A negative return value indicates that an internal error occurred.
+//
+// |out_info_array|: All currently available information about the microphone
+//   is placed into this output parameter.
+// |info_array_size|: The size of |out_info_array|.
+SB_EXPORT int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array,
+                                       int info_array_size);
+
+// Indicates whether the microphone supports the sample rate.
+SB_EXPORT bool SbMicrophoneIsSampleRateSupported(SbMicrophoneId id,
+                                                 int sample_rate_in_hz);
+
+// Creates a microphone with the specified ID, audio sample rate, and cached
+// audio buffer size. Starboard only requires support for creating one
+// microphone at a time, and implementations may return an error if a second
+// microphone is created before the first is destroyed.
+//
+// The function returns the newly created SbMicrophone object. However, if you
+// try to create a microphone that has already been initialized, if the sample
+// rate is unavailable, or if the buffer size is invalid, the function should
+// return |kSbMicrophoneInvalid|.
+//
+// |id|: The ID that will be assigned to the newly created SbMicrophone.
+// |sample_rate_in_hz|: The new microphone's audio sample rate in Hz.
+// |buffer_size_bytes|: The size of the buffer where signed 16-bit integer
+//   audio data is temporarily cached to during the capturing. The audio data
+//   is removed from the audio buffer if it has been read, and new audio data
+//   can be read from this buffer in smaller chunks than this size. This
+//   parameter must be set to a value greater than zero and the ideal size is
+//   |2^n|.
+SB_EXPORT SbMicrophone SbMicrophoneCreate(SbMicrophoneId id,
+                                          int sample_rate_in_hz,
+                                          int buffer_size_bytes);
+
+// Opens the microphone port and starts recording audio on |microphone|.
+//
+// Once started, the client needs to periodically call |SbMicrophoneRead| to
+// receive the audio data. If the microphone has already been started, this call
+// clears the unread buffer. The return value indicates whether the microphone
+// is open.
+// |microphone|: The microphone that will be opened and will start recording
+// audio.
+SB_EXPORT bool SbMicrophoneOpen(SbMicrophone microphone);
+
+// Closes the microphone port, stops recording audio on |microphone|, and
+// clears the unread buffer if it is not empty. If the microphone has already
+// been stopped, this call is ignored. The return value indicates whether the
+// microphone is closed.
+//
+// |microphone|: The microphone to close.
+SB_EXPORT bool SbMicrophoneClose(SbMicrophone microphone);
+
+// Retrieves the recorded audio data from the microphone and writes that data
+// to |out_audio_data|.
+//
+// The return value is zero or the positive number of bytes that were read.
+// Neither the return value nor |audio_data_size| exceeds the buffer size.
+// A negative return value indicates that an error occurred.
+//
+// This function should be called frequently. Otherwise, the microphone only
+// buffers |buffer_size| bytes as configured in |SbMicrophoneCreate| and the
+// new audio data is thrown out. No audio data is read from a stopped
+// microphone.
+//
+// |microphone|: The microphone from which to retrieve recorded audio data.
+// |out_audio_data|: The buffer to which the retrieved data will be written.
+// |audio_data_size|: The number of requested bytes. If |audio_data_size| is
+//   smaller than |min_read_size| of |SbMicrophoneInfo|, the extra audio data
+//   that has already been read from the device is discarded.
+SB_EXPORT int SbMicrophoneRead(SbMicrophone microphone,
+                               void* out_audio_data,
+                               int audio_data_size);
+
+// Destroys a microphone. If the microphone is in started state, it is first
+// stopped and then destroyed. Any data that has been recorded and not read
+// is thrown away.
+SB_EXPORT void SbMicrophoneDestroy(SbMicrophone microphone);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+#endif  // STARBOARD_MICROPHONE_H_
diff --git a/src/starboard/mutex.h b/src/starboard/mutex.h
index d06c752..953fa0d 100644
--- a/src/starboard/mutex.h
+++ b/src/starboard/mutex.h
@@ -12,7 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// A mutually exclusive lock that can be used to coordinate with other threads.
+// Module Overview: Starboard Mutex module
+//
+// Defines a mutually exclusive lock that can be used to coordinate with other
+// threads.
 
 #ifndef STARBOARD_MUTEX_H_
 #define STARBOARD_MUTEX_H_
@@ -37,31 +40,46 @@
   kSbMutexDestroyed,
 } SbMutexResult;
 
-// Returns whether the given result is a success.
+// Indicates whether the given result is a success. A value of |true| indicates
+// that the mutex was acquired.
+//
+// |result|: The result being checked.
 static SB_C_FORCE_INLINE bool SbMutexIsSuccess(SbMutexResult result) {
   return result == kSbMutexAcquired;
 }
 
-// Creates a new mutex, placing the handle to the newly created mutex in
-// |out_mutex|. Returns whether able to create a new mutex.
+// Creates a new mutex. The return value indicates whether the function
+// was able to create a new mutex.
+//
+// |out_mutex|: The handle to the newly created mutex.
 SB_EXPORT bool SbMutexCreate(SbMutex* out_mutex);
 
-// Destroys a mutex, returning whether the destruction was successful. The mutex
-// specified by |mutex| is invalidated.
+// Destroys a mutex. The return value indicates whether the destruction was
+// successful.
+//
+// |mutex|: The mutex to be invalidated.
 SB_EXPORT bool SbMutexDestroy(SbMutex* mutex);
 
-// Acquires |mutex|, blocking indefinitely, returning the acquisition result.
-// SbMutexes are not reentrant, so a recursive acquisition will block forever.
+// Acquires |mutex|, blocking indefinitely. The return value identifies
+// the acquisition result. SbMutexes are not reentrant, so a recursive
+// acquisition blocks forever.
+//
+// |mutex|: The mutex to be acquired.
 SB_EXPORT SbMutexResult SbMutexAcquire(SbMutex* mutex);
 
-// Acquires |mutex|, without blocking, returning the acquisition result.
-// SbMutexes are not reentrant, so a recursive acquisition will always fail.
+// Acquires |mutex|, without blocking. The return value identifies
+// the acquisition result. SbMutexes are not reentrant, so a recursive
+// acquisition always fails.
+//
+// |mutex|: The mutex to be acquired.
 SB_EXPORT SbMutexResult SbMutexAcquireTry(SbMutex* mutex);
 
-// Releases |mutex| held by the current thread, returning whether the release
-// was successful. Releases should always be successful if the mutex is held by
-// the current thread.
-SB_EXPORT bool SbMutexRelease(SbMutex* handle);
+// Releases |mutex| held by the current thread. The return value indicates
+// whether the release was successful. Releases should always be successful
+// if |mutex| is held by the current thread.
+//
+// |mutex|: The mutex to be released.
+SB_EXPORT bool SbMutexRelease(SbMutex* mutex);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/src/starboard/nplb/blitter_flush_context_test.cc b/src/starboard/nplb/blitter_flush_context_test.cc
index 1095b90..4707e10 100644
--- a/src/starboard/nplb/blitter_flush_context_test.cc
+++ b/src/starboard/nplb/blitter_flush_context_test.cc
@@ -67,6 +67,7 @@
   // Check that flush succeeds after some commands have been submitted.
   EXPECT_TRUE(SbBlitterFlushContext(context));
 
+  EXPECT_TRUE(SbBlitterDestroySurface(surface));
   EXPECT_TRUE(SbBlitterDestroyContext(context));
   EXPECT_TRUE(SbBlitterDestroyDevice(device));
 }
diff --git a/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png
new file mode 100644
index 0000000..27424c4
--- /dev/null
+++ b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png
Binary files differ
diff --git a/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRect-expected.png b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectNotInterpolated-expected.png
similarity index 100%
rename from src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRect-expected.png
rename to src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectNotInterpolated-expected.png
Binary files differ
diff --git a/src/starboard/nplb/blitter_pixel_tests/tests.cc b/src/starboard/nplb/blitter_pixel_tests/tests.cc
index 871c074..9554da7 100644
--- a/src/starboard/nplb/blitter_pixel_tests/tests.cc
+++ b/src/starboard/nplb/blitter_pixel_tests/tests.cc
@@ -99,9 +99,15 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
-TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRect) {
+#if SB_HAS(BILINEAR_FILTERING_SUPPORT)
+TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRectInterpolated) {
+#else
+TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRectNotInterpolated) {
+#endif
   // Create an image with a height and width of 2x2.
   SbBlitterSurface checker_image = CreateCheckerImageWithBlits(
       device_, context_, SbBlitterColorFromRGBA(255, 255, 255, 255),
@@ -112,6 +118,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, 2, 2),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, MinifyBlitRectToRect) {
@@ -124,6 +132,8 @@
   SbBlitterBlitRectToRect(
       context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth() / 8, GetHeight() / 8));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRectToRectPartialSourceRect) {
@@ -137,6 +147,8 @@
       context_, checker_image,
       SbBlitterMakeRect(GetWidth() / 2, 0, GetWidth() / 2, GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRectToRectTiled) {
@@ -149,6 +161,8 @@
   SbBlitterBlitRectToRectTiled(
       context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRectToRectTiledWithNoTiling) {
@@ -161,6 +175,8 @@
   SbBlitterBlitRectToRectTiled(
       context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRectToRectTiledNegativeOffset) {
@@ -174,6 +190,8 @@
       context_, checker_image,
       SbBlitterMakeRect(-GetWidth(), -GetHeight(), GetWidth(), GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRectToRectTiledOffCenter) {
@@ -188,6 +206,8 @@
       SbBlitterMakeRect(-GetWidth() / 2, -GetHeight() / 2, GetWidth(),
                         GetHeight()),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 void DrawRectsWithBatchBlit(SbBlitterSurface texture,
@@ -221,6 +241,8 @@
   SbBlitterSetRenderTarget(context_, render_target_);
 
   DrawRectsWithBatchBlit(checker_image, context_, GetWidth(), GetHeight());
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 SbBlitterSurface CreateCheckerImageWithPixelData(SbBlitterDevice device,
@@ -329,6 +351,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitRedGreenRectToRectFromPixelData) {
@@ -339,6 +363,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitHalfTransparentRectToRectFromPixelData) {
@@ -349,6 +375,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitCanPunchThrough) {
@@ -364,6 +392,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlitCanBlend) {
@@ -379,6 +409,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, SimpleAlphaBlitWithNoColorModulation) {
@@ -393,6 +425,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithNoColorModulationOnRed) {
@@ -406,6 +440,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, AlphaBlitWithBlueColorModulation) {
@@ -418,6 +454,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithBlueColorModulationOnRed) {
@@ -432,6 +470,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithAlphaColorModulationOnRed) {
@@ -446,6 +486,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlendedColorBlitWithAlphaColorModulationOnRed) {
@@ -461,6 +503,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, BlendedColorBlitWithAlphaColorModulation) {
@@ -474,6 +518,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, FillRectColorIsNotPremultiplied) {
@@ -508,6 +554,8 @@
   SbBlitterBlitRectToRect(context_, checker_image,
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()),
                           SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, ScissoredBlitRectToRectTiled) {
@@ -525,6 +573,8 @@
       context_, checker_image,
       SbBlitterMakeRect(0, 0, GetWidth() * 2, GetHeight() * 2),
       SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()));
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, ScissoredBlitRectToRects) {
@@ -539,6 +589,8 @@
   SbBlitterSetScissor(
       context_, SbBlitterMakeRect(32, 32, GetWidth() - 48, GetHeight() - 48));
   DrawRectsWithBatchBlit(checker_image, context_, GetWidth(), GetHeight());
+
+  SbBlitterDestroySurface(checker_image);
 }
 
 TEST_F(SbBlitterPixelTest, ScissorResetsWhenSetRenderTargetIsCalled) {
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..287dc21
--- /dev/null
+++ b/src/starboard/nplb/decode_target_create_test.cc
@@ -0,0 +1,193 @@
+// 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);
+  if (SbDecodeTargetIsValid(target)) {
+    SbBlitterSurface plane =
+        SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+    EXPECT_TRUE(SbBlitterIsSurfaceValid(plane));
+  }
+  SbDecodeTargetDestroy(target);
+  EXPECT_TRUE(SbBlitterDestroySurface(surface));
+}
+#elif SB_HAS(GLES2)  // 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_RGBA, GL_UNSIGNED_BYTE, NULL /*data*/);
+  glBindTexture(GL_TEXTURE_2D, 0);
+
+  SbDecodeTarget target = SbDecodeTargetCreate(
+      display_, context_, kSbDecodeTargetFormat1PlaneRGBA, &texture_handle);
+  if (SbDecodeTargetIsValid(target)) {
+    GLuint plane = SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
+    EXPECT_EQ(texture_handle, plane);
+    SbDecodeTargetDestroy(target);
+  }
+  glDeleteTextures(1, &texture_handle);
+}
+
+#else  // SB_HAS(BLITTER)
+
+TEST(SbDecodeTargetTest, SunnyDayCreate) {
+  // When graphics are not enabled, we expect to always create a
+  // kSbDecodeTargetInvalid, and get NULL back for planes.
+  EXPECT_EQ(SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA),
+            kSbDecodeTargetInvalid);
+  EXPECT_EQ(
+      SbDecodeTargetGetPlane(kSbDecodeTargetInvalid, kSbDecodeTargetPlaneRGBA),
+      NULL);
+}
+
+#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..cd53e40
--- /dev/null
+++ b/src/starboard/nplb/decode_target_provider_test.cc
@@ -0,0 +1,154 @@
+// 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;
+};
+
+void AcquireFalse(SbDecodeTargetProvider* provider) {
+  if (provider) {
+    SbDecodeTarget target = SbDecodeTargetAcquireFromProvider(
+        provider, kSbDecodeTargetFormat1PlaneRGBA, 16, 16);
+    bool valid = SbDecodeTargetIsValid(target);
+    EXPECT_FALSE(valid);
+    if (valid) {
+      SbDecodeTargetDestroy(target);
+    }
+  }
+}
+
+void ReleaseInvalid(SbDecodeTargetProvider* provider) {
+  if (provider) {
+    SbDecodeTargetReleaseToProvider(provider, kSbDecodeTargetInvalid);
+  }
+}
+
+TEST(SbDecodeTargetProviderTest, SunnyDayCallsThroughToProvider) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
+                                     &ProviderContext::Release,
+                                     reinterpret_cast<void*>(&context)};
+
+  EXPECT_EQ(0, context.called_acquire);
+  EXPECT_EQ(0, context.called_release);
+
+  AcquireFalse(&provider);
+  EXPECT_EQ(1, context.called_acquire);
+  AcquireFalse(&provider);
+  EXPECT_EQ(2, context.called_acquire);
+
+  ReleaseInvalid(&provider);
+  EXPECT_EQ(1, context.called_release);
+  ReleaseInvalid(&provider);
+  EXPECT_EQ(2, context.called_release);
+}
+
+TEST(SbDecodeTargetProviderTest, SunnyDayMultipleProviders) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
+                                     &ProviderContext::Release,
+                                     reinterpret_cast<void*>(&context)};
+
+  ProviderContext context2 = {};
+  SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire,
+                                      &ProviderContext::Release,
+                                      reinterpret_cast<void*>(&context2)};
+
+  AcquireFalse(&provider2);
+  EXPECT_EQ(1, context2.called_acquire);
+  EXPECT_EQ(0, context.called_acquire);
+  AcquireFalse(&provider2);
+  EXPECT_EQ(2, context2.called_acquire);
+  EXPECT_EQ(0, context.called_acquire);
+
+  ReleaseInvalid(&provider2);
+  EXPECT_EQ(1, context2.called_release);
+  EXPECT_EQ(0, context.called_release);
+  ReleaseInvalid(&provider2);
+  EXPECT_EQ(2, context2.called_release);
+  EXPECT_EQ(0, context.called_release);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayAcquireNoProvider) {
+  AcquireFalse(NULL);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayReleaseNoProvider) {
+  ReleaseInvalid(NULL);
+}
+
+TEST(SbDecodeTargetProviderTest, RainyDayMultipleProviders) {
+  ProviderContext context = {};
+  SbDecodeTargetProvider provider = {&ProviderContext::Acquire,
+                                     &ProviderContext::Release,
+                                     reinterpret_cast<void*>(&context)};
+
+  ProviderContext context2 = {};
+  SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire,
+                                      &ProviderContext::Release,
+                                      reinterpret_cast<void*>(&context2)};
+
+  AcquireFalse(&provider2);
+  EXPECT_EQ(1, context2.called_acquire);
+  AcquireFalse(&provider2);
+  EXPECT_EQ(2, context2.called_acquire);
+
+  ReleaseInvalid(&provider2);
+  EXPECT_EQ(1, context2.called_release);
+  ReleaseInvalid(&provider2);
+  EXPECT_EQ(2, context2.called_release);
+
+  AcquireFalse(&provider);
+  EXPECT_EQ(2, context2.called_acquire);
+  AcquireFalse(&provider);
+  EXPECT_EQ(2, context2.called_acquire);
+
+  ReleaseInvalid(&provider);
+  EXPECT_EQ(2, context2.called_release);
+  ReleaseInvalid(&provider);
+  EXPECT_EQ(2, context2.called_release);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/file_get_info_test.cc b/src/starboard/nplb/file_get_info_test.cc
index 1722577..fa61d8b 100644
--- a/src/starboard/nplb/file_get_info_test.cc
+++ b/src/starboard/nplb/file_get_info_test.cc
@@ -40,6 +40,11 @@
     // Assuming platforms have at least 1 second precision on filesystem
     // timestamps, we need to go back two seconds to avoid rounding issues.
     SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+    // On platforms with coarse access time, we assume 1 day precision and go
+    // back 2 days to avoid rounding issues.
+    SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay);
+#endif
 
     const int kFileSize = 12;
     starboard::nplb::ScopedRandomFile random_file(kFileSize);
@@ -56,7 +61,11 @@
       EXPECT_FALSE(info.is_directory);
       EXPECT_FALSE(info.is_symbolic_link);
       EXPECT_LE(time, info.last_modified);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+      EXPECT_LE(coarse_time, info.last_accessed);
+#else
       EXPECT_LE(time, info.last_accessed);
+#endif
       EXPECT_LE(time, info.creation_time);
     }
 
@@ -65,30 +74,6 @@
   }
 }
 
-TEST(SbFileGetInfoTest, WorksOnADirectory) {
-  char path[SB_FILE_MAX_PATH] = {0};
-  bool result =
-      SbSystemGetPath(kSbSystemPathTempDirectory, path, SB_FILE_MAX_PATH);
-  EXPECT_TRUE(result);
-
-  SbFile file = SbFileOpen(path, kSbFileOpenOnly | kSbFileRead, NULL, NULL);
-  ASSERT_TRUE(SbFileIsValid(file));
-
-  {
-    SbFileInfo info = {0};
-    bool result = SbFileGetInfo(file, &info);
-    EXPECT_LE(0, info.size);
-    EXPECT_TRUE(info.is_directory);
-    EXPECT_FALSE(info.is_symbolic_link);
-    EXPECT_LE(0, info.last_modified);
-    EXPECT_LE(0, info.last_accessed);
-    EXPECT_LE(0, info.creation_time);
-  }
-
-  result = SbFileClose(file);
-  EXPECT_TRUE(result);
-}
-
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/file_get_path_info_test.cc b/src/starboard/nplb/file_get_path_info_test.cc
index f96e205..fc024ff 100644
--- a/src/starboard/nplb/file_get_path_info_test.cc
+++ b/src/starboard/nplb/file_get_path_info_test.cc
@@ -52,6 +52,11 @@
     // Assuming platforms have at least 1 second precision on filesystem
     // timestamps, we need to go back two seconds to avoid rounding issues.
     SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+    // On platforms with coarse access time, we assume 1 day precision and go
+    // back 2 days to avoid rounding issues.
+    SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay);
+#endif
 
     const int kFileSize = 12;
     ScopedRandomFile random_file(kFileSize);
@@ -64,7 +69,11 @@
       EXPECT_FALSE(info.is_directory);
       EXPECT_FALSE(info.is_symbolic_link);
       EXPECT_LE(time, info.last_modified);
+#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME)
+      EXPECT_LE(coarse_time, info.last_accessed);
+#else
       EXPECT_LE(time, info.last_accessed);
+#endif
       EXPECT_LE(time, info.creation_time);
     }
   }
diff --git a/src/starboard/nplb/file_read_test.cc b/src/starboard/nplb/file_read_test.cc
index e294bca..a4c828d 100644
--- a/src/starboard/nplb/file_read_test.cc
+++ b/src/starboard/nplb/file_read_test.cc
@@ -22,17 +22,39 @@
 namespace nplb {
 namespace {
 
+// Sets up an empty test fixture, required for typed tests.
+template <class SbFileReadType>
+class SbFileReadTest : public testing::Test {};
+
+class SbFileReader {
+ public:
+  static int Read(SbFile file, char* data, int size) {
+    return SbFileRead(file, data, size);
+  }
+};
+
+class SbFileReaderAll {
+ public:
+  static int Read(SbFile file, char* data, int size) {
+    return SbFileReadAll(file, data, size);
+  }
+};
+
+typedef testing::Types<SbFileReader, SbFileReaderAll> SbFileReadTestTypes;
+
+TYPED_TEST_CASE(SbFileReadTest, SbFileReadTestTypes);
+
 const int kBufferLength = 16 * 1024;
 
-TEST(SbFileReadTest, InvalidFileErrors) {
+TYPED_TEST(SbFileReadTest, InvalidFileErrors) {
   char buffer[kBufferLength];
-  int result = SbFileRead(kSbFileInvalid, buffer, kBufferLength);
+  int result = TypeParam::Read(kSbFileInvalid, buffer, kBufferLength);
   EXPECT_EQ(-1, result);
 }
 
-TEST(SbFileReadTest, BasicReading) {
-  // Create a pattern file that is not an even multiple of the buffer size, but
-  // is over several times the size of the buffer.
+TYPED_TEST(SbFileReadTest, BasicReading) {
+  // Create a pattern file that is not an even multiple of the buffer size,
+  // but is over several times the size of the buffer.
   const int kFileSize = kBufferLength * 16 / 3;
   ScopedRandomFile random_file(kFileSize);
   const std::string& filename = random_file.filename();
@@ -41,8 +63,8 @@
       SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL);
   ASSERT_TRUE(SbFileIsValid(file));
 
-  // Create a bigger buffer than necessary, so we can test the memory around the
-  // portion given to SbFileRead.
+  // Create a bigger buffer than necessary, so we can test the memory around
+  // the portion given to SbFileRead.
   const int kRealBufferLength = kBufferLength * 2;
   char real_buffer[kRealBufferLength] = {0};
   const int kBufferOffset = kBufferLength / 2;
@@ -58,7 +80,7 @@
   int previous_total = 0;
   int max = 0;
   while (true) {
-    int bytes_read = SbFileRead(file, buffer, kBufferLength);
+    int bytes_read = TypeParam::Read(file, buffer, kBufferLength);
     if (bytes_read == 0) {
       break;
     }
@@ -96,7 +118,7 @@
   EXPECT_TRUE(result);
 }
 
-TEST(SbFileReadTest, ReadPastEnd) {
+TYPED_TEST(SbFileReadTest, ReadPastEnd) {
   const int kFileSize = kBufferLength;
   ScopedRandomFile random_file(kFileSize);
   const std::string& filename = random_file.filename();
@@ -105,8 +127,8 @@
       SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL);
   ASSERT_TRUE(SbFileIsValid(file));
 
-  // Create a bigger buffer than necessary, so we can test the memory around the
-  // portion given to SbFileRead.
+  // Create a bigger buffer than necessary, so we can test the memory around
+  // the portion given to SbFileRead.
   const int kRealBufferLength = kBufferLength * 2;
   char real_buffer[kRealBufferLength] = {0};
   const int kBufferOffset = kBufferLength / 2;
@@ -120,7 +142,7 @@
   // Read off the end of the file.
   int position = SbFileSeek(file, kSbFileFromEnd, 0);
   EXPECT_EQ(kFileSize, position);
-  int bytes_read = SbFileRead(file, buffer, kBufferLength);
+  int bytes_read = TypeParam::Read(file, buffer, kBufferLength);
   EXPECT_EQ(0, bytes_read);
 
   for (int i = 0; i < kRealBufferLength; ++i) {
@@ -131,7 +153,7 @@
   EXPECT_TRUE(result);
 }
 
-TEST(SbFileReadTest, ReadZeroBytes) {
+TYPED_TEST(SbFileReadTest, ReadZeroBytes) {
   const int kFileSize = kBufferLength;
   ScopedRandomFile random_file(kFileSize);
   const std::string& filename = random_file.filename();
@@ -140,8 +162,8 @@
       SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL);
   ASSERT_TRUE(SbFileIsValid(file));
 
-  // Create a bigger buffer than necessary, so we can test the memory around the
-  // portion given to SbFileRead.
+  // Create a bigger buffer than necessary, so we can test the memory around
+  // the portion given to SbFileRead.
   const int kRealBufferLength = kBufferLength * 2;
   char real_buffer[kRealBufferLength] = {0};
   const int kBufferOffset = kBufferLength / 2;
@@ -154,7 +176,7 @@
 
   // Read zero bytes.
   for (int i = 0; i < 10; ++i) {
-    int bytes_read = SbFileRead(file, buffer, 0);
+    int bytes_read = TypeParam::Read(file, buffer, 0);
     EXPECT_EQ(0, bytes_read);
   }
 
@@ -166,7 +188,7 @@
   EXPECT_TRUE(result);
 }
 
-TEST(SbFileReadTest, ReadFromMiddle) {
+TYPED_TEST(SbFileReadTest, ReadFromMiddle) {
   const int kFileSize = kBufferLength * 2;
   ScopedRandomFile random_file(kFileSize);
   const std::string& filename = random_file.filename();
@@ -175,8 +197,8 @@
       SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL);
   ASSERT_TRUE(SbFileIsValid(file));
 
-  // Create a bigger buffer than necessary, so we can test the memory around the
-  // portion given to SbFileRead.
+  // Create a bigger buffer than necessary, so we can test the memory around
+  // the portion given to SbFileRead.
   const int kRealBufferLength = kBufferLength * 2;
   char real_buffer[kRealBufferLength] = {0};
   const int kBufferOffset = kBufferLength / 2;
@@ -190,7 +212,7 @@
   // Read from the middle of the file.
   int position = SbFileSeek(file, kSbFileFromBegin, kFileSize / 4);
   EXPECT_EQ(kFileSize / 4, position);
-  int bytes_read = SbFileRead(file, buffer, kBufferLength);
+  int bytes_read = TypeParam::Read(file, buffer, kBufferLength);
   EXPECT_GE(kBufferLength, bytes_read);
   EXPECT_LT(0, bytes_read);
 
diff --git a/src/starboard/nplb/file_write_test.cc b/src/starboard/nplb/file_write_test.cc
index 3b49a8d..e503a86 100644
--- a/src/starboard/nplb/file_write_test.cc
+++ b/src/starboard/nplb/file_write_test.cc
@@ -25,28 +25,50 @@
 namespace nplb {
 namespace {
 
+// Sets up an empty test fixture, required for typed tests.
+template <class SbFileWriteType>
+class SbFileWriteTest : public testing::Test {};
+
+class SbFileWriter {
+ public:
+  static int Write(SbFile file, char* data, int size) {
+    return SbFileWrite(file, data, size);
+  }
+};
+
+class SbFileWriterAll {
+ public:
+  static int Write(SbFile file, char* data, int size) {
+    return SbFileWriteAll(file, data, size);
+  }
+};
+
+typedef testing::Types<SbFileWriter, SbFileWriterAll> SbFileWriteTestTypes;
+
+TYPED_TEST_CASE(SbFileWriteTest, SbFileWriteTestTypes);
+
 const int kBufferLength = 16 * 1024;
 
-TEST(SbFileWriteTest, InvalidFileErrors) {
+TYPED_TEST(SbFileWriteTest, InvalidFileErrors) {
   char buffer[kBufferLength] = {0};
-  int result = SbFileWrite(kSbFileInvalid, buffer, kBufferLength);
+  int result = TypeParam::Write(kSbFileInvalid, buffer, kBufferLength);
   EXPECT_EQ(-1, result);
 }
 
-TEST(SbFileWriteTest, BasicWriting) {
-  // Choose a file size that is not an even multiple of the buffer size, but is
-  // over several times the size of the buffer.
+TYPED_TEST(SbFileWriteTest, BasicWriting) {
+  // Choose a file size that is not an even multiple of the buffer size, but
+  // is over several times the size of the buffer.
   const int kFileSize = kBufferLength * 16 / 3;
   ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate);
   const std::string& filename = random_file.filename();
 
-  SbFile file =
-      SbFileOpen(filename.c_str(),
-                 kSbFileCreateAlways | kSbFileWrite | kSbFileRead, NULL, NULL);
+  SbFile file = SbFileOpen(filename.c_str(),
+                           kSbFileCreateAlways | kSbFileWrite | kSbFileRead,
+                           NULL, NULL);
   ASSERT_TRUE(SbFileIsValid(file));
 
-  // Create a bigger buffer than necessary, so we can test the memory around the
-  // portion given to SbFileRead.
+  // Create a bigger buffer than necessary, so we can test the memory around
+  // the portion given to SbFileRead.
   char buffer[kBufferLength] = {0};
 
   // Initialize to some arbitrary pattern so we can verify it later.
@@ -63,7 +85,7 @@
 
     int remaining = kFileSize - total;
     int to_write = remaining < kBufferLength ? remaining : kBufferLength;
-    int bytes_written = SbFileWrite(file, buffer, to_write);
+    int bytes_written = TypeParam::Write(file, buffer, to_write);
 
     // Check that we didn't write more than the buffer size.
     EXPECT_GE(to_write, bytes_written);
@@ -85,7 +107,7 @@
   total = 0;
   int previous_total = 0;
   while (true) {
-    int bytes_read = SbFileRead(file, buffer, kBufferLength);
+    int bytes_read = SbFileReadAll(file, buffer, kBufferLength);
     if (bytes_read == 0) {
       break;
     }
@@ -111,7 +133,7 @@
   EXPECT_TRUE(result);
 }
 
-TEST(SbFileWriteTest, WriteZeroBytes) {
+TYPED_TEST(SbFileWriteTest, WriteZeroBytes) {
   ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate);
   const std::string& filename = random_file.filename();
 
@@ -123,7 +145,7 @@
 
   // Write zero bytes.
   for (int i = 0; i < 10; ++i) {
-    int bytes_written = SbFileWrite(file, buffer, 0);
+    int bytes_written = TypeParam::Write(file, buffer, 0);
     EXPECT_EQ(0, bytes_written);
   }
 
diff --git a/src/starboard/nplb/include_all.c b/src/starboard/nplb/include_all.c
index 95f0147..e55a271 100644
--- a/src/starboard/nplb/include_all.c
+++ b/src/starboard/nplb/include_all.c
@@ -15,26 +15,31 @@
 // Includes all headers in a C context to make sure they compile as C files.
 
 #include "starboard/atomic.h"
+#include "starboard/audio_sink.h"
 #include "starboard/blitter.h"
 #include "starboard/byte_swap.h"
 #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"
 #include "starboard/event.h"
 #include "starboard/export.h"
 #include "starboard/file.h"
+#include "starboard/input.h"
 #include "starboard/key.h"
 #include "starboard/log.h"
 #include "starboard/media.h"
 #include "starboard/memory.h"
+#include "starboard/microphone.h"
 #include "starboard/mutex.h"
 #include "starboard/once.h"
 #include "starboard/player.h"
 #include "starboard/socket.h"
 #include "starboard/socket_waiter.h"
+#include "starboard/speech_synthesis.h"
 #include "starboard/storage.h"
 #include "starboard/string.h"
 #include "starboard/system.h"
diff --git a/src/starboard/nplb/include_all_too.c b/src/starboard/nplb/include_all_too.c
new file mode 100644
index 0000000..f7d6644
--- /dev/null
+++ b/src/starboard/nplb/include_all_too.c
@@ -0,0 +1,19 @@
+// 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.
+
+// A second translation unit that includes all the headers to verify there are
+// no duplicate symbols.
+
+#include "starboard/nplb/include_all.c"
+
diff --git a/src/starboard/nplb/memory_allocate_aligned_test.cc b/src/starboard/nplb/memory_allocate_aligned_test.cc
index 72cb82b..dbdc217 100644
--- a/src/starboard/nplb/memory_allocate_aligned_test.cc
+++ b/src/starboard/nplb/memory_allocate_aligned_test.cc
@@ -29,7 +29,7 @@
     void* memory = SbMemoryAllocateAligned(align, kSize);
     ASSERT_NE(static_cast<void*>(NULL), memory);
     EXPECT_TRUE(SbMemoryIsAligned(memory, align));
-    SbMemoryFreeAligned(memory);
+    SbMemoryDeallocateAligned(memory);
   }
 }
 
@@ -43,7 +43,7 @@
     if (memory) {
       EXPECT_TRUE(SbMemoryIsAligned(memory, align));
     }
-    SbMemoryFreeAligned(memory);
+    SbMemoryDeallocateAligned(memory);
   }
 }
 
@@ -60,7 +60,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFreeAligned(memory);
+  SbMemoryDeallocateAligned(memory);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_allocate_test.cc b/src/starboard/nplb/memory_allocate_test.cc
index 1da3ae6..87e252c 100644
--- a/src/starboard/nplb/memory_allocate_test.cc
+++ b/src/starboard/nplb/memory_allocate_test.cc
@@ -24,20 +24,20 @@
 TEST(SbMemoryAllocateTest, AllocatesNormally) {
   void* memory = SbMemoryAllocate(kSize);
   EXPECT_NE(static_cast<void*>(NULL), memory);
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryAllocateTest, AllocatesZero) {
   void* memory = SbMemoryAllocate(0);
   // We can't expect anything here because some implementations may return an
   // allocated zero-size memory block, and some implementations may return NULL.
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryAllocateTest, AllocatesOne) {
   void* memory = SbMemoryAllocate(1);
   EXPECT_NE(static_cast<void*>(NULL), memory);
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryAllocateTest, CanReadWriteToResult) {
@@ -52,7 +52,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_compare_test.cc b/src/starboard/nplb/memory_compare_test.cc
index f0d07c1..2057df7 100644
--- a/src/starboard/nplb/memory_compare_test.cc
+++ b/src/starboard/nplb/memory_compare_test.cc
@@ -36,8 +36,8 @@
   int result = SbMemoryCompare(memory1, memory2, kSize);
   EXPECT_EQ(0, result);
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 TEST(SbMemoryCompareTest, SunnyDayLessAndMore) {
@@ -62,8 +62,8 @@
   result = SbMemoryCompare(memory2, memory1, kSize);
   EXPECT_GT(0, result);
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_copy_test.cc b/src/starboard/nplb/memory_copy_test.cc
index 1142615..f8d3342 100644
--- a/src/starboard/nplb/memory_copy_test.cc
+++ b/src/starboard/nplb/memory_copy_test.cc
@@ -41,8 +41,8 @@
     EXPECT_EQ(data2[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 TEST(SbMemoryCopyTest, CopiesZeroData) {
@@ -65,8 +65,8 @@
     EXPECT_EQ(data2[i], static_cast<char>(0));
   }
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_deallocate_aligned_test.cc b/src/starboard/nplb/memory_deallocate_aligned_test.cc
new file mode 100644
index 0000000..39cea85
--- /dev/null
+++ b/src/starboard/nplb/memory_deallocate_aligned_test.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "starboard/memory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+const size_t kSize = 1024 * 128;
+
+TEST(SbMemoryDeallocateAlignedTest, DeallocatesAligned) {
+  const size_t kMaxAlign = 4096 + 1;
+  for (size_t align = 2; align < kMaxAlign; align <<= 1) {
+    void* memory = SbMemoryAllocateAligned(align, kSize);
+    SbMemoryDeallocateAligned(memory);
+  }
+}
+
+TEST(SbMemoryDeallocateAlignedTest, FreesNull) {
+  SbMemoryDeallocateAligned(NULL);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/memory_deallocate_test.cc b/src/starboard/nplb/memory_deallocate_test.cc
new file mode 100644
index 0000000..9bd8739
--- /dev/null
+++ b/src/starboard/nplb/memory_deallocate_test.cc
@@ -0,0 +1,36 @@
+// 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.
+
+#include "starboard/memory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+const size_t kSize = 1024 * 128;
+
+TEST(SbMemoryDeallocateTest, FreesNormally) {
+  void* memory = SbMemoryAllocate(kSize);
+  EXPECT_NE(static_cast<void*>(NULL), memory);
+  SbMemoryDeallocate(memory);
+}
+
+TEST(SbMemoryDeallocateTest, FreesNull) {
+  SbMemoryDeallocate(NULL);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/memory_free_aligned_test.cc b/src/starboard/nplb/memory_free_aligned_test.cc
deleted file mode 100644
index 5fbc8da..0000000
--- a/src/starboard/nplb/memory_free_aligned_test.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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.
-
-#include "starboard/memory.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace starboard {
-namespace nplb {
-namespace {
-
-const size_t kSize = 1024 * 128;
-
-TEST(SbMemoryFreeAlignedTest, FreesAligned) {
-  const size_t kMaxAlign = 4096 + 1;
-  for (size_t align = 2; align < kMaxAlign; align <<= 1) {
-    void* memory = SbMemoryAllocateAligned(align, kSize);
-    SbMemoryFreeAligned(memory);
-  }
-}
-
-TEST(SbMemoryFreeAlignedTest, FreesNull) {
-  SbMemoryFreeAligned(NULL);
-}
-
-}  // namespace
-}  // namespace nplb
-}  // namespace starboard
diff --git a/src/starboard/nplb/memory_free_test.cc b/src/starboard/nplb/memory_free_test.cc
deleted file mode 100644
index 3e25330..0000000
--- a/src/starboard/nplb/memory_free_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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.
-
-#include "starboard/memory.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace starboard {
-namespace nplb {
-namespace {
-
-const size_t kSize = 1024 * 128;
-
-TEST(SbMemoryFreeTest, FreesNormally) {
-  void* memory = SbMemoryAllocate(kSize);
-  EXPECT_NE(static_cast<void*>(NULL), memory);
-  SbMemoryFree(memory);
-}
-
-TEST(SbMemoryFreeTest, FreesNull) {
-  SbMemoryFree(NULL);
-}
-
-}  // namespace
-}  // namespace nplb
-}  // namespace starboard
diff --git a/src/starboard/nplb/memory_move_test.cc b/src/starboard/nplb/memory_move_test.cc
index 590a51e..c4b0e39 100644
--- a/src/starboard/nplb/memory_move_test.cc
+++ b/src/starboard/nplb/memory_move_test.cc
@@ -41,8 +41,8 @@
     EXPECT_EQ(data2[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 TEST(SbMemoryMoveTest, MovesZeroData) {
@@ -65,8 +65,8 @@
     EXPECT_EQ(data2[i], static_cast<char>(i + 1));
   }
 
-  SbMemoryFree(memory1);
-  SbMemoryFree(memory2);
+  SbMemoryDeallocate(memory1);
+  SbMemoryDeallocate(memory2);
 }
 
 TEST(SbMemoryMoveTest, MovesOverlappingDataForward) {
@@ -84,7 +84,7 @@
     EXPECT_EQ(data[i + 3], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryMoveTest, MovesOverlappingDataBackwards) {
@@ -102,7 +102,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i + 3));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_reallocate_test.cc b/src/starboard/nplb/memory_reallocate_test.cc
index 72d0036..7ca252c 100644
--- a/src/starboard/nplb/memory_reallocate_test.cc
+++ b/src/starboard/nplb/memory_reallocate_test.cc
@@ -24,20 +24,20 @@
 TEST(SbMemoryReallocateTest, AllocatesNormally) {
   void* memory = SbMemoryReallocate(NULL, kSize);
   EXPECT_NE(static_cast<void*>(NULL), memory);
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, AllocatesZero) {
   void* memory = SbMemoryReallocate(NULL, 0);
   // We can't expect anything here because some implementations may return an
   // allocated zero-size memory block, and some implementations may return NULL.
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, AllocatesOne) {
   void* memory = SbMemoryReallocate(NULL, 1);
   EXPECT_NE(static_cast<void*>(NULL), memory);
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, CanReadWriteToResult) {
@@ -52,7 +52,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, ReallocatesSmaller) {
@@ -74,7 +74,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, ReallocatesBigger) {
@@ -104,7 +104,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, ReallocatestoZero) {
@@ -112,7 +112,7 @@
   ASSERT_NE(static_cast<void*>(NULL), memory);
   memory = SbMemoryReallocate(memory, 0);
   // See allocates zero above.
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemoryReallocateTest, ReallocatestoSameSize) {
@@ -134,7 +134,7 @@
     EXPECT_EQ(data[i], static_cast<char>(i));
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/memory_reporter_test.cc b/src/starboard/nplb/memory_reporter_test.cc
new file mode 100644
index 0000000..f49e339
--- /dev/null
+++ b/src/starboard/nplb/memory_reporter_test.cc
@@ -0,0 +1,496 @@
+// 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/memory.h"
+#include "starboard/memory_reporter.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+// This thread local boolean is used to filter allocations and
+// 1) Prevent allocations from other threads.
+// 2) Selectively disable allocations so that unintended allocs from
+//    gTest (ASSERT_XXX) don't cause the current test to fail.
+void InitMemoryTrackingState_ThreadLocal();
+bool GetMemoryTrackingEnabled_ThreadLocal();
+void SetMemoryTrackingEnabled_ThreadLocal(bool value);
+
+// Scoped object that temporary turns off MemoryTracking. This is needed
+// to avoid allocations from gTest being inserted into the memory tracker.
+struct NoMemTracking {
+  bool prev_val;
+  NoMemTracking() {
+    prev_val = GetMemoryTrackingEnabled_ThreadLocal();
+    SetMemoryTrackingEnabled_ThreadLocal(false);
+  }
+  ~NoMemTracking() {
+    SetMemoryTrackingEnabled_ThreadLocal(prev_val);
+  }
+};
+
+// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when
+// for unit testing allocations. These overrides disable memory
+// tracking for the duration of the EXPECT and ASSERT operations.
+#define EXPECT_EQ_NO_TRACKING(A, B)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  EXPECT_EQ(A, B);                                 \
+}
+
+#define EXPECT_NE_NO_TRACKING(A, B)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  EXPECT_NE(A, B);                                 \
+}
+
+#define EXPECT_TRUE_NO_TRACKING(A)                 \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  EXPECT_TRUE(A);                                  \
+}
+
+#define EXPECT_FALSE_NO_TRACKING(A)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  EXPECT_FALSE(A);                                 \
+}
+
+#define ASSERT_EQ_NO_TRACKING(A, B)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  ASSERT_EQ(A, B);                                 \
+}
+
+#define ASSERT_NE_NO_TRACKING(A, B)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  ASSERT_NE(A, B);                                 \
+}
+
+#define ASSERT_TRUE_NO_TRACKING(A)                 \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  ASSERT_TRUE(A);                                  \
+}
+
+#define ASSERT_FALSE_NO_TRACKING(A)                \
+{ NoMemTracking no_memory_tracking_in_this_scope;  \
+  ASSERT_FALSE(A);                                 \
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// A memory reporter that is used to watch allocations from the system.
+class TestMemReporter {
+ public:
+  TestMemReporter() { Construct(); }
+
+  SbMemoryReporter* memory_reporter() {
+    return &memory_reporter_;
+  }
+
+  // Removes this object from listening to allocations.
+  void RemoveGlobalHooks() {
+    SbMemorySetReporter(NULL);
+    // Sleep to allow other threads time to pass through.
+    SbThreadSleep(250 * kSbTimeMillisecond);
+  }
+
+  // Total number allocations outstanding.
+  int number_allocs() const { return number_allocs_; }
+
+  void Clear() {
+    starboard::ScopedLock lock(mutex_);
+    number_allocs_ = 0;
+    last_allocation_ = NULL;
+    last_deallocation_ = NULL;
+    last_mem_map_ = NULL;
+    last_mem_unmap_ = NULL;
+  }
+
+  const void* last_allocation() const {
+    return last_allocation_;
+  }
+  const void* last_deallocation() const {
+    return last_deallocation_;
+  }
+  const void* last_mem_map() const {
+    return last_mem_map_;
+  }
+  const void* last_mem_unmap() const {
+    return last_mem_unmap_;
+  }
+
+ private:
+  // Boilerplate to delegate static function callbacks to the class instance.
+  static void OnMalloc(void* context, const void* memory, size_t size) {
+    TestMemReporter* t = static_cast<TestMemReporter*>(context);
+    t->ReportAlloc(memory, size);
+  }
+
+  static void OnMapMem(void* context, const void* memory, size_t size) {
+    TestMemReporter* t = static_cast<TestMemReporter*>(context);
+    t->ReportMapMem(memory, size);
+  }
+
+  static void OnDealloc(void* context, const void* memory) {
+    TestMemReporter* t = static_cast<TestMemReporter*>(context);
+    t->ReportDealloc(memory);
+  }
+
+  static void SbUnMapMem(void* context, const void* memory, size_t size) {
+    TestMemReporter* t = static_cast<TestMemReporter*>(context);
+    t->ReportUnMapMemory(memory, size);
+  }
+
+  static SbMemoryReporter CreateSbMemoryReporter(TestMemReporter* context) {
+    SbMemoryReporter cb = { OnMalloc, OnDealloc,
+                            OnMapMem, SbUnMapMem,
+                            context };
+    return cb;
+  }
+
+  void ReportAlloc(const void* memory, size_t /*size*/) {
+    if (!GetMemoryTrackingEnabled_ThreadLocal()) {
+      return;
+    }
+    starboard::ScopedLock lock(mutex_);
+    last_allocation_ = memory;
+    number_allocs_++;
+  }
+
+  void ReportMapMem(const void* memory, size_t size) {
+    if (!GetMemoryTrackingEnabled_ThreadLocal()) {
+      return;
+    }
+    starboard::ScopedLock lock(mutex_);
+    last_mem_map_ = memory;
+    number_allocs_++;
+  }
+
+  void ReportDealloc(const void* memory) {
+    if (!GetMemoryTrackingEnabled_ThreadLocal()) {
+      return;
+    }
+    starboard::ScopedLock lock(mutex_);
+    last_deallocation_ = memory;
+    number_allocs_--;
+  }
+
+  void ReportUnMapMemory(const void* memory, size_t size) {
+    if (!GetMemoryTrackingEnabled_ThreadLocal()) {
+      return;
+    }
+    starboard::ScopedLock lock(mutex_);
+    last_mem_unmap_ = memory;
+    number_allocs_--;
+  }
+
+  void Construct() {
+    Clear();
+    memory_reporter_ = CreateSbMemoryReporter(this);
+  }
+
+  SbMemoryReporter memory_reporter_;
+  starboard::Mutex mutex_;
+  const void* last_allocation_;
+  const void* last_deallocation_;
+  const void* last_mem_map_;
+  const void* last_mem_unmap_;
+  int number_allocs_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Needed by all tests that require a memory tracker to be installed. On the
+// first test the test memory tracker is installed and then after the last test
+// the memory tracker is removed.
+class MemoryReportingTest : public ::testing::Test {
+ public:
+  TestMemReporter* mem_reporter() { return s_test_alloc_tracker_; }
+  bool MemoryReportingEnabled() const {
+    return s_memory_reporter_error_enabled_;
+  }
+
+ protected:
+  // Global setup - runs before the first test in the series.
+  static void SetUpTestCase() {
+    InitMemoryTrackingState_ThreadLocal();
+    // global init test code should run here.
+    if (s_test_alloc_tracker_ == NULL) {
+      s_test_alloc_tracker_ = new TestMemReporter;
+    }
+    s_memory_reporter_error_enabled_ =
+        SbMemorySetReporter(s_test_alloc_tracker_->memory_reporter());
+  }
+
+  // Global Teardown after last test has run it's course.
+  static void TearDownTestCase() {
+    s_test_alloc_tracker_->RemoveGlobalHooks();
+  }
+
+  // Per test setup.
+  virtual void SetUp() {
+    mem_reporter()->Clear();
+    // Allows current thread to capture memory allocations. If this wasn't
+    // done then background threads spawned by a framework could notify this
+    // class about allocations and fail the test.
+    SetMemoryTrackingEnabled_ThreadLocal(true);
+  }
+
+  // Per test teardown.
+  virtual void TearDown() {
+    SetMemoryTrackingEnabled_ThreadLocal(false);
+    if (mem_reporter()->number_allocs() != 0) {
+      ADD_FAILURE_AT(__FILE__, __LINE__) << "Memory Leak detected.";
+    }
+    mem_reporter()->Clear();
+  }
+  static TestMemReporter* s_test_alloc_tracker_;
+  static bool s_memory_reporter_error_enabled_;
+};
+TestMemReporter* MemoryReportingTest::s_test_alloc_tracker_ = NULL;
+bool MemoryReportingTest::s_memory_reporter_error_enabled_ = false;
+
+///////////////////////////////////////////////////////////////////////////////
+// TESTS.
+// There are two sets of tests: POSITIVE and NEGATIVE.
+//  The positive tests are active when STARBOARD_ALLOWS_MEMORY_TRACKING is
+//  defined and test that memory tracking is enabled.
+//  NEGATIVE tests ensure that tracking is disabled when
+//  STARBOARD_ALLOWS_MEMORY_TRACKING is not defined.
+// When adding new tests:
+//  POSITIVE tests are named normally.
+//  NEGATIVE tets are named with "No" prefixed to the beginning.
+//  Example:
+//   TEST_F(MemoryReportingTest, CapturesAllocDealloc) <--- POSITIVE test.
+//   TEST_F(MemoryReportingTest, NoCapturesAllocDealloc) <- NEGATIVE test.
+//  All positive & negative tests are grouped together.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef STARBOARD_ALLOWS_MEMORY_TRACKING
+// These are POSITIVE tests, which test the expectation that when the define
+// STARBOARD_ALLOWS_MEMORY_TRACKING is active that the memory tracker will
+// receive memory allocations notifications.
+//
+// Tests the assumption that the SbMemoryAllocate and SbMemoryDeallocate
+// will report memory allocations.
+TEST_F(MemoryReportingTest, CapturesAllocDealloc) {
+  if (!MemoryReportingEnabled()) {
+    GTEST_SUCCEED() << "Memory reporting is disabled.";
+    return;
+  }
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  void* memory = SbMemoryAllocate(4);
+  EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs());
+
+  EXPECT_EQ_NO_TRACKING(memory, mem_reporter()->last_allocation());
+
+  SbMemoryDeallocate(memory);
+  EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0);
+
+  // Should equal the last allocation.
+  EXPECT_EQ_NO_TRACKING(memory, mem_reporter()->last_deallocation());
+}
+
+// Tests the assumption that SbMemoryReallocate() will report a
+// deallocation and an allocation to the memory tracker.
+TEST_F(MemoryReportingTest, CapturesRealloc) {
+  if (!MemoryReportingEnabled()) {
+    GTEST_SUCCEED() << "Memory reporting is disabled.";
+    return;
+  }
+  void* prev_memory = SbMemoryAllocate(4);
+  void* new_memory = SbMemoryReallocate(prev_memory, 8);
+
+  EXPECT_EQ_NO_TRACKING(new_memory, mem_reporter()->last_allocation());
+  EXPECT_EQ_NO_TRACKING(prev_memory, mem_reporter()->last_deallocation());
+
+  EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs());
+
+  SbMemoryDeallocate(new_memory);
+  EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0);
+}
+
+// Tests the assumption that the SbMemoryMap and SbMemoryUnmap
+// will report memory allocations.
+TEST_F(MemoryReportingTest, CapturesMemMapUnmap) {
+  if (!MemoryReportingEnabled()) {
+    GTEST_SUCCEED() << "Memory reporting is disabled.";
+    return;
+  }
+  const int64_t kMemSize = 4096;
+  const int kFlags = 0;
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  void* mem_chunk = SbMemoryMap(kMemSize, kFlags, "TestMemMap");
+  EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs());
+
+  // Now unmap the memory and confirm that this memory was reported as free.
+  EXPECT_EQ_NO_TRACKING(mem_chunk, mem_reporter()->last_mem_map());
+  SbMemoryUnmap(mem_chunk, kMemSize);
+  EXPECT_EQ_NO_TRACKING(mem_chunk, mem_reporter()->last_mem_unmap());
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+}
+
+// Tests the assumption that the operator/delete will report
+// memory allocations.
+TEST_F(MemoryReportingTest, CapturesOperatorNewDelete) {
+  if (!MemoryReportingEnabled()) {
+    GTEST_SUCCEED() << "Memory reporting is disabled.";
+    return;
+  }
+  EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 0);
+  int* my_int = new int();
+  EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 1);
+
+  bool is_last_allocation =
+      my_int == mem_reporter()->last_allocation();
+
+  EXPECT_TRUE_NO_TRACKING(is_last_allocation);
+
+  delete my_int;
+  EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 0);
+
+  // Expect last deallocation to be the expected pointer.
+  EXPECT_EQ_NO_TRACKING(my_int, mem_reporter()->last_deallocation());
+}
+
+#else  // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+// These are NEGATIVE tests, which test the expectation that when the
+// STARBOARD_ALLOWS_MEMORY_TRACKING is undefined that the memory tracker does
+// not receive memory allocations notifications.
+
+TEST_F(MemoryReportingTest, NoCapturesAllocDealloc) {
+  EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled());
+
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  void* memory = SbMemoryAllocate(4);
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation());
+
+  SbMemoryDeallocate(memory);
+  EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0);
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation());
+}
+
+TEST_F(MemoryReportingTest, NoCapturesRealloc) {
+  EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled());
+  void* prev_memory = SbMemoryAllocate(4);
+  void* new_memory = SbMemoryReallocate(prev_memory, 8);
+
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation());
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation());
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+
+  SbMemoryDeallocate(new_memory);
+  EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0);
+}
+
+TEST_F(MemoryReportingTest, NoCapturesMemMapUnmap) {
+  EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled());
+  const int64_t kMemSize = 4096;
+  const int kFlags = 0;
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  void* mem_chunk = SbMemoryMap(kMemSize, kFlags, "TestMemMap");
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+
+  // Now unmap the memory and confirm that this memory was reported as free.
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_mem_map());
+  SbMemoryUnmap(mem_chunk, kMemSize);
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_mem_unmap());
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+}
+
+TEST_F(MemoryReportingTest, NoCapturesOperatorNewDelete) {
+  EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled());
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  int* my_int = new int();
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation());
+
+  delete my_int;
+  EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs());
+  EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation());
+}
+
+#endif  // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+
+/////////////////////////////// Implementation ////////////////////////////////
+
+// Simple ThreadLocalBool class which allows a default value to be
+// true or false.
+class ThreadLocalBool {
+ public:
+  ThreadLocalBool() {
+    // NULL is the destructor.
+    slot_ = SbThreadCreateLocalKey(NULL);
+  }
+
+  ~ThreadLocalBool() {
+    SbThreadDestroyLocalKey(slot_);
+  }
+
+  void SetEnabled(bool value) {
+    SetEnabledThreadLocal(value);
+  }
+
+  bool Enabled() const {
+    return GetEnabledThreadLocal();
+  }
+
+ private:
+  void SetEnabledThreadLocal(bool value) {
+    void* bool_as_pointer;
+    if (value) {
+      bool_as_pointer = reinterpret_cast<void*>(0x1);
+    } else {
+      bool_as_pointer = NULL;
+    }
+    SbThreadSetLocalValue(slot_, bool_as_pointer);
+  }
+
+  bool GetEnabledThreadLocal() const {
+    void* ptr = SbThreadGetLocalValue(slot_);
+    return ptr != NULL;
+  }
+
+  mutable SbThreadLocalKey slot_;
+  bool default_value_;
+};
+
+void InitMemoryTrackingState_ThreadLocal() {
+  GetMemoryTrackingEnabled_ThreadLocal();
+}
+
+static ThreadLocalBool* GetMemoryTrackingState() {
+  static ThreadLocalBool* thread_local_bool = new ThreadLocalBool();
+  return thread_local_bool;
+}
+
+bool GetMemoryTrackingEnabled_ThreadLocal() {
+  return GetMemoryTrackingState()->Enabled();
+}
+
+void SetMemoryTrackingEnabled_ThreadLocal(bool value) {
+  GetMemoryTrackingState()->SetEnabled(value);
+}
+
+// No use for these macros anymore.
+#undef EXPECT_EQ_NO_TRACKING
+#undef EXPECT_TRUE_NO_TRACKING
+#undef EXPECT_FALSE_NO_TRACKING
+#undef ASSERT_EQ_NO_TRACKING
+#undef ASSERT_TRUE_NO_TRACKING
+#undef ASSERT_FALSE_NO_TRACKING
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/memory_set_test.cc b/src/starboard/nplb/memory_set_test.cc
index 0874104..a086170 100644
--- a/src/starboard/nplb/memory_set_test.cc
+++ b/src/starboard/nplb/memory_set_test.cc
@@ -34,7 +34,7 @@
     ASSERT_EQ('\xCD', data[i]);
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemorySetTest, SetsZeroData) {
@@ -50,7 +50,7 @@
     ASSERT_EQ(static_cast<char>(i), data[i]);
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 TEST(SbMemorySetTest, IgnoresExtraData) {
@@ -66,7 +66,7 @@
     ASSERT_EQ('\xCD', data[i]);
   }
 
-  SbMemoryFree(memory);
+  SbMemoryDeallocate(memory);
 }
 
 }  // namespace
diff --git a/src/starboard/nplb/microphone_close_test.cc b/src/starboard/nplb/microphone_close_test.cc
new file mode 100644
index 0000000..662eb0a
--- /dev/null
+++ b/src/starboard/nplb/microphone_close_test.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 "starboard/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneCloseTest, SunnyDayCloseAreCalledMultipleTimes) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_info_array =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_info_array, 0);
+
+  if (available_info_array != 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));
+    bool success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneClose(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneClose(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneClose(microphone);
+    EXPECT_TRUE(success);
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCloseTest, SunnyDayOpenIsNotCalled) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_info_array =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_info_array, 0);
+
+  if (available_info_array != 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));
+
+    bool success = SbMicrophoneClose(microphone);
+    EXPECT_TRUE(success);
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCloseTest, RainyDayCloseWithInvalidMicrophone) {
+  bool success = SbMicrophoneClose(kSbMicrophoneInvalid);
+  EXPECT_FALSE(success);
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_create_test.cc b/src/starboard/nplb/microphone_create_test.cc
new file mode 100644
index 0000000..f89be07
--- /dev/null
+++ b/src/starboard/nplb/microphone_create_test.cc
@@ -0,0 +1,182 @@
+// 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/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneCreateTest, SunnyDayOnlyOneMicrophone) {
+  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);
+    EXPECT_TRUE(SbMicrophoneIsValid(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayOneMicrophoneIsCreatedMultipleTimes) {
+  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);
+    EXPECT_TRUE(SbMicrophoneIsValid(microphone));
+
+    SbMicrophone microphone_1 = SbMicrophoneCreate(
+        info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone_1));
+
+    SbMicrophone microphone_2 = SbMicrophoneCreate(
+        info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone_2));
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidMicrophoneId) {
+  SbMicrophone microphone = SbMicrophoneCreate(
+      kSbMicrophoneIdInvalid, kNormallyUsedSampleRateInHz, kBufferSize);
+  EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_0) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    ASSERT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, 0));
+    SbMicrophone microphone =
+        SbMicrophoneCreate(info_array[0].id, 0, kBufferSize);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_Negative) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    ASSERT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, -8000));
+    SbMicrophone microphone =
+        SbMicrophoneCreate(info_array[0].id, -8000, kBufferSize);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_MoreThanMax) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    ASSERT_FALSE(SbMicrophoneIsSampleRateSupported(
+        info_array[0].id, info_array[0].max_sample_rate_hz + 1));
+    SbMicrophone microphone = SbMicrophoneCreate(
+        info_array[0].id, info_array[0].max_sample_rate_hz + 1, kBufferSize);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_0) {
+  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, 0);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCreateTest, SunnyDayBufferSize_1) {
+  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, 1);
+    EXPECT_TRUE(SbMicrophoneIsValid(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_Negative) {
+  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, -1024);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_2G) {
+  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,
+                           2 * 1024 * 1024 * 1024L);
+    EXPECT_FALSE(SbMicrophoneIsValid(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_destroy_test.cc b/src/starboard/nplb/microphone_destroy_test.cc
new file mode 100644
index 0000000..707a3f1
--- /dev/null
+++ b/src/starboard/nplb/microphone_destroy_test.cc
@@ -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.
+
+#include "starboard/microphone.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneDestroyTest, DestroyInvalidMicrophone) {
+  SbMicrophoneDestroy(kSbMicrophoneInvalid);
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_get_available_test.cc b/src/starboard/nplb/microphone_get_available_test.cc
new file mode 100644
index 0000000..232995b
--- /dev/null
+++ b/src/starboard/nplb/microphone_get_available_test.cc
@@ -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.
+
+#include "starboard/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneGetAvailableTest, SunnyDay) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+}
+
+TEST(SbMicrophoneGetAvailableTest, RainyDay0NumberOfMicrophone) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
+    int available_microphones = SbMicrophoneGetAvailable(info_array, 0);
+    EXPECT_GT(available_microphones, 0);
+  }
+}
+
+TEST(SbMicrophoneGetAvailableTest, RainyDayNegativeNumberOfMicrophone) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
+    int available_microphones = SbMicrophoneGetAvailable(info_array, -10);
+    EXPECT_GT(available_microphones, 0);
+  }
+}
+
+TEST(SbMicrophoneGetAvailableTest, RainyDayNULLInfoArray) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) {
+    int available_microphones =
+        SbMicrophoneGetAvailable(NULL, kMaxNumberOfMicrophone);
+    EXPECT_GT(available_microphones, 0);
+  }
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_helpers.h b/src/starboard/nplb/microphone_helpers.h
new file mode 100644
index 0000000..06d4c37
--- /dev/null
+++ b/src/starboard/nplb/microphone_helpers.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 STARBOARD_NPLB_MICROPHONE_HELPERS_H_
+#define STARBOARD_NPLB_MICROPHONE_HELPERS_H_
+
+#include "starboard/microphone.h"
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+namespace starboard {
+namespace nplb {
+const int kMaxNumberOfMicrophone = 20;
+const int kBufferSize = 32 * 1024;
+const int kNormallyUsedSampleRateInHz = 16000;
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+#endif  // STARBOARD_NPLB_MICROPHONE_HELPERS_H_
diff --git a/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
new file mode 100644
index 0000000..1016b8d
--- /dev/null
+++ b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
@@ -0,0 +1,55 @@
+// 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/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneIsSampleRateSupportedTest, SunnyDay) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    EXPECT_TRUE(SbMicrophoneIsSampleRateSupported(
+        info_array[0].id, info_array[0].max_sample_rate_hz));
+  }
+}
+
+TEST(SbMicrophoneIsSampleRateSupportedTest, RainyDayInvalidSampleRate) {
+  SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
+  int available_microphones =
+      SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone);
+  EXPECT_GE(available_microphones, 0);
+
+  if (available_microphones != 0) {
+    EXPECT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, 0));
+  }
+}
+
+TEST(SbMicrophoneIsSampleRateSupportedTest, RainyDayInvalidMicrophoneId) {
+  EXPECT_FALSE(SbMicrophoneIsSampleRateSupported(kSbMicrophoneIdInvalid,
+                                                 kNormallyUsedSampleRateInHz));
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_open_test.cc b/src/starboard/nplb/microphone_open_test.cc
new file mode 100644
index 0000000..2853457
--- /dev/null
+++ b/src/starboard/nplb/microphone_open_test.cc
@@ -0,0 +1,98 @@
+// 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/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneOpenTest, SunnyDay) {
+  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));
+    bool success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+    success = SbMicrophoneClose(microphone);
+    EXPECT_TRUE(success);
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneOpenTest, SunnyDayNoClose) {
+  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));
+    bool success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneOpenTest, SunnyDayMultipleOpenCalls) {
+  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));
+    bool success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+
+    success = SbMicrophoneOpen(microphone);
+    EXPECT_TRUE(success);
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneOpenTest, RainyDayOpenWithInvalidMicrophone) {
+  bool success = SbMicrophoneOpen(kSbMicrophoneInvalid);
+  EXPECT_FALSE(success);
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/microphone_read_test.cc b/src/starboard/nplb/microphone_read_test.cc
new file mode 100644
index 0000000..7df0260
--- /dev/null
+++ b/src/starboard/nplb/microphone_read_test.cc
@@ -0,0 +1,241 @@
+// 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/microphone.h"
+#include "starboard/nplb/microphone_helpers.h"
+#include "starboard/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+
+#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+TEST(SbMicrophoneReadTest, SunnyDay) {
+  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, 0);
+    int read_bytes =
+        SbMicrophoneRead(microphone, &audio_data[0], audio_data.size());
+    EXPECT_GE(read_bytes, 0);
+
+    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);
+  }
+}
+
+TEST(SbMicrophoneReadTest, SunnyDayOpenSleepCloseAndOpenRead) {
+  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));
+
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
+
+    SbThreadSleep(50 * kSbTimeMillisecond);
+
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
+
+    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[0], audio_data.size());
+    EXPECT_GE(read_bytes, 0);
+    EXPECT_LT(read_bytes, audio_data.size());
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayAudioBufferIsNULL) {
+  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 read_bytes = SbMicrophoneRead(microphone, NULL, 0);
+    EXPECT_EQ(read_bytes, 0);
+
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayAudioBufferSizeIsSmallerThanRequestedSize) {
+  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 read_bytes = SbMicrophoneRead(microphone, NULL, 1024);
+    EXPECT_LE(read_bytes, 0);
+
+    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);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayOpenIsNotCalled) {
+  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));
+
+    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[0], audio_data.size());
+    // An error should have occurred because open was not called.
+    EXPECT_LT(read_bytes, 0);
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayOpenCloseAndRead) {
+  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));
+
+    EXPECT_TRUE(SbMicrophoneOpen(microphone));
+    EXPECT_TRUE(SbMicrophoneClose(microphone));
+
+    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[0], audio_data.size());
+    // An error should have occurred because the microphone was closed.
+    EXPECT_LT(read_bytes, 0);
+
+    SbMicrophoneDestroy(microphone);
+  }
+}
+
+TEST(SbMicrophoneReadTest, RainyDayMicrophoneIsInvalid) {
+  std::vector<uint8_t> audio_data(1024, 0);
+  int read_bytes =
+      SbMicrophoneRead(kSbMicrophoneInvalid, &audio_data[0], audio_data.size());
+  EXPECT_LT(read_bytes, 0);
+}
+
+#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 5b66821..dc39b90 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',
@@ -100,26 +103,34 @@
         'file_truncate_test.cc',
         'file_write_test.cc',
         'include_all.c',
+        'include_all_too.c',
         'log_flush_test.cc',
         'log_format_test.cc',
         'log_is_tty_test.cc',
         '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',
         'memory_compare_test.cc',
         'memory_copy_test.cc',
         'memory_find_byte_test.cc',
-        'memory_free_aligned_test.cc',
-        'memory_free_test.cc',
+        'memory_deallocate_aligned_test.cc',
+        'memory_deallocate_test.cc',
         'memory_get_stack_bounds_test.cc',
         'memory_map_test.cc',
         'memory_move_test.cc',
         'memory_reallocate_test.cc',
+        'memory_reporter_test.cc',
         'memory_set_test.cc',
+        'microphone_close_test.cc',
+        'microphone_create_test.cc',
+        'microphone_destroy_test.cc',
+        'microphone_get_available_test.cc',
+        'microphone_is_sample_rate_supported_test.cc',
+        'microphone_open_test.cc',
+        'microphone_read_test.cc',
         'mutex_acquire_test.cc',
         'mutex_acquire_try_test.cc',
         'mutex_create_test.cc',
@@ -152,6 +163,7 @@
         'socket_waiter_wait_test.cc',
         'socket_waiter_wait_timed_test.cc',
         'socket_waiter_wake_up_test.cc',
+        'speech_synthesis_basic_test.cc',
         'storage_close_record_test.cc',
         'storage_delete_record_test.cc',
         'storage_get_record_size_test.cc',
diff --git a/src/starboard/nplb/once_test.cc b/src/starboard/nplb/once_test.cc
index b40817a..a504fc2 100644
--- a/src/starboard/nplb/once_test.cc
+++ b/src/starboard/nplb/once_test.cc
@@ -22,6 +22,7 @@
 

 namespace starboard {

 namespace nplb {

+namespace {

 

 int s_global_value;

 

@@ -149,5 +150,14 @@
   EXPECT_EQ(0, s_global_value);

 }

 

-}  // namespace nplb

-}  // namespace starboard

+SB_ONCE_INITIALIZE_FUNCTION(int, GetIntSingleton);

+TEST(SbOnceTest, InitializeOnceMacroFunction) {

+  int* int_singelton = GetIntSingleton();

+  ASSERT_TRUE(int_singelton);

+  EXPECT_EQ(0, *int_singelton)

+      << "Singleton Macro does not default initialize.";

+}

+

+}  // namespace.

+}  // namespace nplb.

+}  // namespace starboard.

diff --git a/src/starboard/nplb/player_create_test.cc b/src/starboard/nplb/player_create_test.cc
index d81c51c..dffa92c 100644
--- a/src/starboard/nplb/player_create_test.cc
+++ b/src/starboard/nplb/player_create_test.cc
@@ -44,7 +44,12 @@
   SbPlayer player =

       SbPlayerCreate(window, kSbMediaVideoCodecH264, kSbMediaAudioCodecAac,

                      SB_PLAYER_NO_DURATION, kSbDrmSystemInvalid, &audio_header,

-                     NULL, NULL, NULL, NULL);

+                     NULL, NULL, NULL, NULL

+#if SB_VERSION(3)

+                     ,

+                     NULL

+#endif

+                     );  // NOLINT

   EXPECT_TRUE(SbPlayerIsValid(player));

   SbPlayerDestroy(player);

   SbWindowDestroy(window);

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_send_to_test.cc b/src/starboard/nplb/socket_send_to_test.cc
index b7b2f06..9db1d36 100644
--- a/src/starboard/nplb/socket_send_to_test.cc
+++ b/src/starboard/nplb/socket_send_to_test.cc
@@ -15,19 +15,78 @@
 // SendTo is largely tested with ReceiveFrom, so look there for more invovled
 // tests.
 
+#include "starboard/memory.h"
+#include "starboard/nplb/socket_helpers.h"
 #include "starboard/socket.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace starboard {
 namespace nplb {
 namespace {
 
+// Thread entry point to continuously write to a socket that is expected to
+// be closed on another thread.
+void* SendToServerSocketEntryPoint(void* trio_as_void_ptr) {
+  ConnectedTrio* trio = static_cast<ConnectedTrio*>(trio_as_void_ptr);
+  // The contents of this buffer are inconsequential.
+  const size_t kBufSize = 1024;
+  char* send_buf = new char[kBufSize];
+  SbMemorySet(send_buf, 0, kBufSize);
+
+  // Continue sending to the socket until it fails to send. It's expected that
+  // SbSocketSendTo will fail when the server socket closes, but the application
+  // should
+  // not terminate.
+  SbTime start = SbTimeGetNow();
+  SbTime now = start;
+  SbTime kTimeout = kSbTimeSecond;
+  int result = 0;
+  while (result >= 0 && (now - start < kTimeout)) {
+    result = SbSocketSendTo(trio->server_socket, send_buf, kBufSize, NULL);
+    now = SbTimeGetNow();
+  }
+
+  delete[] send_buf;
+  return NULL;
+}
+
 TEST(SbSocketSendToTest, RainyDayInvalidSocket) {
   char buf[16];
   int result = SbSocketSendTo(NULL, buf, sizeof(buf), NULL);
   EXPECT_EQ(-1, result);
 }
 
+TEST(SbSocketSendToTest, RainyDaySendToClosedSocket) {
+  ConnectedTrio trio =
+      CreateAndConnect(GetPortNumberForTests(), kSocketTimeout);
+  EXPECT_NE(trio.client_socket, kSbSocketInvalid);
+  EXPECT_NE(trio.server_socket, kSbSocketInvalid);
+  EXPECT_NE(trio.listen_socket, kSbSocketInvalid);
+
+  // We don't need the listen socket, so close it.
+  EXPECT_TRUE(SbSocketDestroy(trio.listen_socket));
+
+  // Start a thread to write to the client socket.
+  const bool kJoinable = true;
+  SbThread send_thread = SbThreadCreate(
+      0, kSbThreadNoPriority, kSbThreadNoAffinity, kJoinable, "SendToTest",
+      SendToServerSocketEntryPoint, static_cast<void*>(&trio));
+
+  // Close the client, which should cause writes to the server socket to fail.
+  EXPECT_TRUE(SbSocketDestroy(trio.client_socket));
+
+  // Wait for the thread to exit and check the last socket error.
+  void* thread_result;
+  EXPECT_TRUE(SbThreadJoin(send_thread, &thread_result));
+  // Check that the server_socket failed, as expected.
+  EXPECT_EQ(SbSocketGetLastError(trio.server_socket), kSbSocketErrorFailed);
+
+  // Clean up the server socket.
+  EXPECT_TRUE(SbSocketDestroy(trio.server_socket));
+}
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
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/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc
new file mode 100644
index 0000000..4e01d35
--- /dev/null
+++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -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.
+
+#include "starboard/speech_synthesis.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+
+TEST(SbSpeechSynthesisBasicTest, Basic) {
+  // An implementation must at least support US English
+  EXPECT_TRUE(SbSpeechSynthesisSetLanguage("en-US"));
+  SbSpeechSynthesisSpeak("Hello");
+  SbSpeechSynthesisCancel();
+}
+
+#endif  // SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+
diff --git a/src/starboard/nplb/storage_helpers.h b/src/starboard/nplb/storage_helpers.h
index 56b4fc2..efc422e 100644
--- a/src/starboard/nplb/storage_helpers.h
+++ b/src/starboard/nplb/storage_helpers.h
@@ -27,12 +27,12 @@
 const int64_t kStorageSize2 = kStorageSize * 2 + kStorageOffset;
 
 // Deletes the storage for the current user.
-SB_C_INLINE void ClearStorageRecord() {
+static SB_C_INLINE void ClearStorageRecord() {
   SbStorageDeleteRecord(SbUserGetCurrent());
 }
 
 // Opens the storage record for the current user, validating that it is valid.
-SB_C_INLINE SbStorageRecord OpenStorageRecord() {
+static SB_C_INLINE SbStorageRecord OpenStorageRecord() {
   SbStorageRecord record = SbStorageOpenRecord(SbUserGetCurrent());
   EXPECT_TRUE(SbStorageIsValidRecord(record));
   return record;
@@ -40,7 +40,8 @@
 
 // Writes a standard pattern of |size| bytes into the given open storage
 // |record|.
-SB_C_INLINE void WriteStorageRecord(SbStorageRecord record, int64_t size) {
+static SB_C_INLINE void WriteStorageRecord(SbStorageRecord record,
+                                           int64_t size) {
   char* data = new char[size];
   for (int64_t i = 0; i < size; ++i) {
     data[i] = static_cast<char>(i + 2 % 0xFF);
@@ -52,7 +53,8 @@
 
 // Ensures that the storage record for the current user is initialized with the
 // standard pattern for exactly |length| bytes.
-SB_C_INLINE void InitializeStorageRecord(int64_t length) {
+
+static SB_C_INLINE void InitializeStorageRecord(int64_t length) {
   ClearStorageRecord();
   SbStorageRecord record = OpenStorageRecord();
   WriteStorageRecord(record, length);
@@ -62,7 +64,7 @@
 // Checks a buffer of |total| size for the expected pattern (written in
 // WriteStorageRecord) to start at |offset| and continue for |length|, and the
 // rest of the buffer, before and after, should be set to 0.
-SB_C_INLINE void CheckStorageBuffer(char* data,
+static SB_C_INLINE void CheckStorageBuffer(char* data,
                                     int64_t offset,
                                     int64_t length,
                                     int64_t total) {
@@ -83,7 +85,7 @@
 // |offset| and reporting the buffer size as |length|, checks that the number of
 // read bytes is |expected_length| and then checks the buffer for the expected
 // pattern written in WriteStorageRecord over the expected range of the buffer.
-SB_C_INLINE void ReadAndCheckStorage(SbStorageRecord record,
+static SB_C_INLINE void ReadAndCheckStorage(SbStorageRecord record,
                                      int64_t offset,
                                      int64_t expected_length,
                                      int64_t length,
diff --git a/src/starboard/nplb/string_duplicate_test.cc b/src/starboard/nplb/string_duplicate_test.cc
index d61e0c2..17b794b 100644
--- a/src/starboard/nplb/string_duplicate_test.cc
+++ b/src/starboard/nplb/string_duplicate_test.cc
@@ -26,7 +26,7 @@
   EXPECT_NE(kNull, dupe);
   EXPECT_EQ(0, SbStringCompareNoCase(input, dupe));
   EXPECT_EQ(SbStringGetLength(input), SbStringGetLength(dupe));
-  SbMemoryFree(dupe);
+  SbMemoryDeallocate(dupe);
 }
 
 TEST(SbStringDuplicateTest, SunnyDay) {
diff --git a/src/starboard/nplb/system_get_property_test.cc b/src/starboard/nplb/system_get_property_test.cc
index de94895..ef4078f 100644
--- a/src/starboard/nplb/system_get_property_test.cc
+++ b/src/starboard/nplb/system_get_property_test.cc
@@ -70,6 +70,9 @@
   BasicTest(kSbSystemPropertyChipsetModelNumber, false, true, __LINE__);
   BasicTest(kSbSystemPropertyFirmwareVersion, false, true, __LINE__);
   BasicTest(kSbSystemPropertyNetworkOperatorName, false, true, __LINE__);
+#if SB_VERSION(2)
+  BasicTest(kSbSystemPropertySpeechApiKey, false, true, __LINE__);
+#endif  // SB_VERSION(2)
 
   if (IsCEDevice(SbSystemGetDeviceType())) {
     BasicTest(kSbSystemPropertyBrandName, true, true, __LINE__);
diff --git a/src/starboard/nplb/thread_helpers.h b/src/starboard/nplb/thread_helpers.h
index 0ebbd03..aa4e32d 100644
--- a/src/starboard/nplb/thread_helpers.h
+++ b/src/starboard/nplb/thread_helpers.h
@@ -16,10 +16,14 @@
 #define STARBOARD_NPLB_THREAD_HELPERS_H_
 
 #include "starboard/condition_variable.h"
+#include "starboard/configuration.h"
 #include "starboard/mutex.h"
+#include "starboard/thread.h"
 #include "starboard/time.h"
 #include "starboard/types.h"
 
+#include "testing/gtest/include/gtest/gtest.h"
+
 namespace starboard {
 namespace nplb {
 
@@ -93,6 +97,53 @@
   SbTime delay_after_signal;
 };
 
+// AbstractTestThread that is a bare bones class wrapper around Starboard
+// thread. Subclasses must override Run().
+class AbstractTestThread {
+ public:
+  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_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(AbstractTestThread);
+};
+
 }  // namespace nplb
 }  // namespace starboard
 
diff --git a/src/starboard/nplb/thread_join_test.cc b/src/starboard/nplb/thread_join_test.cc
index 42a2b6c..8b93e32 100644
--- a/src/starboard/nplb/thread_join_test.cc
+++ b/src/starboard/nplb/thread_join_test.cc
@@ -28,6 +28,45 @@
   EXPECT_EQ(NULL, result);
 }
 
+// Tests the expectation that SbThreadJoin() will block until
+// the thread function has been run.
+TEST(SbThreadLocalValueTest, ThreadJoinWaitsForFunctionRun) {
+  // Thread functionality needs to bind to functions. In C++11 we'd use a
+  // lambda function to tie everything together locally, but this
+  // function-scoped struct with static function emulates this functionality
+  // pretty well.
+  struct LocalStatic {
+    static void* ThreadEntryPoint(void* input) {
+      int* value = static_cast<int*>(input);
+      static const SbTime kSleepTime = 10*kSbTimeMillisecond;  // 10 ms.
+      // Wait to write the value to increase likelyhood of catching
+      // a race condition.
+      SbThreadSleep(kSleepTime);
+      (*value)++;
+      return NULL;
+    }
+  };
+
+  // Try to increase likelyhood of a race condition by running multiple times.
+  for (int i = 0; i < 10; ++i) {
+    int num_times_thread_entry_point_run = 0;
+    SbThread thread = SbThreadCreate(
+        0,                    // Signals automatic thread stack size.
+        kSbThreadNoPriority,  // Signals default priority.
+        kSbThreadNoAffinity,  // Signals default affinity.
+        true,                 // joinable thread.
+        "TestThread",
+        LocalStatic::ThreadEntryPoint,
+        &num_times_thread_entry_point_run);
+
+    ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful";
+    ASSERT_TRUE(SbThreadJoin(thread, NULL));
+
+    ASSERT_EQ(1, num_times_thread_entry_point_run)
+        << "Expected SbThreadJoin() to be blocked until ThreadFunction runs.";
+  }
+}
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/thread_local_value_test.cc b/src/starboard/nplb/thread_local_value_test.cc
index 43d32c4..4083684 100644
--- a/src/starboard/nplb/thread_local_value_test.cc
+++ b/src/starboard/nplb/thread_local_value_test.cc
@@ -92,6 +92,73 @@
   EXPECT_FALSE(my_value.destroyed);
 }
 
+// Helper function that ensures that the returned key is not recycled.
+SbThreadLocalKey CreateTLSKey_NoRecycle(SbThreadLocalDestructor dtor) {
+  SbThreadLocalKey key = SbThreadCreateLocalKey(NULL);
+  EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+  // Some Starboard implementations may recycle the original key, so this test
+  // ensures that in that case it will be reset to NULL.
+  SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
+  SbThreadDestroyLocalKey(key);
+  key = SbThreadCreateLocalKey(DestroyThreadLocalValue);
+  return key;
+}
+
+// Tests the expectation that thread at-exit destructors don't
+// run for ThreadLocal pointers that are set to NULL.
+TEST(SbThreadLocalValueTest, NoDestructorsForNullValue) {
+  static int s_num_destructor_calls = 0;  // Must be initialized to 0.
+  s_num_destructor_calls = 0;             // Allows test to be re-run.
+
+  // Thread functionality needs to bind to functions. In C++11 we'd use a
+  // lambda function to tie everything together locally, but this
+  // function-scoped struct with static members emulates this functionality
+  // pretty well.
+  struct LocalStatic {
+    // Used as a fake destructor for thread-local-storage objects in this
+    // test.
+    static void CountsDestructorCalls(void* /*value*/) {
+      s_num_destructor_calls++;
+    }
+
+    // Sets a thread local non-NULL value, and then sets it back to NULL.
+    static void* ThreadEntryPoint(void* ptr) {
+      SbThreadLocalKey key = *static_cast<SbThreadLocalKey*>(ptr);
+      EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+      // Set the value and then NULL it out. We expect that because the final
+      // value set was NULL, that the destructor attached to the thread's
+      // at-exit function will not run.
+      SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
+      SbThreadSetLocalValue(key, NULL);
+      return NULL;
+    }
+  };
+
+  // Setup the thread key and bind the fake test destructor.
+  SbThreadLocalKey key =
+      CreateTLSKey_NoRecycle(LocalStatic::CountsDestructorCalls);
+  EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
+
+  // Spawn the thread.
+  SbThread thread = SbThreadCreate(
+      0,                    // Signals automatic thread stack size.
+      kSbThreadNoPriority,  // Signals default priority.
+      kSbThreadNoAffinity,  // Signals default affinity.
+      true,                 // joinable thread.
+      "TestThread",
+      LocalStatic::ThreadEntryPoint,
+      static_cast<void*>(&key));
+
+  ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful";
+  // 2nd param is return value from ThreadEntryPoint, which is always NULL.
+  ASSERT_TRUE(SbThreadJoin(thread, NULL));
+
+  // No destructors should have run.
+  EXPECT_EQ(0, s_num_destructor_calls);
+
+  SbThreadDestroyLocalKey(key);
+}
+
 TEST(SbThreadLocalValueTest, SunnyDay) {
   DoSunnyDayTest(true);
 }
@@ -101,15 +168,9 @@
 }
 
 TEST(SbThreadLocalValueTest, SunnyDayFreshlyCreatedValuesAreNull) {
-  SbThreadLocalKey key = SbThreadCreateLocalKey(NULL);
+  SbThreadLocalKey key = CreateTLSKey_NoRecycle(NULL);  // NULL dtor.
   EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
 
-  // Some Starboard implementations may recycle the original key, so this test
-  // ensures that in that case it will be reset to NULL.
-  SbThreadSetLocalValue(key, reinterpret_cast<void*>(1));
-  SbThreadDestroyLocalKey(key);
-
-  key = SbThreadCreateLocalKey(NULL);
   EXPECT_EQ(NULL, SbThreadGetLocalValue(key));
   SbThreadDestroyLocalKey(key);
 }
diff --git a/src/starboard/once.h b/src/starboard/once.h
index 03039c9..8eba6cb 100644
--- a/src/starboard/once.h
+++ b/src/starboard/once.h
@@ -1,43 +1,75 @@
-// 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.

-

-// Onces represent initializations that should only ever happen once per

-// process, in a thread safe way.

-

-#ifndef STARBOARD_ONCE_H_

-#define STARBOARD_ONCE_H_

-

-#include "starboard/export.h"

-#include "starboard/thread_types.h"

-#include "starboard/types.h"

-

-#ifdef __cplusplus

-extern "C" {

-#endif

-

-// Function pointer type for methods that can be called va the SbOnce() system.

-typedef void (*SbOnceInitRoutine)(void);

-

-// If SbOnce() was never called before on with |once_control|, this function

-// will run |init_routine| in a thread-safe way and then returns true.  If

-// SbOnce() was called before, the function returns (true) immediately.

-// If |once_control| or |init_routine| are invalid, the function returns false.

-SB_EXPORT bool SbOnce(SbOnceControl* once_control,

-                      SbOnceInitRoutine init_routine);

-

-#ifdef __cplusplus

-}

-#endif

-

-#endif  // STARBOARD_ONCE_H_

+// 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.
+
+// Module Overview: Starboard Once module
+//
+// Onces represent initializations that should only ever happen once per
+// process, in a thread-safe way.
+
+#ifndef STARBOARD_ONCE_H_
+#define STARBOARD_ONCE_H_
+
+#include "starboard/export.h"
+#include "starboard/thread_types.h"
+#include "starboard/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Function pointer type for methods that can be called via the SbOnce() system.
+typedef void (*SbOnceInitRoutine)(void);
+
+// Thread-safely runs |init_routine| only once.
+// - If this |once_control| has not run a function yet, this function runs
+//   |init_routine| in a thread-safe way and then returns |true|.
+// - If SbOnce() was called with |once_control| before, the function returns
+//   |true| immediately.
+// - If |once_control| or |init_routine| is invalid, the function returns
+//   |false|.
+SB_EXPORT bool SbOnce(SbOnceControl* once_control,
+                      SbOnceInitRoutine init_routine);
+
+#ifdef __cplusplus
+// Defines a function that will initialize the indicated type once and return
+// it. This initialization is thread safe if the type being created is side
+// effect free.
+//
+// These macros CAN ONLY BE USED IN A CC file, never in a header file.
+//
+// Example (in cc file):
+//   SB_ONCE_INITIALIZE_FUNCTION(MyClass, GetOrCreateMyClass)
+//   ..
+//   MyClass* instance = GetOrCreateMyClass();
+//   MyClass* instance2 = GetOrCreateMyClass();
+//   DCHECK_EQ(instance, instance2);
+#define SB_ONCE_INITIALIZE_FUNCTION(Type, FunctionName)    \
+Type* FunctionName() {                                     \
+  static SbOnceControl s_once_flag = SB_ONCE_INITIALIZER;  \
+  static Type* s_singleton = NULL;                         \
+  struct Local {                                           \
+    static void Init() {                                   \
+      s_singleton = new Type();                            \
+    }                                                      \
+  };                                                       \
+  SbOnce(&s_once_flag, Local::Init);                       \
+  return s_singleton;                                      \
+}
+#endif  // __cplusplus
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // STARBOARD_ONCE_H_
diff --git a/src/starboard/player.h b/src/starboard/player.h
index bc19c20..cbd1ff3 100644
--- a/src/starboard/player.h
+++ b/src/starboard/player.h
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// An interface for controlling playback of media elementary streams.
+// Module Overview: Starboard Player module
+//
+// Defines an interface for controlling playback of media elementary streams.
 
 #ifndef STARBOARD_PLAYER_H_
 #define STARBOARD_PLAYER_H_
@@ -21,6 +23,7 @@
 
 #if SB_HAS(PLAYER)
 
+#include "starboard/decode_target.h"
 #include "starboard/drm.h"
 #include "starboard/export.h"
 #include "starboard/media.h"
@@ -35,13 +38,12 @@
 
 // An indicator of whether the decoder can accept more samples.
 typedef enum SbPlayerDecoderState {
-  // The decoder is ready for and eager to receive more samples.  Note that
-  // once the decoder notifies the user with kSbPlayerDecoderStateNeedsData,
-  // it cannot enter kSbPlayerDecoderStateBufferFull before SbPlayerWriteSample
-  // or SbPlayerWriteEndOfStream is called.
+  // The decoder is asking for one more sample.
   kSbPlayerDecoderStateNeedsData,
 
   // The decoder is not ready for any more samples, so do not send them.
+  // Note that this enum value has been deprecated and the SbPlayer
+  // implementation should no longer use this value.
   kSbPlayerDecoderStateBufferFull,
 
   // The player has been destroyed, and will send no more callbacks.
@@ -50,7 +52,7 @@
 
 // An indicator of the general playback state.
 typedef enum SbPlayerState {
-  // The player has just been initialized.  It is expecting an SbPlayerSeek()
+  // The player has just been initialized. It is expecting an SbPlayerSeek()
   // call to enter the prerolling state.
   kSbPlayerStateInitialized,
 
@@ -60,8 +62,9 @@
   // Prerolling after a Seek.
   kSbPlayerStatePrerolling,
 
-  // The player is presenting media, and it is either actively playing in
-  // real-time, or it is paused.
+  // The player is presenting media, and it is either paused or actively
+  // playing in real-time. Note that the implementation should use this
+  // state to signal that the preroll has been finished.
   kSbPlayerStatePresenting,
 
   // The player is presenting media, but it is paused at the end of the stream.
@@ -70,8 +73,8 @@
   // The player has been destroyed, and will send no more callbacks.
   kSbPlayerStateDestroyed,
 
-  // The player encounters an error.  It is expecting an SbPlayerDestroy() call
-  // to tear down the player.  Any call to other functions maybe ignored and
+  // The player encountered an error. It expects an SbPlayerDestroy() call
+  // to tear down the player. Calls to other functions may be ignored and
   // callbacks may not be triggered.
   kSbPlayerStateError,
 } SbPlayerState;
@@ -124,7 +127,11 @@
 // this callback was dispatched. This is to distinguish status callbacks for
 // interrupting seeks. These callbacks will happen on a different thread than
 // the calling thread, and it is not valid to call SbPlayer functions from
-// within this callback.
+// within this callback. After an update with kSbPlayerDecoderStateNeedsData,
+// the user of the player will make at most one call to SbPlayerWriteSample() or
+// SbPlayerWriteEndOfStream(). The player implementation should update the
+// decoder status again after such call to notify its user to continue writing
+// more frames.
 typedef void (*SbPlayerDecoderStatusFunc)(SbPlayer player,
                                           void* context,
                                           SbMediaType type,
@@ -166,7 +173,7 @@
 #define kSbPlayerInvalid ((SbPlayer)NULL)
 
 // Returns whether the given player handle is valid.
-SB_C_INLINE bool SbPlayerIsValid(SbPlayer player) {
+static SB_C_INLINE bool SbPlayerIsValid(SbPlayer player) {
   return player != kSbPlayerInvalid;
 }
 
@@ -174,108 +181,146 @@
 
 // Creates a player that will be displayed on |window| for the specified
 // |video_codec| and |audio_codec|, acquiring all resources needed to operate
-// it, and returning an opaque handle to it. |window| can be kSbWindowInvalid
-// for platforms where video will be only displayed on a particular window
-// which the underlying implementation already has access to. If |video_codec|
-// is kSbMediaVideoCodecNone, the player is an audio-only player. Otherwise, the
-// player is an audio/video decoder. |audio_codec| should never be
-// kSbMediaAudioCodecNone. The expectation is that a new player will be created
-// and destroyed for every playback.
+// it, and returning an opaque handle to it. The expectation is that a new
+// player will be created and destroyed for every playback.
 //
-// |duration_pts| is the expected media duration in 90KHz ticks (PTS). It may be
-// set to SB_PLAYER_NO_DURATION for live streams.
+// This function returns the created player. Note the following:
+// - The associated decoder of the returned player should be assumed to not be
+//   in |kSbPlayerDecoderStateNeedsData| until SbPlayerSeek() has been called
+//   on it.
+// - It is expected either that the thread that calls SbPlayerCreate is the same
+//   thread that calls the other |SbPlayer| functions for that player, or that
+//   there is a mutex guarding calls into each |SbPlayer| instance.
+// - If there is a platform limitation on how many players can coexist
+//   simultaneously, then calls made to this function that attempt to exceed
+//   that limit will return |kSbPlayerInvalid|.
 //
-// If the media stream has encrypted portions, then an appropriate DRM system
-// must first be created with SbDrmCreateSystem() and passed into |drm_system|.
-// If not, then |drm_system| may be kSbDrmSystemInvalid.
+// |window|: The window that will display the player. |window| can be
+//   |kSbWindowInvalid| for platforms where video is only displayed on a
+//   particular window that the underlying implementation already has access to.
 //
-// If |audio_codec| is kSbMediaAudioCodecAac, then the caller must provide a
-// populated |audio_header|. Otherwise, this may be NULL.
+// |video_codec|: The video codec used for the player. If |video_codec| is
+//   |kSbMediaVideoCodecNone|, the player is an audio-only player. If
+//   |video_codec| is any other value, the player is an audio/video decoder.
 //
-// If not NULL, the player will call |sample_deallocator_func| on an internal
-// thread to free the sample buffers passed into SbPlayerWriteSample().
+// |audio_codec|: The audio codec used for the player. The value should never
+//   be |kSbMediaAudioCodecNone|. In addition, the caller must provide a
+//   populated |audio_header| if the audio codec is |kSbMediaAudioCodecAac|.
 //
-// If not NULL, the decoder will call |decoder_status_func| on an internal
-// thread to provide an update on the decoder's status. No work should be done
-// on this thread, it should just signal the client thread interacting with the
-// decoder.
+// |duration_pts|: The expected media duration in 90KHz ticks (PTS). It may be
+//   set to |SB_PLAYER_NO_DURATION| for live streams.
 //
-// If not NULL, the player will call |player_status_func| on an internal thread
-// to provide an update on the playback status. No work should be done on this
-// thread, it should just signal the client thread interacting with the decoder.
+// |drm_system|: If the media stream has encrypted portions, then this
+//   parameter provides an appropriate DRM system, created with
+//   |SbDrmCreateSystem()|. If the stream does not have encrypted portions,
+//   then |drm_system| may be |kSbDrmSystemInvalid|.
 //
-// |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 player.
+// |audio_header|: Note that the caller must provide a populated |audio_header|
+//   if the audio codec is |kSbMediaAudioCodecAac|. Otherwise, |audio_header|
+//   can be NULL. See media.h for the format of the |SbMediaAudioHeader| struct.
 //
-// The associated decoder of the returned player should be assumed to be in
-// kSbPlayerDecoderStateBufferFull until SbPlayerSeek() has been called on it.
+// |sample_deallocator_func|: If not |NULL|, the player calls this function
+//   on an internal thread to free the sample buffers passed into
+//   SbPlayerWriteSample().
 //
-// It is expected that the thread that calls SbPlayerCreate is the same thread
-// that calls the other SbPlayer functions for that player, or that there is a
-// mutex guarding calls into each SbPlayer instance.
+// |decoder_status_func|: If not |NULL|, the decoder calls this function on an
+//   internal thread to provide an update on the decoder's status. No work
+//   should be done on this thread. Rather, it should just signal the client
+//   thread interacting with the decoder.
 //
-// If there is a platform limitation on how many players can coexist
-// simultaneously, then calls made to this function that attempt to exceed that
-// limit will return kSbPlayerInvalid.
-SB_EXPORT SbPlayer
-SbPlayerCreate(SbWindow window,
-               SbMediaVideoCodec video_codec,
-               SbMediaAudioCodec audio_codec,
-               SbMediaTime duration_pts,
-               SbDrmSystem drm_system,
-               const SbMediaAudioHeader* audio_header,
-               SbPlayerDeallocateSampleFunc sample_deallocate_func,
-               SbPlayerDecoderStatusFunc decoder_status_func,
-               SbPlayerStatusFunc player_status_func,
-               void* context);
+// |player_status_func|: If not |NULL|, the player calls this function on an
+//   internal thread to provide an update on the playback status. No work
+//   should be done on this thread. Rather, it should just signal the client
+//   thread interacting with the decoder.
+//
+// |context|: This is passed to all callbacks and is generally used to point
+//   at a class or struct that contains state associated with the player.
+//
+// |provider|: Only present in Starboard version 3 and up.  If not |NULL|,
+//   then when SB_HAS(PLAYER_PRODUCING_TEXTURE) is true, the player MAY use the
+//   provider to create SbDecodeTargets. A provider could also potentially be
+//   required by the player, in which case, if the provider is not given, the
+//   player will fail by returning kSbPlayerInvalid.
+SB_EXPORT SbPlayer SbPlayerCreate(
+    SbWindow window,
+    SbMediaVideoCodec video_codec,
+    SbMediaAudioCodec audio_codec,
+    SbMediaTime duration_pts,
+    SbDrmSystem drm_system,
+    const SbMediaAudioHeader* audio_header,
+    SbPlayerDeallocateSampleFunc sample_deallocate_func,
+    SbPlayerDecoderStatusFunc decoder_status_func,
+    SbPlayerStatusFunc player_status_func,
+    void* context
+#if SB_VERSION(3)
+    ,
+    SbDecodeTargetProvider* provider
+#endif  // SB_VERSION(3)
+    );  // NOLINT
 
 // Destroys |player|, freeing all associated resources. Each callback must
-// receive one more callback to say that the player was destroyed.  Callbacks
+// receive one more callback to say that the player was destroyed. Callbacks
 // may be in-flight when SbPlayerDestroy is called, and should be ignored once
 // this function is called.
 //
-// It is not allowed to pass |player| into any other SbPlayer function once
-// SbPlayerDestroy has been called on it.
+// It is not allowed to pass |player| into any other |SbPlayer| function once
+// SbPlayerDestroy has been called on that player.
+//
+// |player|: The player to be destroyed.
 SB_EXPORT void SbPlayerDestroy(SbPlayer player);
 
-// Tells the player to freeze playback where it is (if it has already started),
-// reset/flush the decoder pipeline, and go back to the Prerolling state. The
-// player should restart playback once it can display the frame at
-// |seek_to_pts|, or the closest it can get (some players can only seek to
-// I-Frames, for example). The client should send no more audio or video samples
-// until SbPlayerDecoderStatusFunc is called back with
-// kSbPlayerDecoderStateNeedsData, for each required media type.
+// Tells the player to freeze playback (if playback has already started),
+// reset or flush the decoder pipeline, and go back to the Prerolling state.
+// The player should restart playback once it can display the frame at
+// |seek_to_pts|, or the closest it can get. (Some players can only seek to
+// I-Frames, for example.)
 //
-// A call to seek may interrupt another seek.
+// - Seek must be called before samples are sent when starting playback for
+//   the first time, or the client never receives the
+//   |kSbPlayerDecoderStateNeedsData| signal.
+// - A call to seek may interrupt another seek.
+// - After this function is called, the client should not send any more audio
+//   or video samples until |SbPlayerDecoderStatusFunc| is called back with
+//   |kSbPlayerDecoderStateNeedsData| for each required media type.
+//   |SbPlayerDecoderStatusFunc| is the |decoder_status_func| callback function
+//   that was specified when the player was created (SbPlayerCreate).
 //
-// |ticket| is a user-supplied unique ID that will be passed to all subsequent
-// SbPlayerDecoderStatusFunc calls. This is used to filter calls that may have
-// been in flight when SbPlayerSeek is called. To be very specific, once
-// SbPlayerSeek has been called with ticket X, a client should ignore all
-// SbPlayerDecoderStatusFunc calls that don't pass in ticket X.
+// |player|: The SbPlayer in which the seek operation is being performed.
+// |seek_to_pts|: The frame at which playback should begin.
+// |ticket|: A user-supplied unique ID that is be passed to all subsequent
+//   |SbPlayerDecoderStatusFunc| calls. (That is the |decoder_status_func|
+//   callback function specified when calling SbPlayerCreate.)
 //
-// Seek must also be called before samples are sent when starting playback for
-// the first time, or the client will never receive the
-// kSbPlayerDecoderStateNeedsData signal.
+//   The |ticket| value is used to filter calls that may have been in flight
+//   when SbPlayerSeek was called. To be very specific, once SbPlayerSeek has
+//   been called with ticket X, a client should ignore all
+//   |SbPlayerDecoderStatusFunc| calls that do not pass in ticket X.
 SB_EXPORT void SbPlayerSeek(SbPlayer player,
                             SbMediaTime seek_to_pts,
                             int ticket);
 
-// Writes a sample of the given media type to |player|'s input stream.
-// |sample_type| is the type of sample, audio or video. |sample_buffer| is a
-// pointer to a buffer with the data for this sample. This buffer is expected to
-// be a portion of a H.264 bytestream, containing a sequence of whole NAL Units
-// for video, or a complete audio frame. |sample_buffer_size| is the number of
-// bytes in the given sample. |sample_pts| is the timestamp of the sample in
-// 90KHz ticks (PTS), and samples MAY be written "slightly" out-of-order.
-// |video_sample_info| must be provided for every call where |sample_type| is
-// kSbMediaTypeVideo, and must be NULL otherwise.  |sample_drm_info| must be
-// provided for encrypted samples, and must be NULL otherwise.
+// Writes a sample of the given media type to |player|'s input stream. The
+// lifetime of |video_sample_info| and |sample_drm_info| (as well as member
+// |subsample_mapping| contained inside it) are not guaranteed past the call
+// to SbPlayerWriteSample. That means that before returning, the implementation
+// must synchronously copy any information it wants to retain from those
+// structures.
 //
-// The lifetime of |video_sample_info| and |sample_drm_info| are not guaranteed
-// past the call to SbPlayerWriteSample, so the implementation must copy any
-// information it wants to retain from those structures synchronously, before it
-// returns.
+// |player|: The player to which the sample is written.
+// |sample_type|: The type of sample being written. See the |SbMediaType|
+//   enum in media.h.
+// |sample_buffer|: A pointer to a buffer with the data for this sample. The
+//   buffer is expected to be a portion of a bytestream of the codec type that
+//   the player was created with. The buffer should contain a sequence of whole
+//   NAL Units for video, or a complete audio frame.
+// |sample_buffer_size|: The number of bytes in the given sample.
+// |sample_pts|: The timestamp of the sample in 90KHz ticks (PTS). Note that
+//   samples MAY be written "slightly" out of order.
+// |video_sample_info|: Information about a video sample. This value is
+//   required if |sample_type| is |kSbMediaTypeVideo|. Otherwise, it must be
+//   |NULL|.
+// |sample_drm_info|: The DRM system for the media sample. This value is
+//   required for encrypted samples. Otherwise, it must be |NULL|.
 SB_EXPORT void SbPlayerWriteSample(
     SbPlayer player,
     SbMediaType sample_type,
@@ -285,19 +330,32 @@
     const SbMediaVideoSampleInfo* video_sample_info,
     const SbDrmSampleInfo* sample_drm_info);
 
-// Writes a marker to |player|'s input stream of |stream_type| that there are no
-// more samples for that media type for the remainder of this media stream.
-// This marker is invalidated, along with the rest of the contents of the
-// stream, after a call to SbPlayerSeek.
+// Writes a marker to |player|'s input stream of |stream_type| indicating that
+// there are no more samples for that media type for the remainder of this
+// media stream. This marker is invalidated, along with the rest of the stream's
+// contents, after a call to SbPlayerSeek.
+//
+// |player|: The player to which the marker is written.
+// |stream_type|: The type of stream for which the marker is written.
 SB_EXPORT void SbPlayerWriteEndOfStream(SbPlayer player,
                                         SbMediaType stream_type);
 
 #if SB_IS(PLAYER_PUNCHED_OUT)
-// Sets the player bounds to the given graphics plane coordinates. Will not take
-// effect until the next graphics frame buffer swap. The default bounds for a
-// player are the full screen. This function should be expected to be called up
-// to once per frame, so implementors should take care to avoid related
-// performance concerns with such frequent calls.
+// Sets the player bounds to the given graphics plane coordinates. The changes
+// do not take effect until the next graphics frame buffer swap. The default
+// bounds for a player is the full screen.
+//
+// This function is called on every graphics frame that changes the video
+// bounds. For example, if the video bounds are being animated, then this will
+// be called at up to 60 Hz. Since the function could be called up to once per
+// frame, implementors should take care to avoid related performance concerns
+// with such frequent calls.
+//
+// |player|: The player that is being resized.
+// |x|: The x-coordinate of the upper-left corner of the player.
+// |y|: The y-coordinate of the upper-left corner of the player.
+// |width|: The width of the player, in pixels.
+// |height|: The height of the player, in pixels.
 SB_EXPORT void SbPlayerSetBounds(SbPlayer player,
                                  int x,
                                  int y,
@@ -305,30 +363,42 @@
                                  int height);
 #endif
 
-// Sets the pause status for |player|. If |player| is in kPlayerStatePrerolling,
-// this will set the initial pause state for the current seek target.
+// Pauses or unpauses the |player|. If the |player|'s state is
+// |kPlayerStatePrerolling|, this function sets the initial pause state for
+// the current seek target.
 SB_EXPORT void SbPlayerSetPause(SbPlayer player, bool pause);
 
-// Sets the volume for |player|, in [0.0, 1.0]. 0.0 means the audio should be
-// muted, and 1.0 means it should be played at full volume.
+// Sets the player's volume.
+//
+// |player|: The player in which the volume is being adjusted.
+// |volume|: The new player volume. The value must be between |0.0| and |1.0|,
+//   inclusive. A value of |0.0| means that the audio should be muted, and a
+//   value of |1.0| means that it should be played at full volume.
 SB_EXPORT void SbPlayerSetVolume(SbPlayer player, double volume);
 
-// Gets a snapshot of the current player state and writes it into
-// |out_player_info|. This function is expected to be inexpensive, and may be
-// called very frequently.
+// Gets a snapshot of the current player state and writes it to
+// |out_player_info|. This function may be called very frequently and is
+// expected to be inexpensive.
+//
+// |player|: The player about which information is being retrieved.
+// |out_player_info|: The information retrieved for the player.
 SB_EXPORT void SbPlayerGetInfo(SbPlayer player, SbPlayerInfo* out_player_info);
 
 #if SB_IS(PLAYER_COMPOSITED)
 // Gets a handle that represents the player's video output, for the purpose of
-// composing with SbCompositor (currently undefined).
+// composing with |SbCompositor|, which is currently undefined.
+//
+// |player|: The player for which the video output handle is retrieved.
 SB_EXPORT SbPlayerCompositionHandle
 SbPlayerGetCompositionHandle(SbPlayer player);
 #endif
 
 #if SB_IS(PLAYER_PRODUCING_TEXTURE)
 // Gets an OpenGL texture ID that points to the player's video output frame at
-// the time it was called. This can be called once, and the same texture ID will
-// be appropriately mapped to the current video frame when drawn.
+// the time it was called. This function can be called once, and the texture ID
+// will be appropriately remapped to the current video frame when it is drawn.
+//
+// |player|: The player for which the texture ID is retrieved.
 SB_EXPORT uint32_t SbPlayerGetTextureId(SbPlayer player);
 #endif
 
diff --git a/src/starboard/queue.h b/src/starboard/queue.h
index 65127f0..53f7633 100644
--- a/src/starboard/queue.h
+++ b/src/starboard/queue.h
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// A C++-only synchronized queue implementation, built entirely on top of
-// Starboard synchronization primitives. Can be safely used by both clients and
-// implementations.
+// Module Overview: Starboard Queue module
+//
+// Defines a C++-only synchronized queue implementation, built entirely on top
+// of Starboard synchronization primitives. It can be safely used by both
+// clients and implementations.
 
 #ifndef STARBOARD_QUEUE_H_
 #define STARBOARD_QUEUE_H_
diff --git a/src/starboard/raspi/1/gyp_configuration.gypi b/src/starboard/raspi/1/gyp_configuration.gypi
index 12a59f3..040ca1e 100644
--- a/src/starboard/raspi/1/gyp_configuration.gypi
+++ b/src/starboard/raspi/1/gyp_configuration.gypi
@@ -17,7 +17,6 @@
     'target_arch': 'arm',
     'target_os': 'linux',
 
-    'enable_webdriver': '1',
     'in_app_dial%': 0,
     'sysroot%': '/',
     'gl_type': 'system_gles2',
@@ -32,7 +31,9 @@
 
     # This should have a default value in cobalt/base.gypi. See the comment
     # there for acceptable values for this variable.
-    'javascript_engine': 'javascriptcore',
+    'javascript_engine': 'mozjs',
+    'cobalt_enable_jit': 0,
+    'cobalt_minimum_frame_time_in_milliseconds': '33',
 
     # RasPi 1 is ARMv6
     'arm_version': 6,
diff --git a/src/starboard/raspi/1/starboard_platform.gyp b/src/starboard/raspi/1/starboard_platform.gyp
index 6d8c257..c39d4e3 100644
--- a/src/starboard/raspi/1/starboard_platform.gyp
+++ b/src/starboard/raspi/1/starboard_platform.gyp
@@ -33,7 +33,17 @@
         '<(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/dispmanx_util.cc',
+        '<(DEPTH)/starboard/raspi/shared/dispmanx_util.h',
         '<(DEPTH)/starboard/raspi/shared/main.cc',
+        '<(DEPTH)/starboard/raspi/shared/open_max/open_max_component.cc',
+        '<(DEPTH)/starboard/raspi/shared/open_max/open_max_component.h',
+        '<(DEPTH)/starboard/raspi/shared/open_max/open_max_video_decode_component.cc',
+        '<(DEPTH)/starboard/raspi/shared/open_max/open_max_video_decode_component.h',
+        '<(DEPTH)/starboard/raspi/shared/open_max/video_decoder.cc',
+        '<(DEPTH)/starboard/raspi/shared/open_max/video_decoder.h',
+        '<(DEPTH)/starboard/raspi/shared/thread_create_priority.cc',
         '<(DEPTH)/starboard/raspi/shared/window_create.cc',
         '<(DEPTH)/starboard/raspi/shared/window_destroy.cc',
         '<(DEPTH)/starboard/raspi/shared/window_get_platform_handle.cc',
@@ -46,7 +56,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',
@@ -58,8 +67,6 @@
         '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h',
         '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
         '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
-        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h',
         '<(DEPTH)/starboard/shared/gcc/atomic_gcc.h',
         '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
         '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
@@ -179,6 +186,7 @@
         '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
         '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
         '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
+        '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc',
         '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
         '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
         '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc',
@@ -197,6 +205,7 @@
         '<(DEPTH)/starboard/shared/pthread/once.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h',
         '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
@@ -233,12 +242,17 @@
         '<(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/audio_decoder_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
@@ -254,11 +268,8 @@
         '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
         '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc',
-        '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h',
         '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
         '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
         '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
@@ -268,12 +279,19 @@
         '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
         '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
         '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
+        '<(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',
+        '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc',
         '<(DEPTH)/starboard/shared/stub/drm_close_session.cc',
         '<(DEPTH)/starboard/shared/stub/drm_create_system.cc',
         '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc',
         '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc',
         '<(DEPTH)/starboard/shared/stub/drm_system_internal.h',
         '<(DEPTH)/starboard/shared/stub/drm_update_session.cc',
+        '<(DEPTH)/starboard/shared/stub/image_decode.cc',
+        '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc',
         '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
         '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
         '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
@@ -287,6 +305,7 @@
         'STARBOARD_IMPLEMENTATION',
       ],
       'dependencies': [
+        '<(DEPTH)/starboard/common/common.gyp:common',
         '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
         '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
         'starboard_base_symbolize',
diff --git a/src/starboard/raspi/1/system_get_property.cc b/src/starboard/raspi/1/system_get_property.cc
index 6b5cee9..50f9d92 100644
--- a/src/starboard/raspi/1/system_get_property.cc
+++ b/src/starboard/raspi/1/system_get_property.cc
@@ -47,6 +47,7 @@
     case kSbSystemPropertyModelName:
     case kSbSystemPropertyModelYear:
     case kSbSystemPropertyNetworkOperatorName:
+    case kSbSystemPropertySpeechApiKey:
       return false;
 
     case kSbSystemPropertyFriendlyName:
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/raspi/shared/application_dispmanx.cc b/src/starboard/raspi/shared/application_dispmanx.cc
index ae3fb8e..4f9dddf 100644
--- a/src/starboard/raspi/shared/application_dispmanx.cc
+++ b/src/starboard/raspi/shared/application_dispmanx.cc
@@ -36,6 +36,10 @@
 
 using ::starboard::shared::dev_input::DevInput;
 
+namespace {
+const int kVideoLayer = -1;
+}  // namespace
+
 SbWindow ApplicationDispmanx::CreateWindow(const SbWindowOptions* options) {
   if (SbWindowIsValid(window_)) {
     return kSbWindowInvalid;
@@ -44,8 +48,11 @@
   InitializeDispmanx();
 
   SB_DCHECK(IsDispmanxInitialized());
-  window_ = new SbWindowPrivate(display_, options);
+  window_ = new SbWindowPrivate(*display_, options);
   input_ = DevInput::Create(window_);
+
+  video_renderer_.reset(new DispmanxVideoRenderer(*display_, kVideoLayer));
+
   return window_;
 }
 
@@ -76,6 +83,15 @@
   SbAudioSinkPrivate::TearDown();
 }
 
+void ApplicationDispmanx::AcceptFrame(SbPlayer player,
+                                      const scoped_refptr<VideoFrame>& frame,
+                                      int x,
+                                      int y,
+                                      int width,
+                                      int height) {
+  video_renderer_->Update(frame);
+}
+
 bool ApplicationDispmanx::MayHaveSystemEvents() {
   return input_ != NULL;
 }
@@ -102,8 +118,7 @@
     return;
   }
 
-  bcm_host_init();
-  display_ = vc_dispmanx_display_open(0);
+  display_.reset(new DispmanxDisplay);
   SB_DCHECK(IsDispmanxInitialized());
 }
 
@@ -113,10 +128,8 @@
   }
 
   SB_DCHECK(!SbWindowIsValid(window_));
-  int result = vc_dispmanx_display_close(display_);
-  SB_DCHECK(result == 0);
-  display_ = DISPMANX_NO_HANDLE;
-  bcm_host_deinit();
+
+  display_.reset();
 }
 
 }  // namespace shared
diff --git a/src/starboard/raspi/shared/application_dispmanx.h b/src/starboard/raspi/shared/application_dispmanx.h
index 6f6a938..d08c406 100644
--- a/src/starboard/raspi/shared/application_dispmanx.h
+++ b/src/starboard/raspi/shared/application_dispmanx.h
@@ -15,11 +15,9 @@
 #ifndef STARBOARD_RASPI_SHARED_APPLICATION_DISPMANX_H_
 #define STARBOARD_RASPI_SHARED_APPLICATION_DISPMANX_H_
 
-#include <bcm_host.h>
-
-#include <vector>
-
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/configuration.h"
+#include "starboard/raspi/shared/dispmanx_util.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/shared/linux/dev_input/dev_input.h"
 #include "starboard/shared/starboard/application.h"
@@ -35,8 +33,7 @@
 class ApplicationDispmanx
     : public ::starboard::shared::starboard::QueueApplication {
  public:
-  ApplicationDispmanx()
-      : display_(DISPMANX_NO_HANDLE), window_(kSbWindowInvalid), input_(NULL) {}
+  ApplicationDispmanx() : window_(kSbWindowInvalid), input_(NULL) {}
   ~ApplicationDispmanx() SB_OVERRIDE {}
 
   static ApplicationDispmanx* Get() {
@@ -51,6 +48,12 @@
   // --- Application overrides ---
   void Initialize() SB_OVERRIDE;
   void Teardown() SB_OVERRIDE;
+  void AcceptFrame(SbPlayer player,
+                   const scoped_refptr<VideoFrame>& frame,
+                   int x,
+                   int y,
+                   int width,
+                   int height) SB_OVERRIDE;
 
   // --- QueueApplication overrides ---
   bool MayHaveSystemEvents() SB_OVERRIDE;
@@ -60,7 +63,7 @@
 
  private:
   // Returns whether DISPMANX has been initialized.
-  bool IsDispmanxInitialized() { return display_ != DISPMANX_NO_HANDLE; }
+  bool IsDispmanxInitialized() { return display_ != NULL; }
 
   // Ensures that X is up, display is populated and connected.
   void InitializeDispmanx();
@@ -69,7 +72,10 @@
   void ShutdownDispmanx();
 
   // The DISPMANX display.
-  DISPMANX_DISPLAY_HANDLE_T display_;
+  scoped_ptr<DispmanxDisplay> display_;
+
+  // DISPMANX helper to render video frames.
+  scoped_ptr<DispmanxVideoRenderer> video_renderer_;
 
   // The single open window, if any.
   SbWindow window_;
diff --git a/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
new file mode 100644
index 0000000..f5026b8
--- /dev/null
+++ b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
@@ -0,0 +1,20 @@
+// 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/audio_sink.h"
+
+bool SbAudioSinkIsAudioSampleTypeSupported(
+    SbMediaAudioSampleType audio_sample_type) {
+  return audio_sample_type == kSbMediaAudioSampleTypeInt16;
+}
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h
index fcdaf1e..ba25c7f 100644
--- a/src/starboard/raspi/shared/configuration_public.h
+++ b/src/starboard/raspi/shared/configuration_public.h
@@ -18,7 +18,7 @@
 #define STARBOARD_RASPI_SHARED_CONFIGURATION_PUBLIC_H_
 
 // The API version implemented by this platform.
-#define SB_API_VERSION 1
+#define SB_API_VERSION 3
 
 // --- System Header Configuration -------------------------------------------
 
@@ -46,6 +46,15 @@
 // Whether the current platform provides the standard header limits.h.
 #define SB_HAS_LIMITS_H 1
 
+// Whether the current platform provides the standard header float.h.
+#define SB_HAS_FLOAT_H 1
+
+// Whether the current platform has microphone supported.
+#define SB_HAS_MICROPHONE 0
+
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 0
+
 // Type detection for wchar_t.
 #if defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
@@ -63,6 +72,14 @@
 #define SB_IS_WCHAR_T_SIGNED 1
 #endif
 
+// --- 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.
+#define SB_HAS_THREAD_PRIORITY_SUPPORT 1
+
 // --- Attribute Configuration -----------------------------------------------
 
 // The platform's annotation for forcing a C function to be inlined.
@@ -360,6 +377,12 @@
 // The maximum number of users that can be signed in at the same time.
 #define SB_USER_MAX_SIGNED_IN 1
 
+// --- Timing API ------------------------------------------------------------
+
+// Whether this platform has an API to retrieve how long the current thread
+// has spent in the executing state.
+#define SB_HAS_TIME_THREAD_NOW 1
+
 // --- Platform Specific Audits ----------------------------------------------
 
 #if !defined(__GNUC__)
diff --git a/src/starboard/raspi/shared/dispmanx_util.cc b/src/starboard/raspi/shared/dispmanx_util.cc
new file mode 100644
index 0000000..f33a441
--- /dev/null
+++ b/src/starboard/raspi/shared/dispmanx_util.cc
@@ -0,0 +1,169 @@
+// 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/raspi/shared/dispmanx_util.h"
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/memory.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+
+namespace {
+
+const int kElementChangeAttributesFlagSrcRect = 1 << 3;
+
+class DispmanxAutoUpdate {
+ public:
+  DispmanxAutoUpdate() {
+    handle_ = vc_dispmanx_update_start(0 /*screen*/);
+    SB_DCHECK(handle_ != DISPMANX_NO_HANDLE);
+  }
+  ~DispmanxAutoUpdate() {
+    if (handle_ != DISPMANX_NO_HANDLE) {
+      Update();
+    }
+  }
+
+  DISPMANX_UPDATE_HANDLE_T handle() const { return handle_; }
+
+  void Update() {
+    SB_DCHECK(handle_ != DISPMANX_NO_HANDLE);
+    int32_t result = vc_dispmanx_update_submit_sync(handle_);
+    SB_DCHECK(result == 0) << " result=" << result;
+    handle_ = DISPMANX_NO_HANDLE;
+  }
+
+ private:
+  DISPMANX_UPDATE_HANDLE_T handle_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DispmanxAutoUpdate);
+};
+
+}  // namespace
+
+DispmanxResource::DispmanxResource(VC_IMAGE_TYPE_T image_type,
+                                   uint32_t width,
+                                   uint32_t height,
+                                   uint32_t visible_width,
+                                   uint32_t visible_height)
+    : width_(width),
+      height_(height),
+      visible_width_(visible_width),
+      visible_height_(visible_height) {
+  static const uint32_t kMaxDimension = 1 << 16;
+
+  SB_DCHECK(width_ > 0 && width_ < kMaxDimension);
+  SB_DCHECK(height_ > 0 && height_ < kMaxDimension);
+  SB_DCHECK(visible_width_ > 0 && visible_width_ < kMaxDimension);
+  SB_DCHECK(visible_height > 0 && visible_height < kMaxDimension);
+  SB_DCHECK(width_ >= visible_width_);
+  SB_DCHECK(height_ >= visible_height);
+
+  uint32_t vc_image_ptr;
+
+  handle_ = vc_dispmanx_resource_create(
+      image_type, visible_width_ | (width_ << 16),
+      visible_height | (height_ << 16), &vc_image_ptr);
+  SB_DCHECK(handle_ != DISPMANX_NO_HANDLE);
+}
+
+void DispmanxYUV420Resource::WriteData(const void* data) {
+  SB_DCHECK(handle() != DISPMANX_NO_HANDLE);
+
+  DispmanxRect dst_rect(0, 0, width(), height() * 3 / 2);
+  int32_t result = vc_dispmanx_resource_write_data(
+      handle(), VC_IMAGE_YUV420, width(), const_cast<void*>(data), &dst_rect);
+  SB_DCHECK(result == 0);
+}
+
+void DispmanxYUV420Resource::ClearWithBlack() {
+  scoped_array<uint8_t> data(new uint8_t[width() * height() * 3 / 2]);
+  SbMemorySet(data.get(), width() * height(), 0);
+  SbMemorySet(data.get() + width() * height(), width() * height() / 2, 0x80);
+  WriteData(data.get());
+}
+
+void DispmanxRGB565Resource::WriteData(const void* data) {
+  SB_DCHECK(handle() != DISPMANX_NO_HANDLE);
+
+  DispmanxRect dst_rect(0, 0, width(), height());
+  int32_t result =
+      vc_dispmanx_resource_write_data(handle(), VC_IMAGE_RGB565, width() * 2,
+                                      const_cast<void*>(data), &dst_rect);
+  SB_DCHECK(result == 0);
+}
+
+void DispmanxRGB565Resource::ClearWithBlack() {
+  scoped_array<uint8_t> data(new uint8_t[width() * height() * 2]);
+  SbMemorySet(data.get(), width() * height() * 2, 0);
+  WriteData(data.get());
+}
+
+DispmanxElement::DispmanxElement(const DispmanxDisplay& display,
+                                 int32_t layer,
+                                 const DispmanxRect& dest_rect,
+                                 const DispmanxResource& src,
+                                 const DispmanxRect& src_rect) {
+  DispmanxAutoUpdate update;
+  handle_ = vc_dispmanx_element_add(update.handle(), display.handle(), layer,
+                                    &dest_rect, src.handle(), &src_rect,
+                                    DISPMANX_PROTECTION_NONE, NULL /*alpha*/,
+                                    NULL /*clamp*/, DISPMANX_NO_ROTATE);
+  SB_DCHECK(handle_ != DISPMANX_NO_HANDLE);
+}
+
+DispmanxElement::~DispmanxElement() {
+  DispmanxAutoUpdate update;
+  int32_t result = vc_dispmanx_element_remove(update.handle(), handle_);
+  SB_DCHECK(result == 0) << " result=" << result;
+}
+
+void DispmanxElement::ChangeSource(const DispmanxResource& new_src) {
+  DispmanxAutoUpdate update;
+  vc_dispmanx_element_change_source(update.handle(), handle_, new_src.handle());
+}
+
+DispmanxVideoRenderer::DispmanxVideoRenderer(const DispmanxDisplay& display,
+                                             int32_t layer)
+    : black_frame_(256, 256, 256, 256) {
+  black_frame_.ClearWithBlack();
+  element_.reset(new DispmanxElement(display, layer, DispmanxRect(),
+                                     black_frame_, DispmanxRect()));
+}
+
+void DispmanxVideoRenderer::Update(
+    const scoped_refptr<VideoFrame>& video_frame) {
+  SB_DCHECK(video_frame);
+
+  if (frame_ == video_frame) {
+    return;
+  }
+
+  if (video_frame->IsEndOfStream()) {
+    element_->ChangeSource(black_frame_);
+    frame_ = video_frame;
+    return;
+  }
+
+  DispmanxYUV420Resource* resource =
+      reinterpret_cast<DispmanxYUV420Resource*>(video_frame->native_texture());
+  element_->ChangeSource(*resource);
+  frame_ = video_frame;
+}
+
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/dispmanx_util.h b/src/starboard/raspi/shared/dispmanx_util.h
new file mode 100644
index 0000000..f051e86
--- /dev/null
+++ b/src/starboard/raspi/shared/dispmanx_util.h
@@ -0,0 +1,167 @@
+// 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_RASPI_SHARED_DISPMANX_UTIL_H_
+#define STARBOARD_RASPI_SHARED_DISPMANX_UTIL_H_
+
+#include <bcm_host.h>
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+
+class DispmanxRect : public VC_RECT_T {
+ public:
+  DispmanxRect() { vc_dispmanx_rect_set(this, 0, 0, 0, 0); }
+  DispmanxRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+    vc_dispmanx_rect_set(this, x, y, width, height);
+  }
+};
+
+class DispmanxDisplay {
+ public:
+  DispmanxDisplay() {
+    bcm_host_init();
+    handle_ = vc_dispmanx_display_open(0);
+    SB_DCHECK(handle_ != DISPMANX_NO_HANDLE);
+  }
+  ~DispmanxDisplay() {
+    int result = vc_dispmanx_display_close(handle_);
+    SB_DCHECK(result == 0);
+    bcm_host_deinit();
+  }
+
+  DISPMANX_DISPLAY_HANDLE_T handle() const { return handle_; }
+
+ private:
+  DISPMANX_DISPLAY_HANDLE_T handle_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DispmanxDisplay);
+};
+
+class DispmanxResource {
+ public:
+  DispmanxResource() : handle_(DISPMANX_NO_HANDLE) {}
+
+  virtual ~DispmanxResource() {
+    if (handle_ != DISPMANX_NO_HANDLE) {
+      int32_t result = vc_dispmanx_resource_delete(handle_);
+      SB_DCHECK(result == 0) << " result=" << result;
+    }
+  }
+
+  DISPMANX_RESOURCE_HANDLE_T handle() const { return handle_; }
+  uint32_t visible_width() const { return visible_width_; }
+  uint32_t visible_height() const { return visible_height_; }
+  uint32_t width() const { return width_; }
+  uint32_t height() const { return height_; }
+
+ protected:
+  DispmanxResource(VC_IMAGE_TYPE_T image_type,
+                   uint32_t width,
+                   uint32_t height,
+                   uint32_t visible_width,
+                   uint32_t visible_height);
+
+ private:
+  DISPMANX_RESOURCE_HANDLE_T handle_;
+  uint32_t width_;
+  uint32_t height_;
+  uint32_t visible_width_;
+  uint32_t visible_height_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DispmanxResource);
+};
+
+class DispmanxYUV420Resource : public DispmanxResource {
+ public:
+  DispmanxYUV420Resource(uint32_t width,
+                         uint32_t height,
+                         uint32_t visible_width,
+                         uint32_t visible_height)
+      : DispmanxResource(VC_IMAGE_YUV420,
+                         width,
+                         height,
+                         visible_width,
+                         visible_height) {}
+
+  void WriteData(const void* data);
+  void ClearWithBlack();
+};
+
+class DispmanxRGB565Resource : public DispmanxResource {
+ public:
+  DispmanxRGB565Resource(uint32_t width,
+                         uint32_t height,
+                         uint32_t visible_width,
+                         uint32_t visible_height)
+      : DispmanxResource(VC_IMAGE_RGB565,
+                         width,
+                         height,
+                         visible_width,
+                         visible_height) {}
+
+  void WriteData(const void* data);
+  void ClearWithBlack();
+};
+
+class DispmanxElement {
+ public:
+  DispmanxElement(const DispmanxDisplay& display,
+                  int32_t layer,
+                  const DispmanxRect& dest_rect,
+                  const DispmanxResource& src,
+                  const DispmanxRect& src_rect);
+  ~DispmanxElement();
+
+  DISPMANX_ELEMENT_HANDLE_T handle() const { return handle_; }
+  void ChangeSource(const DispmanxResource& new_src);
+
+ private:
+  DISPMANX_ELEMENT_HANDLE_T handle_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DispmanxElement);
+};
+
+class DispmanxVideoRenderer {
+ public:
+  typedef starboard::shared::starboard::player::VideoFrame VideoFrame;
+
+  DispmanxVideoRenderer(const DispmanxDisplay& display, int32_t layer);
+
+  void Update(const scoped_refptr<VideoFrame>& video_frame);
+
+ private:
+  scoped_ptr<DispmanxElement> element_;
+  scoped_refptr<VideoFrame> frame_;
+
+  // Used to fill the background with black if no video is playing so that the
+  // console does not show through.
+  DispmanxRGB565Resource black_frame_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DispmanxVideoRenderer);
+};
+
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
+
+#endif  // STARBOARD_RASPI_SHARED_DISPMANX_UTIL_H_
diff --git a/src/starboard/raspi/shared/open_max/open_max_component.cc b/src/starboard/raspi/shared/open_max/open_max_component.cc
new file mode 100644
index 0000000..bb814f6c
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/open_max_component.cc
@@ -0,0 +1,373 @@
+// 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/raspi/shared/open_max/open_max_component.h"
+
+#include <algorithm>
+
+#include "starboard/configuration.h"
+#include "starboard/once.h"
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+namespace {
+
+const int kInvalidPort = -1;
+
+OMX_INDEXTYPE kPortTypes[] = {OMX_IndexParamAudioInit, OMX_IndexParamVideoInit,
+                              OMX_IndexParamImageInit, OMX_IndexParamOtherInit};
+
+SbOnceControl s_open_max_initialization_once = SB_ONCE_INITIALIZER;
+
+void DoInitializeOpenMax() {
+  OMX_ERRORTYPE error = OMX_Init();
+  SB_DCHECK(error == OMX_ErrorNone);
+}
+
+void InitializeOpenMax() {
+  bool initialized =
+      SbOnce(&s_open_max_initialization_once, DoInitializeOpenMax);
+  SB_DCHECK(initialized);
+}
+
+}  // namespace
+
+OpenMaxComponent::OpenMaxComponent(const char* name)
+    : condition_variable_(mutex_),
+      handle_(NULL),
+      input_port_(kInvalidPort),
+      output_port_(kInvalidPort),
+      output_setting_changed_(false),
+      output_port_enabled_(false) {
+  InitializeOpenMax();
+
+  OMX_CALLBACKTYPE callbacks;
+  callbacks.EventHandler = OpenMaxComponent::EventHandler;
+  callbacks.EmptyBufferDone = OpenMaxComponent::EmptyBufferDone;
+  callbacks.FillBufferDone = OpenMaxComponent::FillBufferDone;
+
+  OMX_ERRORTYPE error =
+      OMX_GetHandle(&handle_, const_cast<char*>(name), this, &callbacks);
+  SB_DCHECK(error == OMX_ErrorNone);
+
+  for (size_t i = 0; i < SB_ARRAY_SIZE(kPortTypes); ++i) {
+    OMX_PORT_PARAM_TYPE port;
+    port.nSize = sizeof(OMX_PORT_PARAM_TYPE);
+    port.nVersion.nVersion = OMX_VERSION;
+
+    error = OMX_GetParameter(handle_, kPortTypes[i], &port);
+    if (error == OMX_ErrorNone && port.nPorts == 2) {
+      input_port_ = port.nStartPortNumber;
+      output_port_ = input_port_ + 1;
+      SendCommandAndWaitForCompletion(OMX_CommandPortDisable, input_port_);
+      SendCommandAndWaitForCompletion(OMX_CommandPortDisable, output_port_);
+      break;
+    }
+  }
+  SB_CHECK(input_port_ != kInvalidPort);
+  SB_CHECK(output_port_ != kInvalidPort);
+  SB_DLOG(INFO) << "Opened \"" << name << "\" with port " << input_port_
+                << " and " << output_port_;
+
+  SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle);
+}
+
+OpenMaxComponent::~OpenMaxComponent() {
+  if (!handle_) {
+    return;
+  }
+  SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle);
+
+  SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_);
+  SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_);
+
+  SendCommand(OMX_CommandPortDisable, input_port_);
+  for (size_t i = 0; i < input_buffers_.size(); ++i) {
+    OMX_ERRORTYPE error =
+        OMX_FreeBuffer(handle_, input_port_, input_buffers_[i]);
+    SB_DCHECK(error == OMX_ErrorNone);
+  }
+  WaitForCommandCompletion();
+
+  SendCommand(OMX_CommandPortDisable, output_port_);
+  for (size_t i = 0; i < output_buffers_.size(); ++i) {
+    OMX_ERRORTYPE error =
+        OMX_FreeBuffer(handle_, output_port_, output_buffers_[i]);
+    SB_DCHECK(error == OMX_ErrorNone);
+  }
+  output_buffers_.clear();
+  WaitForCommandCompletion();
+
+  SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateLoaded);
+  OMX_FreeHandle(handle_);
+}
+
+void OpenMaxComponent::Start() {
+  EnableInputPortAndAllocateBuffers();
+  SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateExecuting);
+}
+
+void OpenMaxComponent::Flush() {
+  SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_);
+  SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_);
+}
+
+void OpenMaxComponent::WriteData(const void* data,
+                                 size_t size,
+                                 SbTime timestamp) {
+  size_t offset = 0;
+
+  while (offset != size) {
+    OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer();
+
+    int size_to_append = std::min(size - offset, buffer_header->nAllocLen);
+    buffer_header->nOffset = 0;
+    buffer_header->nFilledLen = size_to_append;
+    buffer_header->nFlags = 0;
+
+    buffer_header->nTimeStamp.nLowPart = timestamp;
+    buffer_header->nTimeStamp.nHighPart = timestamp >> 32;
+
+    memcpy(buffer_header->pBuffer, (const char*)data + offset, size_to_append);
+    offset += size_to_append;
+
+    OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header);
+    SB_DCHECK(error == OMX_ErrorNone);
+  }
+}
+
+void OpenMaxComponent::WriteEOS() {
+  OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer();
+
+  buffer_header->nOffset = 0;
+  buffer_header->nFilledLen = 0;
+  buffer_header->nFlags = OMX_BUFFERFLAG_EOS;
+
+  OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header);
+  SB_DCHECK(error == OMX_ErrorNone);
+}
+
+OMX_BUFFERHEADERTYPE* OpenMaxComponent::PeekNextOutputBuffer() {
+  {
+    ScopedLock scoped_lock(mutex_);
+
+    if (!output_setting_changed_) {
+      return NULL;
+    }
+  }
+
+  if (!output_port_enabled_) {
+    EnableOutputPortAndAllocateBuffer();
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  return filled_output_buffers_.empty() ? NULL : filled_output_buffers_.front();
+}
+
+void OpenMaxComponent::DropNextOutputBuffer() {
+  OMX_BUFFERHEADERTYPE* buffer = NULL;
+  {
+    ScopedLock scoped_lock(mutex_);
+    SB_DCHECK(!filled_output_buffers_.empty());
+    buffer = filled_output_buffers_.front();
+    filled_output_buffers_.pop();
+  }
+  buffer->nFilledLen = 0;
+  OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, buffer);
+  SB_DCHECK(error == OMX_ErrorNone);
+}
+
+void OpenMaxComponent::SendCommand(OMX_COMMANDTYPE command, int param) {
+  OMX_ERRORTYPE error = OMX_SendCommand(handle_, command, param, NULL);
+  SB_DCHECK(error == OMX_ErrorNone);
+}
+
+void OpenMaxComponent::WaitForCommandCompletion() {
+  for (;;) {
+    ScopedLock scoped_lock(mutex_);
+    for (EventDescriptions::iterator iter = event_descriptions_.begin();
+         iter != event_descriptions_.end(); ++iter) {
+      if (iter->event == OMX_EventCmdComplete) {
+        event_descriptions_.erase(iter);
+        return;
+      }
+      // Special case for OMX_CommandStateSet.
+      if (iter->event == OMX_EventError && iter->data1 == OMX_ErrorSameState) {
+        event_descriptions_.erase(iter);
+        return;
+      }
+    }
+    condition_variable_.Wait();
+  }
+}
+
+void OpenMaxComponent::SendCommandAndWaitForCompletion(OMX_COMMANDTYPE command,
+                                                       int param) {
+  SendCommand(command, param);
+  WaitForCommandCompletion();
+}
+
+void OpenMaxComponent::EnableInputPortAndAllocateBuffers() {
+  SB_DCHECK(input_buffers_.empty());
+
+  OMXParamPortDefinition port_definition;
+  GetInputPortParam(&port_definition);
+  if (OnEnableInputPort(&port_definition)) {
+    SetPortParam(port_definition);
+  }
+
+  SendCommand(OMX_CommandPortEnable, input_port_);
+
+  for (int i = 0; i < port_definition.nBufferCountActual; ++i) {
+    OMX_BUFFERHEADERTYPE* buffer;
+    OMX_ERRORTYPE error = OMX_AllocateBuffer(handle_, &buffer, input_port_,
+                                             NULL, port_definition.nBufferSize);
+    SB_DCHECK(error == OMX_ErrorNone);
+    input_buffers_.push_back(buffer);
+    unused_input_buffers_.push(buffer);
+  }
+
+  WaitForCommandCompletion();
+}
+
+void OpenMaxComponent::EnableOutputPortAndAllocateBuffer() {
+  SB_DCHECK(!output_port_enabled_);
+
+  GetOutputPortParam(&output_port_definition_);
+  if (OnEnableOutputPort(&output_port_definition_)) {
+    SetPortParam(output_port_definition_);
+  }
+
+  SendCommand(OMX_CommandPortEnable, output_port_);
+
+  output_buffers_.reserve(output_port_definition_.nBufferCountActual);
+  for (int i = 0; i < output_port_definition_.nBufferCountActual; ++i) {
+    OMX_BUFFERHEADERTYPE* buffer;
+    OMX_ERRORTYPE error =
+        OMX_AllocateBuffer(handle_, &buffer, output_port_, NULL,
+                           output_port_definition_.nBufferSize);
+    SB_DCHECK(error == OMX_ErrorNone);
+    output_buffers_.push_back(buffer);
+  }
+
+  WaitForCommandCompletion();
+
+  output_port_enabled_ = true;
+
+  for (size_t i = 0; i < output_buffers_.size(); ++i) {
+    output_buffers_[i]->nFilledLen = 0;
+    OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, output_buffers_[i]);
+    SB_DCHECK(error == OMX_ErrorNone);
+  }
+}
+
+OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetUnusedInputBuffer() {
+  for (;;) {
+    {
+      ScopedLock scoped_lock(mutex_);
+      if (!unused_input_buffers_.empty()) {
+        OMX_BUFFERHEADERTYPE* buffer_header = unused_input_buffers_.front();
+        unused_input_buffers_.pop();
+        return buffer_header;
+      }
+    }
+    SbThreadSleep(kSbTimeMillisecond);
+  }
+  SB_NOTREACHED();
+  return NULL;
+}
+
+OMX_ERRORTYPE OpenMaxComponent::OnEvent(OMX_EVENTTYPE event,
+                                        OMX_U32 data1,
+                                        OMX_U32 data2,
+                                        OMX_PTR event_data) {
+  if (event == OMX_EventError && data1 != OMX_ErrorSameState) {
+    SB_NOTREACHED() << "OMX_EventError received with " << std::hex << data1
+                    << " " << data2;
+    return OMX_ErrorNone;
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  if (event == OMX_EventPortSettingsChanged && data1 == output_port_) {
+    output_setting_changed_ = true;
+    return OMX_ErrorNone;
+  }
+  EventDescription event_desc;
+  event_desc.event = event;
+  event_desc.data1 = data1;
+  event_desc.data2 = data2;
+  event_desc.event_data = event_data;
+  event_descriptions_.push_back(event_desc);
+  condition_variable_.Signal();
+
+  return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE OpenMaxComponent::OnEmptyBufferDone(
+    OMX_BUFFERHEADERTYPE* buffer) {
+  ScopedLock scoped_lock(mutex_);
+  unused_input_buffers_.push(buffer);
+}
+
+void OpenMaxComponent::OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer) {
+  ScopedLock scoped_lock(mutex_);
+  filled_output_buffers_.push(buffer);
+}
+
+// static
+OMX_ERRORTYPE OpenMaxComponent::EventHandler(OMX_HANDLETYPE handle,
+                                             OMX_PTR app_data,
+                                             OMX_EVENTTYPE event,
+                                             OMX_U32 data1,
+                                             OMX_U32 data2,
+                                             OMX_PTR event_data) {
+  SB_DCHECK(app_data != NULL);
+  OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data);
+  SB_DCHECK(handle == component->handle_);
+
+  return component->OnEvent(event, data1, data2, event_data);
+}
+
+// static
+OMX_ERRORTYPE OpenMaxComponent::EmptyBufferDone(OMX_HANDLETYPE handle,
+                                                OMX_PTR app_data,
+                                                OMX_BUFFERHEADERTYPE* buffer) {
+  SB_DCHECK(app_data != NULL);
+  OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data);
+  SB_DCHECK(handle == component->handle_);
+
+  return component->OnEmptyBufferDone(buffer);
+}
+
+// static
+OMX_ERRORTYPE OpenMaxComponent::FillBufferDone(OMX_HANDLETYPE handle,
+                                               OMX_PTR app_data,
+                                               OMX_BUFFERHEADERTYPE* buffer) {
+  SB_DCHECK(app_data != NULL);
+  OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data);
+  SB_DCHECK(handle == component->handle_);
+
+  component->OnFillBufferDone(buffer);
+
+  return OMX_ErrorNone;
+}
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/open_max_component.h b/src/starboard/raspi/shared/open_max/open_max_component.h
new file mode 100644
index 0000000..d9b5295
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/open_max_component.h
@@ -0,0 +1,161 @@
+// 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_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_
+#define STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_
+
+// OMX_SKIP64BIT is required for using VC GPU code.
+#define OMX_SKIP64BIT 1
+
+#include <IL/OMX_Broadcom.h>
+#include <interface/vcos/vcos.h>
+#include <interface/vcos/vcos_logging.h>
+#include <interface/vmcs_host/vchost.h>
+
+#include <queue>
+#include <vector>
+
+#include "starboard/condition_variable.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+template <typename ParamType, OMX_INDEXTYPE index>
+struct OMXParam : public ParamType {
+  static const OMX_INDEXTYPE Index = index;
+
+  OMXParam() : ParamType() {
+    ParamType::nSize = sizeof(ParamType);
+    ParamType::nVersion.nVersion = OMX_VERSION;
+  }
+};
+
+typedef OMXParam<OMX_PARAM_PORTDEFINITIONTYPE, OMX_IndexParamPortDefinition>
+    OMXParamPortDefinition;
+typedef OMXParam<OMX_VIDEO_PARAM_PORTFORMATTYPE, OMX_IndexParamVideoPortFormat>
+    OMXVideoParamPortFormat;
+
+class OpenMaxComponent {
+ public:
+  explicit OpenMaxComponent(const char* name);
+  virtual ~OpenMaxComponent();
+
+  void Start();
+  void Flush();
+
+  void WriteData(const void* data, size_t size, SbTime timestamp);
+  void WriteEOS();
+
+  OMX_BUFFERHEADERTYPE* PeekNextOutputBuffer();
+  void DropNextOutputBuffer();
+
+  template <typename ParamType>
+  void GetInputPortParam(ParamType* param) const {
+    param->nPortIndex = input_port_;
+    OMX_ERRORTYPE error = OMX_GetParameter(handle_, ParamType::Index, param);
+    SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_GetParameter("
+                                      << ParamType::Index
+                                      << ") failed with error " << error;
+  }
+
+  template <typename ParamType>
+  void GetOutputPortParam(ParamType* param) const {
+    param->nPortIndex = output_port_;
+    OMX_ERRORTYPE error = OMX_GetParameter(handle_, ParamType::Index, param);
+    SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_GetParameter("
+                                      << ParamType::Index
+                                      << ") failed with error " << error;
+  }
+
+  template <typename ParamType>
+  void SetPortParam(const ParamType& param) const {
+    OMX_ERRORTYPE error = OMX_SetParameter(handle_, ParamType::Index,
+                                           const_cast<ParamType*>(&param));
+    SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_SetParameter("
+                                      << ParamType::Index
+                                      << ") failed with error " << error;
+  }
+
+ private:
+  struct EventDescription {
+    OMX_EVENTTYPE event;
+    OMX_U32 data1;
+    OMX_U32 data2;
+    OMX_PTR event_data;
+  };
+
+  typedef std::vector<EventDescription> EventDescriptions;
+
+  virtual bool OnEnableInputPort(OMXParamPortDefinition* port_definition) {
+    return false;
+  }
+  virtual bool OnEnableOutputPort(OMXParamPortDefinition* port_definition) {
+    return false;
+  }
+
+  void SendCommand(OMX_COMMANDTYPE command, int param);
+  void WaitForCommandCompletion();
+  void SendCommandAndWaitForCompletion(OMX_COMMANDTYPE command, int param);
+  void EnableInputPortAndAllocateBuffers();
+  void EnableOutputPortAndAllocateBuffer();
+  OMX_BUFFERHEADERTYPE* GetUnusedInputBuffer();
+
+  OMX_ERRORTYPE OnEvent(OMX_EVENTTYPE event,
+                        OMX_U32 data1,
+                        OMX_U32 data2,
+                        OMX_PTR event_data);
+  OMX_ERRORTYPE OnEmptyBufferDone(OMX_BUFFERHEADERTYPE* buffer);
+  void OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer);
+
+  static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE handle,
+                                    OMX_PTR app_data,
+                                    OMX_EVENTTYPE event,
+                                    OMX_U32 data1,
+                                    OMX_U32 data2,
+                                    OMX_PTR event_data);
+  static OMX_ERRORTYPE EmptyBufferDone(OMX_HANDLETYPE handle,
+                                       OMX_PTR app_data,
+                                       OMX_BUFFERHEADERTYPE* buffer);
+  static OMX_ERRORTYPE FillBufferDone(OMX_HANDLETYPE handle,
+                                      OMX_PTR app_data,
+                                      OMX_BUFFERHEADERTYPE* buffer);
+
+  Mutex mutex_;
+  ConditionVariable condition_variable_;
+  OMX_HANDLETYPE handle_;
+  int input_port_;
+  int output_port_;
+  bool output_setting_changed_;
+  EventDescriptions event_descriptions_;
+  std::vector<OMX_BUFFERHEADERTYPE*> input_buffers_;
+  std::queue<OMX_BUFFERHEADERTYPE*> unused_input_buffers_;
+  std::vector<OMX_BUFFERHEADERTYPE*> output_buffers_;
+  std::queue<OMX_BUFFERHEADERTYPE*> filled_output_buffers_;
+
+  OMXParamPortDefinition output_port_definition_;
+  bool output_port_enabled_;
+};
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
+
+#endif  // STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_
diff --git a/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc
new file mode 100644
index 0000000..0520c36
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc
@@ -0,0 +1,165 @@
+// 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/raspi/shared/open_max/open_max_video_decode_component.h"
+
+#include <algorithm>
+
+#include "starboard/configuration.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+namespace {
+
+const char kVideoDecodeComponentName[] = "OMX.broadcom.video_decode";
+const size_t kResourcePoolSize = 12;
+const size_t kOMXOutputBufferCount = 4;
+const int kMaxFrameWidth = 1920;
+const int kMaxFrameHeight = 1088;
+const size_t kMaxVideoFrameSize = kMaxFrameWidth * kMaxFrameHeight * 3 / 2;
+
+}  // namespace
+
+typedef OpenMaxVideoDecodeComponent::VideoFrame VideoFrame;
+
+VideoFrameResourcePool::VideoFrameResourcePool(size_t max_number_of_resources)
+    : max_number_of_resources_(max_number_of_resources),
+      number_of_resources_(0),
+      last_frame_height_(0) {}
+
+VideoFrameResourcePool::~VideoFrameResourcePool() {
+  for (ResourceMap::iterator iter = resource_map_.begin();
+       iter != resource_map_.end(); ++iter) {
+    while (!iter->second.empty()) {
+      delete iter->second.front();
+      iter->second.pop();
+      --number_of_resources_;
+    }
+  }
+  SB_DCHECK(number_of_resources_ == 0) << number_of_resources_;
+}
+
+DispmanxYUV420Resource* VideoFrameResourcePool::Alloc(int width,
+                                                      int height,
+                                                      int visible_width,
+                                                      int visible_height) {
+  ScopedLock scoped_lock(mutex_);
+
+  last_frame_height_ = height;
+
+  ResourceMap::iterator iter = resource_map_.find(height);
+  if (iter != resource_map_.end() && !iter->second.empty()) {
+    DispmanxYUV420Resource* resource = iter->second.front();
+    iter->second.pop();
+    return resource;
+  }
+
+  if (number_of_resources_ >= max_number_of_resources_) {
+    return NULL;
+  }
+
+  ++number_of_resources_;
+  return new DispmanxYUV420Resource(width, height, visible_width,
+                                    visible_height);
+}
+
+void VideoFrameResourcePool::Free(DispmanxYUV420Resource* resource) {
+  ScopedLock scoped_lock(mutex_);
+  if (resource->height() != last_frame_height_) {
+    // The video has adapted, free the resource as it won't be reused any soon.
+    delete resource;
+    --number_of_resources_;
+    return;
+  }
+  resource_map_[resource->height()].push(resource);
+}
+
+// static
+void VideoFrameResourcePool::DisposeDispmanxYUV420Resource(
+    void* context,
+    void* dispmanx_resource) {
+  SB_DCHECK(context != NULL);
+  SB_DCHECK(dispmanx_resource != NULL);
+  VideoFrameResourcePool* pool =
+      reinterpret_cast<VideoFrameResourcePool*>(context);
+  pool->Free(reinterpret_cast<DispmanxYUV420Resource*>(dispmanx_resource));
+  pool->Release();
+}
+
+OpenMaxVideoDecodeComponent::OpenMaxVideoDecodeComponent()
+    : OpenMaxComponent(kVideoDecodeComponentName),
+      resource_pool_(new VideoFrameResourcePool(kResourcePoolSize)) {}
+
+scoped_refptr<VideoFrame> OpenMaxVideoDecodeComponent::ReadVideoFrame() {
+  if (OMX_BUFFERHEADERTYPE* buffer = PeekNextOutputBuffer()) {
+    if (scoped_refptr<VideoFrame> frame = CreateVideoFrame(buffer)) {
+      DropNextOutputBuffer();
+      return frame;
+    }
+  }
+  return NULL;
+}
+
+scoped_refptr<VideoFrame> OpenMaxVideoDecodeComponent::CreateVideoFrame(
+    OMX_BUFFERHEADERTYPE* buffer) {
+  scoped_refptr<VideoFrame> frame;
+  if (buffer->nFlags & OMX_BUFFERFLAG_EOS) {
+    frame = VideoFrame::CreateEOSFrame();
+  } else {
+    OMX_VIDEO_PORTDEFINITIONTYPE& video_definition =
+        output_port_definition_.format.video;
+    DispmanxYUV420Resource* resource = resource_pool_->Alloc(
+        video_definition.nStride, video_definition.nSliceHeight,
+        video_definition.nFrameWidth, video_definition.nFrameHeight);
+    if (!resource) {
+      return NULL;
+    }
+
+    resource->WriteData(buffer->pBuffer);
+
+    SbMediaTime timestamp = ((buffer->nTimeStamp.nHighPart * 0x100000000ull) +
+                             buffer->nTimeStamp.nLowPart) *
+                            kSbMediaTimeSecond / kSbTimeSecond;
+
+    resource_pool_->AddRef();
+    frame = new VideoFrame(
+        video_definition.nFrameWidth, video_definition.nFrameHeight, timestamp,
+        resource, resource_pool_,
+        &VideoFrameResourcePool::DisposeDispmanxYUV420Resource);
+  }
+  return frame;
+}
+
+bool OpenMaxVideoDecodeComponent::OnEnableOutputPort(
+    OMXParamPortDefinition* port_definition) {
+  SB_DCHECK(port_definition);
+
+  output_port_definition_ = *port_definition;
+  SB_DCHECK(port_definition->format.video.eColorFormat ==
+            OMX_COLOR_FormatYUV420PackedPlanar);
+  port_definition->format.video.eColorFormat =
+      OMX_COLOR_FormatYUV420PackedPlanar;
+  port_definition->nBufferCountActual = kOMXOutputBufferCount;
+  port_definition->nBufferSize =
+      std::max(port_definition->nBufferSize, kMaxVideoFrameSize);
+  return true;
+}
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/open_max_video_decode_component.h b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.h
new file mode 100644
index 0000000..94b9cda
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.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 STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_
+#define STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_
+
+#include <map>
+#include <queue>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/raspi/shared/dispmanx_util.h"
+#include "starboard/raspi/shared/open_max/open_max_component.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+class VideoFrameResourcePool
+    : public RefCountedThreadSafe<VideoFrameResourcePool> {
+ public:
+  explicit VideoFrameResourcePool(size_t max_number_of_resources);
+  ~VideoFrameResourcePool();
+
+  DispmanxYUV420Resource* Alloc(int width,
+                                int height,
+                                int visible_width,
+                                int visible_height);
+  void Free(DispmanxYUV420Resource* resource);
+
+  static void DisposeDispmanxYUV420Resource(void* context,
+                                            void* dispmanx_resource);
+
+ private:
+  typedef std::queue<DispmanxYUV420Resource*> ResourceQueue;
+  // Map frame height to resource handles.
+  typedef std::map<int, ResourceQueue> ResourceMap;
+
+  const size_t max_number_of_resources_;
+
+  Mutex mutex_;
+  size_t number_of_resources_;
+  int last_frame_height_;
+  ResourceMap resource_map_;
+};
+
+// Encapsulate a "OMX.broadcom.video_decode" component.  Note that member
+// functions of this class is expected to be called from ANY threads as this
+// class works with the VideoDecoder filter, the OpenMAX component, and also
+// manages the disposition of Dispmanx resource.
+class OpenMaxVideoDecodeComponent : public OpenMaxComponent {
+ public:
+  typedef starboard::shared::starboard::player::VideoFrame VideoFrame;
+
+  OpenMaxVideoDecodeComponent();
+
+  scoped_refptr<VideoFrame> ReadVideoFrame();
+
+ private:
+  scoped_refptr<VideoFrame> CreateVideoFrame(OMX_BUFFERHEADERTYPE* buffer);
+
+  bool OnEnableOutputPort(OMXParamPortDefinition* port_definition) SB_OVERRIDE;
+
+  scoped_refptr<VideoFrameResourcePool> resource_pool_;
+  OMXParamPortDefinition output_port_definition_;
+};
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
+
+#endif  // STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_
diff --git a/src/starboard/raspi/shared/open_max/video_decoder.cc b/src/starboard/raspi/shared/open_max/video_decoder.cc
new file mode 100644
index 0000000..a238db7
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/video_decoder.cc
@@ -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.
+
+#include "starboard/raspi/shared/open_max/video_decoder.h"
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+using starboard::shared::starboard::player::VideoFrame;
+
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec)
+    : host_(NULL), stream_ended_(false) {
+  SB_DCHECK(video_codec == kSbMediaVideoCodecH264);
+
+  OMXVideoParamPortFormat port_format;
+  component_.GetInputPortParam(&port_format);
+  port_format.eCompressionFormat = OMX_VIDEO_CodingAVC;
+  component_.SetPortParam(port_format);
+
+  component_.Start();
+}
+
+VideoDecoder::~VideoDecoder() {}
+
+void VideoDecoder::SetHost(Host* host) {
+  SB_DCHECK(host != NULL);
+  SB_DCHECK(host_ == NULL);
+  host_ = host;
+}
+
+void VideoDecoder::WriteInputBuffer(const InputBuffer& input_buffer) {
+  SB_DCHECK(host_ != NULL);
+
+  if (stream_ended_) {
+    SB_LOG(ERROR) << "WriteInputFrame() was called after WriteEndOfStream().";
+    return;
+  }
+  component_.WriteData(input_buffer.data(), input_buffer.size(),
+                       input_buffer.pts() * kSbTimeSecond / kSbMediaTimeSecond);
+  if (scoped_refptr<VideoFrame> frame = component_.ReadVideoFrame()) {
+    host_->OnDecoderStatusUpdate(kNeedMoreInput, frame);
+  } else {
+    // Call the callback with NULL frame to ensure that the host know that more
+    // data is expected.
+    host_->OnDecoderStatusUpdate(kNeedMoreInput, NULL);
+  }
+}
+
+void VideoDecoder::WriteEndOfStream() {
+  SB_DCHECK(!stream_ended_);
+  stream_ended_ = true;
+  component_.WriteEOS();
+}
+
+void VideoDecoder::Reset() {
+  stream_ended_ = false;
+  component_.Flush();
+}
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// static
+VideoDecoder* VideoDecoder::Create(SbMediaVideoCodec video_codec) {
+  return new raspi::shared::open_max::VideoDecoder(video_codec);
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/video_decoder.h b/src/starboard/raspi/shared/open_max/video_decoder.h
new file mode 100644
index 0000000..de684af
--- /dev/null
+++ b/src/starboard/raspi/shared/open_max/video_decoder.h
@@ -0,0 +1,56 @@
+// 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_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_
+#define STARBOARD_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_
+
+#include "starboard/media.h"
+#include "starboard/raspi/shared/open_max/open_max_video_decode_component.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+
+namespace starboard {
+namespace raspi {
+namespace shared {
+namespace open_max {
+
+class VideoDecoder
+    : public starboard::shared::starboard::player::filter::VideoDecoder {
+ public:
+  typedef starboard::shared::starboard::player::InputBuffer InputBuffer;
+
+  explicit VideoDecoder(SbMediaVideoCodec video_codec);
+  ~VideoDecoder() SB_OVERRIDE;
+
+  void SetHost(Host* host) SB_OVERRIDE;
+  void WriteInputBuffer(const InputBuffer& input_buffer) SB_OVERRIDE;
+  void WriteEndOfStream() SB_OVERRIDE;
+  void Reset() SB_OVERRIDE;
+
+ private:
+  OpenMaxVideoDecodeComponent component_;
+
+  // These variables will be initialized inside ctor or SetHost() and will not
+  // be changed during the life time of this class.
+  Host* host_;
+
+  bool stream_ended_;
+};
+
+}  // namespace open_max
+}  // namespace shared
+}  // namespace raspi
+}  // namespace starboard
+
+#endif  // STARBOARD_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_
diff --git a/src/starboard/raspi/shared/thread_create_priority.cc b/src/starboard/raspi/shared/thread_create_priority.cc
new file mode 100644
index 0000000..b36cd1f
--- /dev/null
+++ b/src/starboard/raspi/shared/thread_create_priority.cc
@@ -0,0 +1,71 @@
+// 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/pthread/thread_create_priority.h"
+
+#include <sched.h>
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace shared {
+namespace pthread {
+
+#if SB_HAS(THREAD_PRIORITY_SUPPORT)
+void ThreadSetPriority(SbThreadPriority priority) {
+  // Note that use of sched_setscheduler() has been found to be more reliably
+  // supported than pthread_setschedparam(), so we are using that.
+
+  // Use different schedulers according to priority. This is preferred over
+  // using SCHED_RR for all threads because the scheduler time slice is too
+  // high (defaults to 100ms) for the desired threading behavior.
+  struct sched_param thread_sched_param;
+  int result = 0;
+
+  thread_sched_param.sched_priority = 0;
+  switch (priority) {
+    case kSbThreadPriorityLowest:
+    case kSbThreadPriorityLow:
+      result = sched_setscheduler(0, SCHED_IDLE, &thread_sched_param);
+      break;
+    case kSbThreadNoPriority:
+    case kSbThreadPriorityNormal:
+      result = sched_setscheduler(0, SCHED_OTHER, &thread_sched_param);
+      break;
+    case kSbThreadPriorityHigh:
+      thread_sched_param.sched_priority = 1;
+      result = sched_setscheduler(0, SCHED_RR, &thread_sched_param);
+      break;
+    case kSbThreadPriorityHighest:
+      thread_sched_param.sched_priority = 2;
+      result = sched_setscheduler(0, SCHED_RR, &thread_sched_param);
+      break;
+    case kSbThreadPriorityRealTime:
+      thread_sched_param.sched_priority = 3;
+      result = sched_setscheduler(0, SCHED_RR, &thread_sched_param);
+      break;
+    default:
+      SB_NOTREACHED();
+      break;
+  }
+
+  if (result != 0) {
+    SB_NOTREACHED();
+  }
+}
+#endif  // SB_HAS(THREAD_PRIORITY_SUPPORT)
+
+}  // namespace pthread
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/window_internal.cc b/src/starboard/raspi/shared/window_internal.cc
index 532ecd4..cb03c2d 100644
--- a/src/starboard/raspi/shared/window_internal.cc
+++ b/src/starboard/raspi/shared/window_internal.cc
@@ -14,21 +14,19 @@
 
 #include "starboard/raspi/shared/window_internal.h"
 
-#include <bcm_host.h>
-
 #include "starboard/log.h"
 
 namespace {
 const int32_t kLayer = 0;
-const DISPMANX_RESOURCE_HANDLE_T kResource = DISPMANX_NO_HANDLE;
 }  // namespace
 
-SbWindowPrivate::SbWindowPrivate(DISPMANX_DISPLAY_HANDLE_T display,
-                                 const SbWindowOptions* options)
-    : display(display), element(DISPMANX_NO_HANDLE) {
-  VC_RECT_T destination_rect;
-  VC_RECT_T source_rect;
+using starboard::raspi::shared::DispmanxDisplay;
+using starboard::raspi::shared::DispmanxElement;
+using starboard::raspi::shared::DispmanxRect;
+using starboard::raspi::shared::DispmanxResource;
 
+SbWindowPrivate::SbWindowPrivate(const DispmanxDisplay& display,
+                                 const SbWindowOptions* options) {
   uint32_t window_width = 0;
   uint32_t window_height = 0;
   if (options && options->size.width > 0 && options->size.height > 0) {
@@ -41,41 +39,23 @@
     SB_DCHECK(result >= 0);
   }
 
-  destination_rect.x = 0;
-  destination_rect.y = 0;
-  destination_rect.width = window_width;
-  destination_rect.height = window_height;
-
-  source_rect.x = 0;
-  source_rect.y = 0;
-  // This shift is part of the examples, but unexplained. It appears to work.
-  source_rect.width = window_width << 16;
-  source_rect.height = window_height << 16;
-
+  DispmanxRect destination_rect(0, 0, window_width, window_height);
+  // The "<< 16"s are part of the examples, but unexplained. It appears to work.
+  DispmanxRect source_rect(0, 0, window_width << 16, window_height << 16);
+  // The window doesn't have an image resource associated with it.
+  DispmanxResource resource;
   // Creating a window (called an "element" here, created by adding it to the
   // display) must happen within an "update", which seems to represent a sort of
   // window manager transaction.
-  DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0 /*screen*/);
-  SB_DCHECK(update != DISPMANX_NO_HANDLE);
-  element = vc_dispmanx_element_add(update, display, kLayer, &destination_rect,
-                                    kResource, &source_rect,
-                                    DISPMANX_PROTECTION_NONE, NULL /*alpha*/,
-                                    NULL /*clamp*/, DISPMANX_NO_ROTATE);
-  SB_DCHECK(element != DISPMANX_NO_HANDLE);
-  int32_t result = vc_dispmanx_update_submit_sync(update);
-  SB_DCHECK(result == 0) << " result=" << result;
-
+  element.reset(new DispmanxElement(display, kLayer, destination_rect, resource,
+                                    source_rect));
   // We can then populate this struct, a pointer to which is what EGL expects as
   // a "native window" handle.
-  window.element = element;
+  window.element = element->handle();
   window.width = window_width;
   window.height = window_height;
 }
 
 SbWindowPrivate::~SbWindowPrivate() {
-  DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0 /*screen*/);
-  int32_t result = vc_dispmanx_element_remove(update, element);
-  SB_DCHECK(result == 0) << " result=" << result;
-  vc_dispmanx_update_submit_sync(update);
-  element = DISPMANX_NO_HANDLE;
+  element.reset();
 }
diff --git a/src/starboard/raspi/shared/window_internal.h b/src/starboard/raspi/shared/window_internal.h
index 06a77d5..3f51a8a 100644
--- a/src/starboard/raspi/shared/window_internal.h
+++ b/src/starboard/raspi/shared/window_internal.h
@@ -15,19 +15,19 @@
 #ifndef STARBOARD_RASPI_SHARED_WINDOW_INTERNAL_H_
 #define STARBOARD_RASPI_SHARED_WINDOW_INTERNAL_H_
 
-#include <bcm_host.h>
 #include <EGL/egl.h>
 
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/raspi/shared/dispmanx_util.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/window.h"
 
 struct SbWindowPrivate {
-  SbWindowPrivate(DISPMANX_DISPLAY_HANDLE_T display,
+  SbWindowPrivate(const starboard::raspi::shared::DispmanxDisplay& display,
                   const SbWindowOptions* options);
   ~SbWindowPrivate();
 
-  DISPMANX_DISPLAY_HANDLE_T display;
-  DISPMANX_ELEMENT_HANDLE_T element;
+  starboard::scoped_ptr<starboard::raspi::shared::DispmanxElement> element;
   EGL_DISPMANX_WINDOW_T window;
 };
 
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/directfb/blitter_destroy_swap_chain.cc b/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc
index ba9d317..2f9505b 100644
--- a/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc
+++ b/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc
@@ -26,5 +26,7 @@
 
   swap_chain->render_target.surface->Release(swap_chain->render_target.surface);
 
+  delete swap_chain;
+
   return true;
 }
diff --git a/src/starboard/shared/dlmalloc/memory_map.cc b/src/starboard/shared/dlmalloc/memory_map.cc
index 59730fc..c5d3829 100644
--- a/src/starboard/shared/dlmalloc/memory_map.cc
+++ b/src/starboard/shared/dlmalloc/memory_map.cc
@@ -13,9 +13,11 @@
 // limitations under the License.
 
 #include "starboard/memory.h"
-
+#include "starboard/shared/starboard/memory_reporter_internal.h"
 #include "starboard/shared/dlmalloc/page_internal.h"
 
 void* SbMemoryMap(int64_t size_bytes, int flags, const char* name) {
-  return SbPageMap(size_bytes, flags, name);
+  void* memory = SbPageMap(size_bytes, flags, name);
+  SbMemoryReporterReportMappedMemory(memory, size_bytes);
+  return memory;
 }
diff --git a/src/starboard/shared/dlmalloc/memory_unmap.cc b/src/starboard/shared/dlmalloc/memory_unmap.cc
index ff45918..dc32164 100644
--- a/src/starboard/shared/dlmalloc/memory_unmap.cc
+++ b/src/starboard/shared/dlmalloc/memory_unmap.cc
@@ -13,9 +13,10 @@
 // limitations under the License.
 
 #include "starboard/memory.h"
-
+#include "starboard/shared/starboard/memory_reporter_internal.h"
 #include "starboard/shared/dlmalloc/page_internal.h"
 
 bool SbMemoryUnmap(void* virtual_address, int64_t size_bytes) {
+  SbMemoryReporterReportUnmappedMemory(virtual_address, size_bytes);
   return SbPageUnmap(virtual_address, size_bytes);
 }
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
index 00e98e6..1444180 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/shared/ffmpeg/ffmpeg_audio_decoder.h"
 
+#include "starboard/audio_sink.h"
 #include "starboard/log.h"
 
 namespace starboard {
@@ -22,15 +23,23 @@
 
 namespace {
 
-// The required output format for the decoder is interleaved float.  However
-// the output of the ffmpeg decoder can be in other formats.  So libavresample
-// is used to convert the output into the required format.
-void ConvertToInterleavedFloat(int source_sample_format,
-                               int channel_layout,
-                               int sample_rate,
-                               int samples_per_channel,
-                               uint8_t** input_buffer,
-                               uint8_t* output_buffer) {
+SbMediaAudioSampleType GetSupportedSampleType() {
+  if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) {
+    return kSbMediaAudioSampleTypeFloat32;
+  }
+  return kSbMediaAudioSampleTypeInt16;
+}
+
+// The required output format and the output of the ffmpeg decoder can be
+// different.  In this case libavresample is used to convert the ffmpeg output
+// into the required format.
+void ConvertSamples(int source_sample_format,
+                    int target_sample_format,
+                    int channel_layout,
+                    int sample_rate,
+                    int samples_per_channel,
+                    uint8_t** input_buffer,
+                    uint8_t* output_buffer) {
   AVAudioResampleContext* context = avresample_alloc_context();
   SB_DCHECK(context != NULL);
 
@@ -39,7 +48,7 @@
   av_opt_set_int(context, "in_sample_rate", sample_rate, 0);
   av_opt_set_int(context, "out_sample_rate", sample_rate, 0);
   av_opt_set_int(context, "in_sample_fmt", source_sample_format, 0);
-  av_opt_set_int(context, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
+  av_opt_set_int(context, "out_sample_fmt", target_sample_format, 0);
   av_opt_set_int(context, "internal_sample_fmt", source_sample_format, 0);
 
   int result = avresample_open(context);
@@ -58,7 +67,8 @@
 
 AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
                            const SbMediaAudioHeader& audio_header)
-    : codec_context_(NULL),
+    : sample_type_(GetSupportedSampleType()),
+      codec_context_(NULL),
       av_frame_(NULL),
       stream_ended_(false),
       audio_header_(audio_header) {
@@ -72,7 +82,7 @@
 }
 
 void AudioDecoder::Decode(const InputBuffer& input_buffer,
-                          std::vector<float>* output) {
+                          std::vector<uint8_t>* output) {
   SB_CHECK(output != NULL);
   SB_CHECK(codec_context_ != NULL);
 
@@ -105,11 +115,16 @@
   audio_header_.samples_per_second = codec_context_->sample_rate;
 
   if (decoded_audio_size > 0) {
-    output->resize(decoded_audio_size / sizeof(float));
-    ConvertToInterleavedFloat(
-        codec_context_->sample_fmt, codec_context_->channel_layout,
-        audio_header_.samples_per_second, av_frame_->nb_samples,
-        av_frame_->extended_data, reinterpret_cast<uint8_t*>(&(*output)[0]));
+    output->resize(codec_context_->channels * av_frame_->nb_samples *
+                   (sample_type_ == kSbMediaAudioSampleTypeInt16 ? 2 : 4));
+    if (codec_context_->sample_fmt == codec_context_->request_sample_fmt) {
+      memcpy(&(*output)[0], av_frame_->extended_data, output->size());
+    } else {
+      ConvertSamples(
+          codec_context_->sample_fmt, codec_context_->request_sample_fmt,
+          codec_context_->channel_layout, audio_header_.samples_per_second,
+          av_frame_->nb_samples, av_frame_->extended_data, &(*output)[0]);
+    }
   } else {
     // TODO: Consider fill it with silence.
     SB_LOG(ERROR) << "Decoded audio frame is empty.";
@@ -127,7 +142,11 @@
   stream_ended_ = false;
 }
 
-int AudioDecoder::GetSamplesPerSecond() {
+SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
+  return sample_type_;
+}
+
+int AudioDecoder::GetSamplesPerSecond() const {
   return audio_header_.samples_per_second;
 }
 
@@ -144,7 +163,11 @@
   codec_context_->codec_type = AVMEDIA_TYPE_AUDIO;
   codec_context_->codec_id = AV_CODEC_ID_AAC;
   // Request_sample_fmt is set by us, but sample_fmt is set by the decoder.
-  codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT;  // interleaved float
+  if (sample_type_ == kSbMediaAudioSampleTypeInt16) {
+    codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16;
+  } else {
+    codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT;
+  }
 
   codec_context_->channels = audio_header_.number_of_channels;
   codec_context_->sample_rate = audio_header_.samples_per_second;
@@ -190,6 +213,7 @@
 
 namespace starboard {
 namespace player {
+namespace filter {
 
 // static
 AudioDecoder* AudioDecoder::Create(SbMediaAudioCodec audio_codec,
@@ -203,6 +227,7 @@
   return NULL;
 }
 
+}  // namespace filter
 }  // namespace player
 }  // namespace starboard
 
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
index eac65a2..c2a78db 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
@@ -20,13 +20,13 @@
 #include "starboard/media.h"
 #include "starboard/shared/ffmpeg/ffmpeg_common.h"
 #include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
 
 namespace starboard {
 namespace shared {
 namespace ffmpeg {
 
-class AudioDecoder : public starboard::player::AudioDecoder {
+class AudioDecoder : public starboard::player::filter::AudioDecoder {
  public:
   typedef starboard::player::InputBuffer InputBuffer;
 
@@ -35,10 +35,11 @@
   ~AudioDecoder() SB_OVERRIDE;
 
   void Decode(const InputBuffer& input_buffer,
-              std::vector<float>* output) SB_OVERRIDE;
+              std::vector<uint8_t>* output) SB_OVERRIDE;
   void WriteEndOfStream() SB_OVERRIDE;
   void Reset() SB_OVERRIDE;
-  int GetSamplesPerSecond() SB_OVERRIDE;
+  SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE;
+  int GetSamplesPerSecond() const SB_OVERRIDE;
 
   bool is_valid() const { return codec_context_ != NULL; }
 
@@ -46,6 +47,7 @@
   void InitializeCodec();
   void TeardownCodec();
 
+  SbMediaAudioSampleType sample_type_;
   AVCodecContext* codec_context_;
   AVFrame* av_frame_;
 
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 6c7aec8..2e05ee8 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -78,11 +78,13 @@
   frame->height = codec_context->height;
   frame->format = codec_context->pix_fmt;
 
+  frame->reordered_opaque = codec_context->reordered_opaque;
+
   return 0;
 }
 
 void ReleaseBuffer(AVCodecContext*, AVFrame* frame) {
-  SbMemoryFree(frame->opaque);
+  SbMemoryDeallocate(frame->opaque);
   frame->opaque = NULL;
 
   // The FFmpeg API expects us to zero the data pointers in this callback.
@@ -142,14 +144,16 @@
 }
 
 void VideoDecoder::Reset() {
-  SB_DCHECK(host_ != NULL);
-
   // Join the thread to ensure that all callbacks in process are finished.
   if (SbThreadIsValid(decoder_thread_)) {
     queue_.Put(Event(kReset));
     SbThreadJoin(decoder_thread_, NULL);
   }
 
+  if (codec_context_ != NULL) {
+    avcodec_flush_buffers(codec_context_);
+  }
+
   decoder_thread_ = kSbThreadInvalid;
   stream_ended_ = false;
 }
@@ -178,6 +182,7 @@
       packet.data = const_cast<uint8_t*>(event.input_buffer.data());
       packet.size = event.input_buffer.size();
       packet.pts = event.input_buffer.pts();
+      codec_context_->reordered_opaque = packet.pts;
 
       DecodePacket(&packet);
       host_->OnDecoderStatusUpdate(kNeedMoreInput, NULL);
@@ -192,8 +197,7 @@
         packet.pts = 0;
       } while (DecodePacket(&packet));
 
-      VideoFrame frame = VideoFrame::CreateEOSFrame();
-      host_->OnDecoderStatusUpdate(kBufferFull, &frame);
+      host_->OnDecoderStatusUpdate(kBufferFull, VideoFrame::CreateEOSFrame());
     }
   }
 }
@@ -218,10 +222,11 @@
 
   int pitch = AlignUp(av_frame_->width, kAlignment * 2);
 
-  VideoFrame frame = VideoFrame::CreateYV12Frame(
-      av_frame_->width, av_frame_->height, pitch, av_frame_->pkt_pts,
-      av_frame_->data[0], av_frame_->data[1], av_frame_->data[2]);
-  host_->OnDecoderStatusUpdate(kBufferFull, &frame);
+  scoped_refptr<VideoFrame> frame = VideoFrame::CreateYV12Frame(
+      av_frame_->width, av_frame_->height, pitch,
+      codec_context_->reordered_opaque, av_frame_->data[0], av_frame_->data[1],
+      av_frame_->data[2]);
+  host_->OnDecoderStatusUpdate(kBufferFull, frame);
   return true;
 }
 
@@ -290,6 +295,7 @@
 
 namespace starboard {
 namespace player {
+namespace filter {
 
 // static
 VideoDecoder* VideoDecoder::Create(SbMediaVideoCodec video_codec) {
@@ -301,6 +307,7 @@
   return NULL;
 }
 
+}  // namespace filter
 }  // namespace player
 }  // namespace starboard
 
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
index 6bcd6d3..6fcc2f1 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
@@ -20,8 +20,8 @@
 #include "starboard/queue.h"
 #include "starboard/shared/ffmpeg/ffmpeg_common.h"
 #include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
 #include "starboard/shared/starboard/player/input_buffer_internal.h"
-#include "starboard/shared/starboard/player/video_decoder_internal.h"
 #include "starboard/shared/starboard/player/video_frame_internal.h"
 #include "starboard/thread.h"
 
@@ -29,12 +29,12 @@
 namespace shared {
 namespace ffmpeg {
 
-class VideoDecoder : public starboard::player::VideoDecoder {
+class VideoDecoder : public starboard::player::filter::VideoDecoder {
  public:
   typedef starboard::player::InputBuffer InputBuffer;
   typedef starboard::player::VideoFrame VideoFrame;
 
-  explicit VideoDecoder(SbMediaVideoCodec);
+  explicit VideoDecoder(SbMediaVideoCodec video_codec);
   ~VideoDecoder() SB_OVERRIDE;
 
   void SetHost(Host* host) SB_OVERRIDE;
diff --git a/src/starboard/shared/gcc/atomic_gcc_public.h b/src/starboard/shared/gcc/atomic_gcc_public.h
index 655d2be..9c7afc6 100644
--- a/src/starboard/shared/gcc/atomic_gcc_public.h
+++ b/src/starboard/shared/gcc/atomic_gcc_public.h
@@ -24,7 +24,7 @@
 extern "C" {
 #endif
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr,
                                  SbAtomic32 old_value,
                                  SbAtomic32 new_value) {
@@ -37,7 +37,7 @@
   return prev_value;
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, SbAtomic32 new_value) {
   SbAtomic32 old_value;
   do {
@@ -46,13 +46,14 @@
   return old_value;
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, SbAtomic32 increment) {
   return SbAtomicBarrier_Increment(ptr, increment);
 }
 
-SB_C_FORCE_INLINE SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
-                                                       SbAtomic32 increment) {
+static SB_C_FORCE_INLINE SbAtomic32
+SbAtomicBarrier_Increment(volatile SbAtomic32* ptr,
+                          SbAtomic32 increment) {
   for (;;) {
     // Atomic exchange the old value with an incremented one.
     SbAtomic32 old_value = *ptr;
@@ -65,7 +66,7 @@
   }
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr,
                                SbAtomic32 old_value,
                                SbAtomic32 new_value) {
@@ -74,47 +75,47 @@
   return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr,
                                SbAtomic32 old_value,
                                SbAtomic32 new_value) {
   return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value);
 }
 
-SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() {
+static SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() {
   __sync_synchronize();
 }
 
-SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr,
-                                               SbAtomic32 value) {
+static SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr,
+                                                      SbAtomic32 value) {
   *ptr = value;
 }
 
-SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr,
-                                             SbAtomic32 value) {
+static SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr,
+                                                    SbAtomic32 value) {
   *ptr = value;
   SbAtomicMemoryBarrier();
 }
 
-SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr,
-                                             SbAtomic32 value) {
+static SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr,
+                                                    SbAtomic32 value) {
   SbAtomicMemoryBarrier();
   *ptr = value;
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr) {
   return *ptr;
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr) {
   SbAtomic32 value = *ptr;
   SbAtomicMemoryBarrier();
   return value;
 }
 
-SB_C_FORCE_INLINE SbAtomic32
+static SB_C_FORCE_INLINE SbAtomic32
 SbAtomicRelease_Load(volatile const SbAtomic32* ptr) {
   SbAtomicMemoryBarrier();
   return *ptr;
@@ -122,7 +123,7 @@
 
 // 64-bit atomic operations (only available on 64-bit processors).
 #if SB_HAS(64_BIT_ATOMICS)
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr,
                                    SbAtomic64 old_value,
                                    SbAtomic64 new_value) {
@@ -135,7 +136,7 @@
   return prev_value;
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, SbAtomic64 new_value) {
   SbAtomic64 old_value;
   do {
@@ -144,12 +145,12 @@
   return old_value;
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
   return SbAtomicBarrier_Increment64(ptr, increment);
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) {
   for (;;) {
     // Atomic exchange the old value with an incremented one.
@@ -163,50 +164,51 @@
   }
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr,
                                  SbAtomic64 old_value,
                                  SbAtomic64 new_value) {
   return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr,
                                  SbAtomic64 old_value,
                                  SbAtomic64 new_value) {
   return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value);
 }
 
-SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr,
-                                                 SbAtomic64 value) {
+static SB_C_FORCE_INLINE void
+SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr,
+                          SbAtomic64 value) {
   *ptr = value;
 }
 
-SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr,
-                                               SbAtomic64 value) {
+static SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr,
+                                                      SbAtomic64 value) {
   *ptr = value;
   SbAtomicMemoryBarrier();
 }
 
-SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr,
-                                               SbAtomic64 value) {
+static SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr,
+                                                      SbAtomic64 value) {
   SbAtomicMemoryBarrier();
   *ptr = value;
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr) {
   return *ptr;
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr) {
   SbAtomic64 value = *ptr;
   SbAtomicMemoryBarrier();
   return value;
 }
 
-SB_C_FORCE_INLINE SbAtomic64
+static SB_C_FORCE_INLINE SbAtomic64
 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr) {
   SbAtomicMemoryBarrier();
   return *ptr;
diff --git a/src/starboard/shared/posix/log_raw.cc b/src/starboard/shared/posix/log_raw.cc
index 10cfff0..f790d78 100644
--- a/src/starboard/shared/posix/log_raw.cc
+++ b/src/starboard/shared/posix/log_raw.cc
@@ -23,14 +23,13 @@
   // Cribbed from base/logging.cc's RawLog() function.
   size_t bytes_written = 0;
   const size_t message_len = strlen(message);
-  int rv;
   while (bytes_written < message_len) {
-    rv = HANDLE_EINTR(write(STDERR_FILENO, message + bytes_written,
-                            message_len - bytes_written));
-    if (rv < 0) {
+    int retval = HANDLE_EINTR(write(STDERR_FILENO, message + bytes_written,
+                                    message_len - bytes_written));
+    if (retval < 0) {
       // Give up, nothing we can do now.
       break;
     }
-    bytes_written += rv;
+    bytes_written += retval;
   }
 }
diff --git a/src/starboard/shared/posix/socket_receive_from.cc b/src/starboard/shared/posix/socket_receive_from.cc
index 6acea4c..ca0cb04 100644
--- a/src/starboard/shared/posix/socket_receive_from.cc
+++ b/src/starboard/shared/posix/socket_receive_from.cc
@@ -27,6 +27,8 @@
                         char* out_data,
                         int data_size,
                         SbSocketAddress* out_source) {
+  const int kRecvFlags = 0;
+
   if (!SbSocketIsValid(socket)) {
     errno = EBADF;
     return -1;
@@ -52,7 +54,8 @@
       }
     }
 
-    ssize_t bytes_read = recv(socket->socket_fd, out_data, data_size, 0);
+    ssize_t bytes_read =
+        recv(socket->socket_fd, out_data, data_size, kRecvFlags);
     if (bytes_read >= 0) {
       socket->error = kSbSocketOk;
       return static_cast<int>(bytes_read);
@@ -66,8 +69,9 @@
     return -1;
   } else if (socket->protocol == kSbSocketProtocolUdp) {
     sbposix::SockAddr sock_addr;
-    ssize_t bytes_read = recvfrom(socket->socket_fd, out_data, data_size, 0,
-                                  sock_addr.sockaddr(), &sock_addr.length);
+    ssize_t bytes_read =
+        recvfrom(socket->socket_fd, out_data, data_size, kRecvFlags,
+                 sock_addr.sockaddr(), &sock_addr.length);
 
     if (bytes_read >= 0) {
       if (out_source) {
diff --git a/src/starboard/shared/posix/socket_send_to.cc b/src/starboard/shared/posix/socket_send_to.cc
index 23e134d..6cd0c70 100644
--- a/src/starboard/shared/posix/socket_send_to.cc
+++ b/src/starboard/shared/posix/socket_send_to.cc
@@ -26,6 +26,11 @@
                    const char* data,
                    int data_size,
                    const SbSocketAddress* destination) {
+#if defined(MSG_NOSIGNAL)
+  const int kSendFlags = MSG_NOSIGNAL;
+#else
+  const int kSendFlags = 0;
+#endif
   if (!SbSocketIsValid(socket)) {
     errno = EBADF;
     return -1;
@@ -39,7 +44,8 @@
       return -1;
     }
 
-    ssize_t bytes_written = send(socket->socket_fd, data, data_size, 0);
+    ssize_t bytes_written =
+        send(socket->socket_fd, data, data_size, kSendFlags);
     if (bytes_written >= 0) {
       socket->error = kSbSocketOk;
       return static_cast<int>(bytes_written);
@@ -70,8 +76,8 @@
       sockaddr_length = sock_addr.length;
     }
 
-    ssize_t bytes_written = sendto(socket->socket_fd, data, data_size, 0,
-                                   sockaddr, sockaddr_length);
+    ssize_t bytes_written = sendto(socket->socket_fd, data, data_size,
+                                   kSendFlags, sockaddr, sockaddr_length);
     if (bytes_written >= 0) {
       socket->error = kSbSocketOk;
       return static_cast<int>(bytes_written);
diff --git a/src/starboard/shared/posix/time_get_monotonic_thread_now.cc b/src/starboard/shared/posix/time_get_monotonic_thread_now.cc
new file mode 100644
index 0000000..7050050
--- /dev/null
+++ b/src/starboard/shared/posix/time_get_monotonic_thread_now.cc
@@ -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.
+
+#include "starboard/time.h"
+
+#include <time.h>
+
+#include "starboard/log.h"
+#include "starboard/shared/posix/time_internal.h"
+
+SbTimeMonotonic SbTimeGetMonotonicThreadNow() {
+  struct timespec time;
+  if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time) != 0) {
+    SB_NOTREACHED() << "clock_gettime(CLOCK_THREAD_CPUTIME_ID) failed.";
+    return 0;
+  }
+
+  return FromTimespecDelta(&time);
+}
diff --git a/src/starboard/shared/pthread/thread_create.cc b/src/starboard/shared/pthread/thread_create.cc
index 70a1fdb..6cc9028 100644
--- a/src/starboard/shared/pthread/thread_create.cc
+++ b/src/starboard/shared/pthread/thread_create.cc
@@ -1,4 +1,4 @@
-// Copyright 2015 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.
@@ -16,10 +16,28 @@
 
 #include <pthread.h>
 #include <sched.h>
+#include <sys/resource.h>
+#include <unistd.h>
 
+#include "starboard/log.h"
 #include "starboard/shared/pthread/is_success.h"
+#include "starboard/shared/pthread/thread_create_priority.h"
 #include "starboard/string.h"
 
+namespace starboard {
+namespace shared {
+namespace pthread {
+
+#if !SB_HAS(THREAD_PRIORITY_SUPPORT)
+// Default implementation without thread priority support
+void ThreadSetPriority(SbThreadPriority /* priority */) {
+}
+#endif
+
+}  // namespace pthread
+}  // namespace shared
+}  // namespace starboard
+
 namespace {
 
 struct ThreadParams {
@@ -27,6 +45,7 @@
   SbThreadEntryPoint entry_point;
   char name[128];
   void* context;
+  SbThreadPriority priority;
 };
 
 void* ThreadFunc(void* context) {
@@ -38,6 +57,8 @@
     SbThreadSetName(thread_params->name);
   }
 
+  starboard::shared::pthread::ThreadSetPriority(thread_params->priority);
+
   delete thread_params;
 
   if (SbThreadIsValidAffinity(affinity)) {
@@ -84,10 +105,6 @@
     pthread_attr_setstacksize(&attributes, stack_size);
   }
 
-  // Here is where we would use priority, but it doesn't really work on Linux
-  // without using a realtime scheduling policy, according to this article:
-  // http://stackoverflow.com/questions/3649281/how-to-increase-thread-priority-in-pthreads/3663250
-
   ThreadParams* params = new ThreadParams();
   params->affinity = affinity;
   params->entry_point = entry_point;
@@ -99,6 +116,8 @@
     params->name[0] = '\0';
   }
 
+  params->priority = priority;
+
   SbThread thread = kSbThreadInvalid;
   result = pthread_create(&thread, &attributes, ThreadFunc, params);
 
diff --git a/src/starboard/shared/pthread/thread_create_priority.h b/src/starboard/shared/pthread/thread_create_priority.h
new file mode 100644
index 0000000..1c67ef5
--- /dev/null
+++ b/src/starboard/shared/pthread/thread_create_priority.h
@@ -0,0 +1,34 @@
+// 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_PTHREAD_THREAD_CREATE_PRIORITY_H_
+#define STARBOARD_SHARED_PTHREAD_THREAD_CREATE_PRIORITY_H_
+
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace shared {
+namespace pthread {
+
+// Set priority of the current thread.
+//
+// Implement this in a platform-specific thread_create_priority.cc if the
+// platform SB_HAS(THREAD_PRIORITY_SUPPORT)
+void ThreadSetPriority(SbThreadPriority priority);
+
+}  // namespace pthread
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_PTHREAD_THREAD_CREATE_PRIORITY_H_
diff --git a/src/starboard/shared/pthread/types_public.h b/src/starboard/shared/pthread/types_public.h
index 5475df8..59ee65e 100644
--- a/src/starboard/shared/pthread/types_public.h
+++ b/src/starboard/shared/pthread/types_public.h
@@ -52,6 +52,6 @@
 typedef pthread_t SbThread;
 
 // Well-defined constant value to mean "no thread handle."
-#define kSbThreadInvalid (SbThread) - 1
+#define kSbThreadInvalid (SbThread)-1
 
 #endif  // STARBOARD_SHARED_PTHREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/shared/signal/crash_signals.cc b/src/starboard/shared/signal/crash_signals.cc
index 0d74011..78b3ab2 100644
--- a/src/starboard/shared/signal/crash_signals.cc
+++ b/src/starboard/shared/signal/crash_signals.cc
@@ -32,7 +32,7 @@
 };
 
 const int kStopSignalsToTrap[] = {
-    SIGTERM, SIGINT, SIGHUP,
+    SIGHUP,
 };
 
 void Crash(int signal_id) {
diff --git a/src/starboard/shared/signal/crash_signals_sigaction.cc b/src/starboard/shared/signal/crash_signals_sigaction.cc
index 5273ab1..da318a8 100644
--- a/src/starboard/shared/signal/crash_signals_sigaction.cc
+++ b/src/starboard/shared/signal/crash_signals_sigaction.cc
@@ -32,7 +32,7 @@
 };
 
 const int kStopSignalsToTrap[] = {
-    SIGTERM, SIGINT, SIGHUP,
+    SIGHUP,
 };
 
 void SetSignalHandler(int signal_id, SignalHandlerFunction handler) {
diff --git a/src/starboard/shared/signal/suspend_signals.cc b/src/starboard/shared/signal/suspend_signals.cc
index 6bb42d8..4048a3b 100644
--- a/src/starboard/shared/signal/suspend_signals.cc
+++ b/src/starboard/shared/signal/suspend_signals.cc
@@ -15,6 +15,7 @@
 #include "starboard/shared/signal/suspend_signals.h"
 
 #include <signal.h>
+#include <sys/socket.h>
 
 #include "starboard/configuration.h"
 #include "starboard/log.h"
@@ -71,19 +72,34 @@
 
 }  // namespace
 
+#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
+// See "#if !defined(MSG_NOSIGNAL)" below.
+// OS X, which we do not build for today, has another mechanism which
+// should be used.
+#error On this platform, please use SO_NOSIGPIPE and leave the SIGPIPE \
+       handler at default.
+#endif
+
 void InstallSuspendSignalHandlers() {
   SetSignalHandler(SIGTSTP, &Suspend);
   UnblockSignal(SIGTSTP);
   SetSignalHandler(SIGCONT, &Resume);
 
-  // We might receive SIGPIPE after resuming. We should not terminate.
+#if !defined(MSG_NOSIGNAL)
+  // By default in POSIX, sending to a closed socket causes a SIGPIPE
+  // If we cannot disable that behavior, we must ignore SIGPIPE.
+  // Ignoring SIGPIPE means cases that use pipes to redirect the stdio
+  // log messages may behave in surprising ways, so it's not desirable.
   SetSignalHandler(SIGPIPE, &Ignore);
+#endif
 }
 
 void UninstallSuspendSignalHandlers() {
   SetSignalHandler(SIGCONT, SIG_DFL);
   SetSignalHandler(SIGTSTP, SIG_DFL);
+#if !defined(MSG_NOSIGNAL)
   SetSignalHandler(SIGPIPE, SIG_DFL);
+#endif
 }
 
 }  // namespace signal
diff --git a/src/starboard/shared/starboard/application.cc b/src/starboard/shared/starboard/application.cc
index 26e08db..4f2e850 100644
--- a/src/starboard/shared/starboard/application.cc
+++ b/src/starboard/shared/starboard/application.cc
@@ -17,7 +17,6 @@
 #include "starboard/atomic.h"
 #include "starboard/condition_variable.h"
 #include "starboard/event.h"
-#include "starboard/log.h"
 #include "starboard/memory.h"
 #include "starboard/string.h"
 
@@ -77,7 +76,7 @@
           reinterpret_cast<SbAtomicPtr>(NULL)));
   SB_DCHECK(old_instance);
   SB_DCHECK(old_instance == this);
-  SbMemoryFree(start_link_);
+  SbMemoryDeallocate(start_link_);
 }
 
 int Application::Run(int argc, char** argv) {
@@ -131,7 +130,7 @@
 
 #if SB_HAS(PLAYER) && SB_IS(PLAYER_PUNCHED_OUT)
 void Application::HandleFrame(SbPlayer player,
-                              const player::VideoFrame& frame,
+                              const scoped_refptr<VideoFrame>& frame,
                               int x,
                               int y,
                               int width,
@@ -141,7 +140,7 @@
 #endif  // SB_HAS(PLAYER) && SB_IS(PLAYER_PUNCHED_OUT)
 
 void Application::SetStartLink(const char* start_link) {
-  SbMemoryFree(start_link_);
+  SbMemoryDeallocate(start_link_);
   if (start_link) {
     start_link_ = SbStringDuplicate(start_link);
   } else {
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h
index 7328f2e..4387bed 100644
--- a/src/starboard/shared/starboard/application.h
+++ b/src/starboard/shared/starboard/application.h
@@ -38,6 +38,8 @@
 // dispatching events to the Starboard event handler, SbEventHandle.
 class Application {
  public:
+  typedef player::VideoFrame VideoFrame;
+
   // You can use a void(void *) function to signal that a state-transition event
   // has completed.
   typedef SbEventDataDestructor EventHandledCallback;
@@ -200,7 +202,7 @@
   // used when the application needs to composite video frames with punch-out
   // video manually (should be rare). Will be called from an external thread.
   void HandleFrame(SbPlayer player,
-                   const player::VideoFrame& frame,
+                   const scoped_refptr<VideoFrame>& frame,
                    int x,
                    int y,
                    int width,
@@ -222,7 +224,7 @@
   // Subclasses may override this method to accept video frames from the media
   // system. Will be called from an external thread.
   virtual void AcceptFrame(SbPlayer player,
-                           const player::VideoFrame& frame,
+                           const scoped_refptr<VideoFrame>& frame,
                            int x,
                            int y,
                            int width,
diff --git a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
index 09b2402..cd88dd8 100644
--- a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
+++ b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
@@ -106,7 +106,7 @@
                                &is_playing, &is_eos_reached, context_);
     if (is_playing) {
       int frames_to_consume =
-          std::min(kMaxFramesToConsumePerRequest, frames_in_buffer / 2);
+          std::min(kMaxFramesToConsumePerRequest, frames_in_buffer);
 
       SbThreadSleep(frames_to_consume * kSbTimeSecond / sampling_frequency_hz_);
       consume_frame_func_(frames_to_consume, context_);
diff --git a/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc b/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc
index 3317806..8648663 100644
--- a/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc
+++ b/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc
@@ -248,9 +248,11 @@
     }
   }
 
-  return SbBlitterBlitRectsToRects(context, source_surface, src_rects,
-                                   dst_rects, num_tiles);
+  bool result = SbBlitterBlitRectsToRects(context, source_surface, src_rects,
+                                          dst_rects, num_tiles);
 
   delete[] src_rects;
   delete[] dst_rects;
+
+  return result;
 }
diff --git a/src/starboard/shared/starboard/file_storage/storage_internal.h b/src/starboard/shared/starboard/file_storage/storage_internal.h
index ea322b8..6bb3362 100644
--- a/src/starboard/shared/starboard/file_storage/storage_internal.h
+++ b/src/starboard/shared/starboard/file_storage/storage_internal.h
@@ -33,9 +33,9 @@
 namespace shared {
 namespace starboard {
 // Gets the path to the storage file for the given user.
-SB_C_INLINE bool GetUserStorageFilePath(SbUser user,
-                                        char* out_path,
-                                        int path_size) {
+static SB_C_INLINE bool GetUserStorageFilePath(SbUser user,
+                                               char* out_path,
+                                               int path_size) {
   bool success = SbUserGetProperty(user, kSbUserPropertyHomeDirectory, out_path,
                                    path_size);
   if (!success) {
diff --git a/src/starboard/shared/starboard/localized_strings.cc b/src/starboard/shared/starboard/localized_strings.cc
index 2639c53..0f802e6 100644
--- a/src/starboard/shared/starboard/localized_strings.cc
+++ b/src/starboard/shared/starboard/localized_strings.cc
@@ -116,7 +116,7 @@
     return false;
   }
   SB_DCHECK(file_contents.length() > 0);
-  SB_DCHECK(file_contents.back() == '\n');
+  SB_DCHECK(file_contents[file_contents.length() - 1] == '\n');
 
   // Each line of the file corresponds to one message (key/value).
   size_t pos = 0;
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/shared/starboard/memory_reporter_internal.h b/src/starboard/shared/starboard/memory_reporter_internal.h
new file mode 100644
index 0000000..72b2bef
--- /dev/null
+++ b/src/starboard/shared/starboard/memory_reporter_internal.h
@@ -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.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_
+
+#include "starboard/export.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Internal function used to track mapped memory. This is used internally by
+// implementations of SbMemoryMap().
+SB_EXPORT void SbMemoryReporterReportMappedMemory(const void* memory,
+                                                  size_t size);
+
+// Internal function used to track mapped memory. This is used internally by
+// implementations of SbMemoryUnmap().
+SB_EXPORT void SbMemoryReporterReportUnmappedMemory(const void* memory,
+                                                    size_t size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/new.cc b/src/starboard/shared/starboard/new.cc
index bf4f97b..7e08ed0 100644
--- a/src/starboard/shared/starboard/new.cc
+++ b/src/starboard/shared/starboard/new.cc
@@ -22,7 +22,7 @@
 }
 
 void operator delete(void* pointer) {
-  SbMemoryFree(pointer);
+  SbMemoryDeallocate(pointer);
 }
 
 void* operator new[](size_t size) {
@@ -30,5 +30,5 @@
 }
 
 void operator delete[](void* pointer) {
-  SbMemoryFree(pointer);
+  SbMemoryDeallocate(pointer);
 }
diff --git a/src/starboard/shared/starboard/player/audio_decoder_internal.h b/src/starboard/shared/starboard/player/audio_decoder_internal.h
deleted file mode 100644
index 000a22c..0000000
--- a/src/starboard/shared/starboard/player/audio_decoder_internal.h
+++ /dev/null
@@ -1,61 +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_PLAYER_AUDIO_DECODER_INTERNAL_H_
-#define STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_DECODER_INTERNAL_H_
-
-#include <vector>
-
-#include "starboard/media.h"
-#include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/input_buffer_internal.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-// This class decodes encoded audio stream into playable audio data.
-class AudioDecoder {
- public:
-  virtual ~AudioDecoder() {}
-
-  // Decode the encoded audio data stored in |input_buffer| and store the
-  // result in |output|.
-  virtual void Decode(const InputBuffer& input_buffer,
-                      std::vector<float>* output) = 0;
-  // Note that there won't be more input data unless Reset() is called.
-  virtual void WriteEndOfStream() = 0;
-  // Clear any cached buffer of the codec and reset the state of the codec.
-  // This function will be called during seek to ensure that the left over
-  // data from previous buffers are cleared.
-  virtual void Reset() = 0;
-
-  // Return the sample rate of the incoming audio.  This should be used by the
-  // audio renderer as the sample rate of the underlying audio stream can be
-  // different than the sample rate stored in the meta data.
-  virtual int GetSamplesPerSecond() = 0;
-
-  // Individual implementation has to implement this function to create an
-  // audio decoder.
-  static AudioDecoder* Create(SbMediaAudioCodec audio_codec,
-                              const SbMediaAudioHeader& audio_header);
-};
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/audio_renderer_internal.cc b/src/starboard/shared/starboard/player/audio_renderer_internal.cc
deleted file mode 100644
index c761c09..0000000
--- a/src/starboard/shared/starboard/player/audio_renderer_internal.cc
+++ /dev/null
@@ -1,246 +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/player/audio_renderer_internal.h"
-
-#include <algorithm>
-
-#include "starboard/memory.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-namespace {
-// TODO: This should be retrieved from the decoder.
-// TODO: Make it not dependent on the frame size of AAC and HE-AAC.
-const int kMaxFramesPerAccessUnit = 1024 * 2;
-}  // namespace
-
-AudioRenderer::AudioRenderer(AudioDecoder* decoder,
-                             const SbMediaAudioHeader& audio_header)
-    : channels_(audio_header.number_of_channels),
-      paused_(true),
-      seeking_(false),
-      seeking_to_pts_(0),
-      frame_buffer_(kMaxCachedFrames * audio_header.number_of_channels),
-      frames_in_buffer_(0),
-      offset_in_frames_(0),
-      frames_consumed_(0),
-      end_of_stream_reached_(false),
-      decoder_(decoder),
-      audio_sink_(kSbAudioSinkInvalid) {
-  SB_DCHECK(decoder_ != NULL);
-  frame_buffers_[0] = &frame_buffer_[0];
-}
-
-AudioRenderer::~AudioRenderer() {
-  if (audio_sink_ != kSbAudioSinkInvalid) {
-    SbAudioSinkDestroy(audio_sink_);
-  }
-  delete decoder_;
-}
-
-void AudioRenderer::WriteSample(const InputBuffer& input_buffer) {
-  if (end_of_stream_reached_) {
-    SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts()
-                  << " after EOS reached.";
-    return;
-  }
-
-  SbMediaTime input_pts = input_buffer.pts();
-  std::vector<float> decoded_audio;
-  decoder_->Decode(input_buffer, &decoded_audio);
-  if (decoded_audio.empty()) {
-    SB_DLOG(ERROR) << "decoded_audio contains no frames.";
-    return;
-  }
-
-  {
-    ScopedLock lock(mutex_);
-    if (seeking_) {
-      if (input_pts < seeking_to_pts_) {
-        return;
-      }
-    }
-
-    AppendFrames(&decoded_audio[0], decoded_audio.size() / channels_);
-
-    if (seeking_ && frame_buffer_.size() > kPrerollFrames * channels_) {
-      seeking_ = false;
-    }
-  }
-
-  // Create the audio sink if it is the first incoming AU after seeking.
-  if (audio_sink_ == kSbAudioSinkInvalid) {
-    int sample_rate = decoder_->GetSamplesPerSecond();
-    // TODO: Implement resampler.
-    SB_DCHECK(sample_rate ==
-              SbAudioSinkGetNearestSupportedSampleFrequency(sample_rate));
-    // TODO: Handle sink creation failure.
-    audio_sink_ = SbAudioSinkCreate(
-        channels_, sample_rate, kSbMediaAudioSampleTypeFloat32,
-        kSbMediaAudioFrameStorageTypeInterleaved,
-        reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_),
-        kMaxCachedFrames, &AudioRenderer::UpdateSourceStatusFunc,
-        &AudioRenderer::ConsumeFramesFunc, this);
-  }
-}
-
-void AudioRenderer::WriteEndOfStream() {
-  SB_LOG_IF(WARNING, end_of_stream_reached_)
-      << "Try to write EOS after EOS is reached";
-  if (end_of_stream_reached_) {
-    return;
-  }
-  end_of_stream_reached_ = true;
-  decoder_->WriteEndOfStream();
-
-  ScopedLock lock(mutex_);
-  // If we are seeking, we consider the seek is finished if end of stream is
-  // reached as there won't be any audio data in future.
-  if (seeking_) {
-    seeking_ = false;
-  }
-}
-
-void AudioRenderer::Play() {
-  ScopedLock lock(mutex_);
-  paused_ = false;
-}
-
-void AudioRenderer::Pause() {
-  ScopedLock lock(mutex_);
-  paused_ = true;
-}
-
-void AudioRenderer::Seek(SbMediaTime seek_to_pts) {
-  SB_DCHECK(seek_to_pts >= 0);
-
-  SbAudioSinkDestroy(audio_sink_);
-  audio_sink_ = kSbAudioSinkInvalid;
-
-  seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
-  seeking_ = true;
-  frames_in_buffer_ = 0;
-  offset_in_frames_ = 0;
-  frames_consumed_ = 0;
-  end_of_stream_reached_ = false;
-
-  decoder_->Reset();
-  return;
-}
-
-bool AudioRenderer::IsEndOfStreamPlayed() const {
-  return end_of_stream_reached_ && frames_in_buffer_ == 0;
-}
-
-bool AudioRenderer::CanAcceptMoreData() const {
-  ScopedLock lock(mutex_);
-  return frames_in_buffer_ <= kMaxCachedFrames - kMaxFramesPerAccessUnit &&
-         !end_of_stream_reached_;
-}
-
-bool AudioRenderer::IsSeekingInProgress() const {
-  return seeking_;
-}
-
-SbMediaTime AudioRenderer::GetCurrentTime() {
-  if (seeking_) {
-    return seeking_to_pts_;
-  }
-  return seeking_to_pts_ +
-         frames_consumed_ * kSbMediaTimeSecond /
-             decoder_->GetSamplesPerSecond();
-}
-
-// static
-void AudioRenderer::UpdateSourceStatusFunc(int* frames_in_buffer,
-                                           int* offset_in_frames,
-                                           bool* is_playing,
-                                           bool* is_eos_reached,
-                                           void* context) {
-  AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context);
-  SB_DCHECK(audio_renderer);
-  SB_DCHECK(frames_in_buffer);
-  SB_DCHECK(offset_in_frames);
-  SB_DCHECK(is_playing);
-  SB_DCHECK(is_eos_reached);
-
-  audio_renderer->UpdateSourceStatus(frames_in_buffer, offset_in_frames,
-                                     is_playing, is_eos_reached);
-}
-
-// static
-void AudioRenderer::ConsumeFramesFunc(int frames_consumed, void* context) {
-  AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context);
-  SB_DCHECK(audio_renderer);
-
-  audio_renderer->ConsumeFrames(frames_consumed);
-}
-
-void AudioRenderer::UpdateSourceStatus(int* frames_in_buffer,
-                                       int* offset_in_frames,
-                                       bool* is_playing,
-                                       bool* is_eos_reached) {
-  ScopedLock lock(mutex_);
-
-  *is_eos_reached = end_of_stream_reached_;
-
-  if (paused_ || seeking_) {
-    *is_playing = false;
-    *frames_in_buffer = *offset_in_frames = 0;
-    return;
-  }
-
-  *is_playing = true;
-  *frames_in_buffer = frames_in_buffer_;
-  *offset_in_frames = offset_in_frames_;
-}
-
-void AudioRenderer::ConsumeFrames(int frames_consumed) {
-  ScopedLock lock(mutex_);
-
-  SB_DCHECK(frames_consumed <= frames_in_buffer_);
-  offset_in_frames_ += frames_consumed;
-  offset_in_frames_ %= kMaxCachedFrames;
-  frames_in_buffer_ -= frames_consumed;
-  frames_consumed_ += frames_consumed;
-}
-
-void AudioRenderer::AppendFrames(const float* source_buffer,
-                                 int frames_to_append) {
-  SB_DCHECK(frames_in_buffer_ + frames_to_append <= kMaxCachedFrames);
-
-  int offset_to_append =
-      (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames;
-  if (frames_to_append > kMaxCachedFrames - offset_to_append) {
-    SbMemoryCopy(
-        &frame_buffer_[offset_to_append * channels_], source_buffer,
-        (kMaxCachedFrames - offset_to_append) * sizeof(float) * channels_);
-    source_buffer += (kMaxCachedFrames - offset_to_append) * channels_;
-    frames_to_append -= kMaxCachedFrames - offset_to_append;
-    frames_in_buffer_ += kMaxCachedFrames - offset_to_append;
-    offset_to_append = 0;
-  }
-  SbMemoryCopy(&frame_buffer_[offset_to_append * channels_], source_buffer,
-               frames_to_append * sizeof(float) * channels_);
-  frames_in_buffer_ += frames_to_append;
-}
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/audio_renderer_internal.h b/src/starboard/shared/starboard/player/audio_renderer_internal.h
deleted file mode 100644
index cbc68c8..0000000
--- a/src/starboard/shared/starboard/player/audio_renderer_internal.h
+++ /dev/null
@@ -1,102 +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_PLAYER_AUDIO_RENDERER_INTERNAL_H_
-#define STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_RENDERER_INTERNAL_H_
-
-#include <vector>
-
-#include "starboard/audio_sink.h"
-#include "starboard/log.h"
-#include "starboard/media.h"
-#include "starboard/mutex.h"
-#include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/audio_decoder_internal.h"
-#include "starboard/shared/starboard/player/input_buffer_internal.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-class AudioRenderer {
- public:
-  AudioRenderer(AudioDecoder* decoder, const SbMediaAudioHeader& audio_header);
-  ~AudioRenderer();
-
-  bool is_valid() const { return true; }
-
-  void WriteSample(const InputBuffer& input_buffer);
-  void WriteEndOfStream();
-
-  void Play();
-  void Pause();
-  void Seek(SbMediaTime seek_to_pts);
-
-  bool IsEndOfStreamWritten() const { return end_of_stream_reached_; }
-  bool IsEndOfStreamPlayed() const;
-  bool CanAcceptMoreData() const;
-  bool IsSeekingInProgress() const;
-  SbMediaTime GetCurrentTime();
-
- private:
-  // Preroll considered finished after either kPrerollFrames is cached or EOS
-  // is reached.
-  static const size_t kPrerollFrames = 64 * 1024;
-  // Set a soft limit for the max audio frames we can cache so we can:
-  // 1. Avoid using too much memory.
-  // 2. Have the audio cache full to simulate the state that the renderer can
-  //    no longer accept more data.
-  static const size_t kMaxCachedFrames = 256 * 1024;
-
-  // SbAudioSink callbacks
-  static void UpdateSourceStatusFunc(int* frames_in_buffer,
-                                     int* offset_in_frames,
-                                     bool* is_playing,
-                                     bool* is_eos_reached,
-                                     void* context);
-  static void ConsumeFramesFunc(int frames_consumed, void* context);
-  void UpdateSourceStatus(int* frames_in_buffer,
-                          int* offset_in_frames,
-                          bool* is_playing,
-                          bool* is_eos_reached);
-  void ConsumeFrames(int frames_consumed);
-
-  void AppendFrames(const float* source_buffer, int frames_to_append);
-
-  const int channels_;
-
-  Mutex mutex_;
-  bool paused_;
-  bool seeking_;
-  SbMediaTime seeking_to_pts_;
-
-  std::vector<float> frame_buffer_;
-  float* frame_buffers_[1];
-  int frames_in_buffer_;
-  int offset_in_frames_;
-
-  int frames_consumed_;
-  bool end_of_stream_reached_;
-
-  AudioDecoder* decoder_;
-  SbAudioSink audio_sink_;
-};
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h b/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h
new file mode 100644
index 0000000..cb1850c
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h
@@ -0,0 +1,67 @@
+// 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_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_
+
+#include <vector>
+
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// This class decodes encoded audio stream into playable audio data.
+class AudioDecoder {
+ public:
+  virtual ~AudioDecoder() {}
+
+  // Decode the encoded audio data stored in |input_buffer| and store the
+  // result in |output|.
+  virtual void Decode(const InputBuffer& input_buffer,
+                      std::vector<uint8_t>* output) = 0;
+  // Note that there won't be more input data unless Reset() is called.
+  virtual void WriteEndOfStream() = 0;
+  // Clear any cached buffer of the codec and reset the state of the codec.
+  // This function will be called during seek to ensure that the left over
+  // data from previous buffers are cleared.
+  virtual void Reset() = 0;
+
+  // Return the sample type of the decoded pcm data.
+  virtual SbMediaAudioSampleType GetSampleType() const = 0;
+
+  // Return the sample rate of the incoming audio.  This should be used by the
+  // audio renderer as the sample rate of the underlying audio stream can be
+  // different than the sample rate stored in the meta data.
+  virtual int GetSamplesPerSecond() const = 0;
+
+  // Individual implementation has to implement this function to create an
+  // audio decoder.
+  static AudioDecoder* Create(SbMediaAudioCodec audio_codec,
+                              const SbMediaAudioHeader& audio_header);
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc
new file mode 100644
index 0000000..105e23f
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc
@@ -0,0 +1,250 @@
+// 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/player/filter/audio_renderer_internal.h"
+
+#include <algorithm>
+
+#include "starboard/memory.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+namespace {
+// TODO: This should be retrieved from the decoder.
+// TODO: Make it not dependent on the frame size of AAC and HE-AAC.
+const int kMaxFramesPerAccessUnit = 1024 * 2;
+}  // namespace
+
+AudioRenderer::AudioRenderer(scoped_ptr<AudioDecoder> decoder,
+                             const SbMediaAudioHeader& audio_header)
+    : channels_(audio_header.number_of_channels),
+      bytes_per_frame_(
+          (decoder->GetSampleType() == kSbMediaAudioSampleTypeInt16 ? 2 : 4) *
+          channels_),
+      paused_(true),
+      seeking_(false),
+      seeking_to_pts_(0),
+      frame_buffer_(kMaxCachedFrames * bytes_per_frame_),
+      frames_in_buffer_(0),
+      offset_in_frames_(0),
+      frames_consumed_(0),
+      end_of_stream_reached_(false),
+      decoder_(decoder.Pass()),
+      audio_sink_(kSbAudioSinkInvalid) {
+  SB_DCHECK(decoder_ != NULL);
+  frame_buffers_[0] = &frame_buffer_[0];
+}
+
+AudioRenderer::~AudioRenderer() {
+  if (audio_sink_ != kSbAudioSinkInvalid) {
+    SbAudioSinkDestroy(audio_sink_);
+  }
+}
+
+void AudioRenderer::WriteSample(const InputBuffer& input_buffer) {
+  if (end_of_stream_reached_) {
+    SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts()
+                  << " after EOS reached.";
+    return;
+  }
+
+  SbMediaTime input_pts = input_buffer.pts();
+  std::vector<uint8_t> decoded_audio;
+  decoder_->Decode(input_buffer, &decoded_audio);
+  if (decoded_audio.empty()) {
+    SB_DLOG(ERROR) << "decoded_audio contains no frames.";
+    return;
+  }
+
+  {
+    ScopedLock lock(mutex_);
+    if (seeking_) {
+      if (input_pts < seeking_to_pts_) {
+        return;
+      }
+    }
+
+    AppendFrames(&decoded_audio[0], decoded_audio.size() / bytes_per_frame_);
+
+    if (seeking_ && frame_buffer_.size() > kPrerollFrames * bytes_per_frame_) {
+      seeking_ = false;
+    }
+  }
+
+  // Create the audio sink if it is the first incoming AU after seeking.
+  if (audio_sink_ == kSbAudioSinkInvalid) {
+    int sample_rate = decoder_->GetSamplesPerSecond();
+    // TODO: Implement resampler.
+    SB_DCHECK(sample_rate ==
+              SbAudioSinkGetNearestSupportedSampleFrequency(sample_rate));
+    // TODO: Handle sink creation failure.
+    audio_sink_ = SbAudioSinkCreate(
+        channels_, sample_rate, decoder_->GetSampleType(),
+        kSbMediaAudioFrameStorageTypeInterleaved,
+        reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_),
+        kMaxCachedFrames, &AudioRenderer::UpdateSourceStatusFunc,
+        &AudioRenderer::ConsumeFramesFunc, this);
+  }
+}
+
+void AudioRenderer::WriteEndOfStream() {
+  SB_LOG_IF(WARNING, end_of_stream_reached_)
+      << "Try to write EOS after EOS is reached";
+  if (end_of_stream_reached_) {
+    return;
+  }
+  end_of_stream_reached_ = true;
+  decoder_->WriteEndOfStream();
+
+  ScopedLock lock(mutex_);
+  // If we are seeking, we consider the seek is finished if end of stream is
+  // reached as there won't be any audio data in future.
+  if (seeking_) {
+    seeking_ = false;
+  }
+}
+
+void AudioRenderer::Play() {
+  ScopedLock lock(mutex_);
+  paused_ = false;
+}
+
+void AudioRenderer::Pause() {
+  ScopedLock lock(mutex_);
+  paused_ = true;
+}
+
+void AudioRenderer::Seek(SbMediaTime seek_to_pts) {
+  SB_DCHECK(seek_to_pts >= 0);
+
+  SbAudioSinkDestroy(audio_sink_);
+  audio_sink_ = kSbAudioSinkInvalid;
+
+  seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
+  seeking_ = true;
+  frames_in_buffer_ = 0;
+  offset_in_frames_ = 0;
+  frames_consumed_ = 0;
+  end_of_stream_reached_ = false;
+
+  decoder_->Reset();
+  return;
+}
+
+bool AudioRenderer::IsEndOfStreamPlayed() const {
+  return end_of_stream_reached_ && frames_in_buffer_ == 0;
+}
+
+bool AudioRenderer::CanAcceptMoreData() const {
+  ScopedLock lock(mutex_);
+  return frames_in_buffer_ <= kMaxCachedFrames - kMaxFramesPerAccessUnit &&
+         !end_of_stream_reached_;
+}
+
+bool AudioRenderer::IsSeekingInProgress() const {
+  return seeking_;
+}
+
+SbMediaTime AudioRenderer::GetCurrentTime() {
+  if (seeking_) {
+    return seeking_to_pts_;
+  }
+  return seeking_to_pts_ +
+         frames_consumed_ * kSbMediaTimeSecond /
+             decoder_->GetSamplesPerSecond();
+}
+
+// static
+void AudioRenderer::UpdateSourceStatusFunc(int* frames_in_buffer,
+                                           int* offset_in_frames,
+                                           bool* is_playing,
+                                           bool* is_eos_reached,
+                                           void* context) {
+  AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context);
+  SB_DCHECK(audio_renderer);
+  SB_DCHECK(frames_in_buffer);
+  SB_DCHECK(offset_in_frames);
+  SB_DCHECK(is_playing);
+  SB_DCHECK(is_eos_reached);
+
+  audio_renderer->UpdateSourceStatus(frames_in_buffer, offset_in_frames,
+                                     is_playing, is_eos_reached);
+}
+
+// static
+void AudioRenderer::ConsumeFramesFunc(int frames_consumed, void* context) {
+  AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context);
+  SB_DCHECK(audio_renderer);
+
+  audio_renderer->ConsumeFrames(frames_consumed);
+}
+
+void AudioRenderer::UpdateSourceStatus(int* frames_in_buffer,
+                                       int* offset_in_frames,
+                                       bool* is_playing,
+                                       bool* is_eos_reached) {
+  ScopedLock lock(mutex_);
+
+  *is_eos_reached = end_of_stream_reached_;
+
+  if (paused_ || seeking_) {
+    *is_playing = false;
+    *frames_in_buffer = *offset_in_frames = 0;
+    return;
+  }
+
+  *is_playing = true;
+  *frames_in_buffer = frames_in_buffer_;
+  *offset_in_frames = offset_in_frames_;
+}
+
+void AudioRenderer::ConsumeFrames(int frames_consumed) {
+  ScopedLock lock(mutex_);
+
+  SB_DCHECK(frames_consumed <= frames_in_buffer_);
+  offset_in_frames_ += frames_consumed;
+  offset_in_frames_ %= kMaxCachedFrames;
+  frames_in_buffer_ -= frames_consumed;
+  frames_consumed_ += frames_consumed;
+}
+
+void AudioRenderer::AppendFrames(const uint8_t* source_buffer,
+                                 int frames_to_append) {
+  SB_DCHECK(frames_in_buffer_ + frames_to_append <= kMaxCachedFrames);
+
+  int offset_to_append =
+      (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames;
+  if (frames_to_append > kMaxCachedFrames - offset_to_append) {
+    SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_],
+                 source_buffer,
+                 (kMaxCachedFrames - offset_to_append) * bytes_per_frame_);
+    source_buffer += (kMaxCachedFrames - offset_to_append) * bytes_per_frame_;
+    frames_to_append -= kMaxCachedFrames - offset_to_append;
+    frames_in_buffer_ += kMaxCachedFrames - offset_to_append;
+    offset_to_append = 0;
+  }
+  SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_],
+               source_buffer, frames_to_append * bytes_per_frame_);
+  frames_in_buffer_ += frames_to_append;
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
new file mode 100644
index 0000000..e14457c
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -0,0 +1,108 @@
+// 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_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_
+
+#include <vector>
+
+#include "starboard/audio_sink.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+class AudioRenderer {
+ public:
+  AudioRenderer(scoped_ptr<AudioDecoder> decoder,
+                const SbMediaAudioHeader& audio_header);
+  ~AudioRenderer();
+
+  bool is_valid() const { return true; }
+
+  void WriteSample(const InputBuffer& input_buffer);
+  void WriteEndOfStream();
+
+  void Play();
+  void Pause();
+  void Seek(SbMediaTime seek_to_pts);
+
+  bool IsEndOfStreamWritten() const { return end_of_stream_reached_; }
+  bool IsEndOfStreamPlayed() const;
+  bool CanAcceptMoreData() const;
+  bool IsSeekingInProgress() const;
+  SbMediaTime GetCurrentTime();
+
+ private:
+  // Preroll considered finished after either kPrerollFrames is cached or EOS
+  // is reached.
+  static const size_t kPrerollFrames = 64 * 1024;
+  // Set a soft limit for the max audio frames we can cache so we can:
+  // 1. Avoid using too much memory.
+  // 2. Have the audio cache full to simulate the state that the renderer can
+  //    no longer accept more data.
+  static const size_t kMaxCachedFrames = 256 * 1024;
+
+  // SbAudioSink callbacks
+  static void UpdateSourceStatusFunc(int* frames_in_buffer,
+                                     int* offset_in_frames,
+                                     bool* is_playing,
+                                     bool* is_eos_reached,
+                                     void* context);
+  static void ConsumeFramesFunc(int frames_consumed, void* context);
+  void UpdateSourceStatus(int* frames_in_buffer,
+                          int* offset_in_frames,
+                          bool* is_playing,
+                          bool* is_eos_reached);
+  void ConsumeFrames(int frames_consumed);
+
+  void AppendFrames(const uint8_t* source_buffer, int frames_to_append);
+
+  const int channels_;
+  const int bytes_per_frame_;
+
+  Mutex mutex_;
+  bool paused_;
+  bool seeking_;
+  SbMediaTime seeking_to_pts_;
+
+  std::vector<uint8_t> frame_buffer_;
+  uint8_t* frame_buffers_[1];
+  int frames_in_buffer_;
+  int offset_in_frames_;
+
+  int frames_consumed_;
+  bool end_of_stream_reached_;
+
+  scoped_ptr<AudioDecoder> decoder_;
+  SbAudioSink audio_sink_;
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
new file mode 100644
index 0000000..b73cbb2
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -0,0 +1,242 @@
+// 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/player/filter/filter_based_player_worker_handler.h"
+
+#include "starboard/log.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/drm/drm_system_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+FilterBasedPlayerWorkerHandler::FilterBasedPlayerWorkerHandler(
+    SbMediaVideoCodec video_codec,
+    SbMediaAudioCodec audio_codec,
+    SbDrmSystem drm_system,
+    const SbMediaAudioHeader& audio_header)
+    : player_worker_(NULL),
+      player_(kSbPlayerInvalid),
+      update_media_time_cb_(NULL),
+      get_player_state_cb_(NULL),
+      update_player_state_cb_(NULL),
+      video_codec_(video_codec),
+      audio_codec_(audio_codec),
+      drm_system_(drm_system),
+      audio_header_(audio_header),
+      paused_(false) {}
+
+void FilterBasedPlayerWorkerHandler::Setup(
+    PlayerWorker* player_worker,
+    SbPlayer player,
+    UpdateMediaTimeCB update_media_time_cb,
+    GetPlayerStateCB get_player_state_cb,
+    UpdatePlayerStateCB update_player_state_cb) {
+  // This function should only be called once.
+  SB_DCHECK(player_worker_ == NULL);
+
+  // All parameters has to be valid.
+  SB_DCHECK(player_worker);
+  SB_DCHECK(SbPlayerIsValid(player));
+  SB_DCHECK(update_media_time_cb);
+  SB_DCHECK(get_player_state_cb);
+  SB_DCHECK(update_player_state_cb);
+
+  player_worker_ = player_worker;
+  player_ = player;
+  update_media_time_cb_ = update_media_time_cb;
+  get_player_state_cb_ = get_player_state_cb;
+  update_player_state_cb_ = update_player_state_cb;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessInitEvent() {
+  scoped_ptr<AudioDecoder> audio_decoder(
+      AudioDecoder::Create(audio_codec_, audio_header_));
+  scoped_ptr<VideoDecoder> video_decoder(VideoDecoder::Create(video_codec_));
+
+  if (!audio_decoder || !video_decoder) {
+    return false;
+  }
+
+  audio_renderer_.reset(new AudioRenderer(audio_decoder.Pass(), audio_header_));
+  video_renderer_.reset(new VideoRenderer(video_decoder.Pass()));
+  if (audio_renderer_->is_valid() && video_renderer_->is_valid()) {
+    return true;
+  }
+
+  audio_renderer_.reset(NULL);
+  video_renderer_.reset(NULL);
+  return false;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessSeekEvent(
+    const SeekEventData& data) {
+  SbMediaTime seek_to_pts = data.seek_to_pts;
+  if (seek_to_pts < 0) {
+    SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_pts;
+    seek_to_pts = 0;
+  }
+
+  audio_renderer_->Pause();
+  audio_renderer_->Seek(seek_to_pts);
+  video_renderer_->Seek(seek_to_pts);
+  return true;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessWriteSampleEvent(
+    const WriteSampleEventData& data,
+    bool* written) {
+  SB_DCHECK(written != NULL);
+
+  *written = true;
+
+  if (data.sample_type == kSbMediaTypeAudio) {
+    if (audio_renderer_->IsEndOfStreamWritten()) {
+      SB_LOG(WARNING) << "Try to write audio sample after EOS is reached";
+    } else {
+      if (!audio_renderer_->CanAcceptMoreData()) {
+        *written = false;
+        return true;
+      }
+
+      if (data.input_buffer->drm_info()) {
+        if (!SbDrmSystemIsValid(drm_system_)) {
+          return false;
+        }
+        if (drm_system_->Decrypt(data.input_buffer) ==
+            SbDrmSystemPrivate::kRetry) {
+          *written = false;
+          return true;
+        }
+      }
+      audio_renderer_->WriteSample(*data.input_buffer);
+    }
+  } else {
+    SB_DCHECK(data.sample_type == kSbMediaTypeVideo);
+    if (video_renderer_->IsEndOfStreamWritten()) {
+      SB_LOG(WARNING) << "Try to write video sample after EOS is reached";
+    } else {
+      if (!video_renderer_->CanAcceptMoreData()) {
+        *written = false;
+        return true;
+      }
+      if (data.input_buffer->drm_info()) {
+        if (!SbDrmSystemIsValid(drm_system_)) {
+          return false;
+        }
+        if (drm_system_->Decrypt(data.input_buffer) ==
+            SbDrmSystemPrivate::kRetry) {
+          *written = false;
+          return true;
+        }
+      }
+      video_renderer_->WriteSample(*data.input_buffer);
+    }
+  }
+
+  return true;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessWriteEndOfStreamEvent(
+    const WriteEndOfStreamEventData& data) {
+  if (data.stream_type == kSbMediaTypeAudio) {
+    if (audio_renderer_->IsEndOfStreamWritten()) {
+      SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued";
+    } else {
+      SB_LOG(INFO) << "Audio EOS enqueued";
+      audio_renderer_->WriteEndOfStream();
+    }
+  } else {
+    if (video_renderer_->IsEndOfStreamWritten()) {
+      SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued";
+    } else {
+      SB_LOG(INFO) << "Video EOS enqueued";
+      video_renderer_->WriteEndOfStream();
+    }
+  }
+
+  return true;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessSetPauseEvent(
+    const SetPauseEventData& data) {
+  paused_ = data.pause;
+
+  if (data.pause) {
+    audio_renderer_->Pause();
+    SB_DLOG(INFO) << "Playback paused.";
+  } else {
+    audio_renderer_->Play();
+    SB_DLOG(INFO) << "Playback started.";
+  }
+
+  return true;
+}
+
+bool FilterBasedPlayerWorkerHandler::ProcessUpdateEvent(
+    const SetBoundsEventData& data) {
+  if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePrerolling) {
+    if (!audio_renderer_->IsSeekingInProgress() &&
+        !video_renderer_->IsSeekingInProgress()) {
+      (*player_worker_.*update_player_state_cb_)(kSbPlayerStatePresenting);
+      if (!paused_) {
+        audio_renderer_->Play();
+      }
+    }
+  }
+
+  if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePresenting) {
+    if (audio_renderer_->IsEndOfStreamPlayed() &&
+        video_renderer_->IsEndOfStreamPlayed()) {
+      (*player_worker_.*update_player_state_cb_)(kSbPlayerStateEndOfStream);
+    }
+
+    scoped_refptr<VideoFrame> frame =
+        video_renderer_->GetCurrentFrame(audio_renderer_->GetCurrentTime());
+
+#if SB_IS(PLAYER_PUNCHED_OUT)
+    shared::starboard::Application::Get()->HandleFrame(
+        player_, frame, data.x, data.y, data.width, data.height);
+#endif  // SB_IS(PLAYER_PUNCHED_OUT)
+
+    (*player_worker_.*update_media_time_cb_)(audio_renderer_->GetCurrentTime());
+  }
+
+  return true;
+}
+
+void FilterBasedPlayerWorkerHandler::ProcessStopEvent() {
+  audio_renderer_.reset();
+  video_renderer_.reset();
+
+#if SB_IS(PLAYER_PUNCHED_OUT)
+  // Clear the video frame as we terminate.
+  shared::starboard::Application::Get()->HandleFrame(
+      player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);
+#endif  // SB_IS(PLAYER_PUNCHED_OUT)
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
new file mode 100644
index 0000000..cde0391
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -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.
+
+#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+#include "starboard/player.h"
+#include "starboard/shared/starboard/player/filter/audio_renderer_internal.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/player_worker.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+class FilterBasedPlayerWorkerHandler : public PlayerWorker::Handler {
+ public:
+  FilterBasedPlayerWorkerHandler(SbMediaVideoCodec video_codec,
+                                 SbMediaAudioCodec audio_codec,
+                                 SbDrmSystem drm_system,
+                                 const SbMediaAudioHeader& audio_header);
+
+ private:
+  void Setup(PlayerWorker* player_worker,
+             SbPlayer player,
+             UpdateMediaTimeCB update_media_time_cb,
+             GetPlayerStateCB get_player_state_cb,
+             UpdatePlayerStateCB update_player_state_cb) SB_OVERRIDE;
+  bool ProcessInitEvent() SB_OVERRIDE;
+  bool ProcessSeekEvent(const SeekEventData& data) SB_OVERRIDE;
+  bool ProcessWriteSampleEvent(const WriteSampleEventData& data,
+                               bool* written) SB_OVERRIDE;
+  bool ProcessWriteEndOfStreamEvent(const WriteEndOfStreamEventData& data)
+      SB_OVERRIDE;
+  bool ProcessSetPauseEvent(const SetPauseEventData& data) SB_OVERRIDE;
+  bool ProcessUpdateEvent(const SetBoundsEventData& data) SB_OVERRIDE;
+  void ProcessStopEvent() SB_OVERRIDE;
+
+  PlayerWorker* player_worker_;
+  SbPlayer player_;
+  UpdateMediaTimeCB update_media_time_cb_;
+  GetPlayerStateCB get_player_state_cb_;
+  UpdatePlayerStateCB update_player_state_cb_;
+
+  SbMediaVideoCodec video_codec_;
+  SbMediaAudioCodec audio_codec_;
+  SbDrmSystem drm_system_;
+  SbMediaAudioHeader audio_header_;
+
+  scoped_ptr<AudioRenderer> audio_renderer_;
+  scoped_ptr<VideoRenderer> video_renderer_;
+
+  bool paused_;
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_
diff --git a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
new file mode 100644
index 0000000..e55c685
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
@@ -0,0 +1,76 @@
+// 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_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_
+
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// This class decodes encoded video stream into video frames.
+class VideoDecoder {
+ public:
+  enum Status { kNeedMoreInput, kBufferFull, kFatalError };
+
+  class Host {
+   public:
+    // |frame| can contain a decoded frame or be NULL when |status| is not
+    // kFatalError.   When status is kFatalError, |frame| will be NULL.  Its
+    // user should only call WriteInputFrame() when |status| is kNeedMoreInput
+    // or when the instance is just created.  Also note that calling Reset() or
+    // dtor from this callback will result in deadlock.
+    virtual void OnDecoderStatusUpdate(
+        Status status,
+        const scoped_refptr<VideoFrame>& frame) = 0;
+
+   protected:
+    ~Host() {}
+  };
+
+  virtual ~VideoDecoder() {}
+
+  virtual void SetHost(Host* host) = 0;
+
+  // Send encoded video frame stored in |input_buffer| to decode.
+  virtual void WriteInputBuffer(const InputBuffer& input_buffer) = 0;
+  // Note that there won't be more input data unless Reset() is called.
+  // OnDecoderStatusUpdate will still be called on Host during flushing until
+  // the |frame| is an EOS frame.
+  virtual void WriteEndOfStream() = 0;
+  // Clear any cached buffer of the codec and reset the state of the codec.
+  // This function will be called during seek to ensure that there is no left
+  // over data from previous buffers.  No DecoderStatusFunc call will be made
+  // after this function returns unless WriteInputFrame() or WriteEndOfStream()
+  // is called again.
+  virtual void Reset() = 0;
+
+  // Individual implementation has to implement this function to create a video
+  // decoder.
+  static VideoDecoder* Create(SbMediaVideoCodec video_codec);
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
new file mode 100644
index 0000000..35b6bf0
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
@@ -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.
+
+#include "starboard/shared/starboard/player/filter/video_renderer_internal.h"
+
+#include <algorithm>
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+VideoRenderer::VideoRenderer(scoped_ptr<VideoDecoder> decoder)
+    : seeking_(false),
+      seeking_to_pts_(0),
+      end_of_stream_written_(false),
+      need_more_input_(true),
+      decoder_(decoder.Pass()) {
+  SB_DCHECK(decoder_ != NULL);
+  decoder_->SetHost(this);
+}
+
+void VideoRenderer::WriteSample(const InputBuffer& input_buffer) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (end_of_stream_written_) {
+    SB_LOG(ERROR) << "Appending video sample at " << input_buffer.pts()
+                  << " after EOS reached.";
+    return;
+  }
+
+  {
+    ScopedLock lock(mutex_);
+    need_more_input_ = false;
+  }
+
+  decoder_->WriteInputBuffer(input_buffer);
+}
+
+void VideoRenderer::WriteEndOfStream() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  SB_LOG_IF(WARNING, end_of_stream_written_)
+      << "Try to write EOS after EOS is reached";
+  if (end_of_stream_written_) {
+    return;
+  }
+  end_of_stream_written_ = true;
+  decoder_->WriteEndOfStream();
+}
+
+void VideoRenderer::Seek(SbMediaTime seek_to_pts) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(seek_to_pts >= 0);
+
+  decoder_->Reset();
+
+  ScopedLock lock(mutex_);
+
+  seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
+  seeking_ = true;
+  end_of_stream_written_ = false;
+
+  if (!frames_.empty()) {
+    seeking_frame_ = frames_.front();
+  }
+  frames_.clear();
+}
+
+scoped_refptr<VideoFrame> VideoRenderer::GetCurrentFrame(
+    SbMediaTime media_time) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (frames_.empty()) {
+    return seeking_frame_;
+  }
+  // Remove any frames with timestamps earlier than |media_time|, but always
+  // keep at least one of the frames.
+  while (frames_.size() > 1 && frames_.front()->pts() < media_time) {
+    frames_.pop_front();
+  }
+
+  return frames_.front();
+}
+
+bool VideoRenderer::IsEndOfStreamPlayed() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  return end_of_stream_written_ && frames_.size() <= 1;
+}
+
+bool VideoRenderer::CanAcceptMoreData() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  ScopedLock lock(mutex_);
+  return frames_.size() < kMaxCachedFrames && !end_of_stream_written_ &&
+         need_more_input_;
+}
+
+bool VideoRenderer::IsSeekingInProgress() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  return seeking_;
+}
+
+void VideoRenderer::OnDecoderStatusUpdate(
+    VideoDecoder::Status status,
+    const scoped_refptr<VideoFrame>& frame) {
+  ScopedLock lock(mutex_);
+
+  if (frame) {
+    bool frame_too_early = false;
+    if (seeking_) {
+      if (frame->IsEndOfStream()) {
+        seeking_ = false;
+      } else if (frame->pts() < seeking_to_pts_) {
+        frame_too_early = true;
+      }
+    }
+    if (!frame_too_early) {
+      frames_.push_back(frame);
+    }
+
+    if (seeking_ && frames_.size() >= kPrerollFrames) {
+      seeking_ = false;
+    }
+  }
+
+  need_more_input_ = (status == VideoDecoder::kNeedMoreInput);
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
new file mode 100644
index 0000000..6cc4cad
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
@@ -0,0 +1,96 @@
+// 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_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_
+
+#include <list>
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+class VideoRenderer : private VideoDecoder::Host {
+ public:
+  explicit VideoRenderer(scoped_ptr<VideoDecoder> decoder);
+
+  bool is_valid() const { return true; }
+
+  void WriteSample(const InputBuffer& input_buffer);
+  void WriteEndOfStream();
+
+  void Seek(SbMediaTime seek_to_pts);
+
+  scoped_refptr<VideoFrame> GetCurrentFrame(SbMediaTime media_time);
+
+  bool IsEndOfStreamWritten() const { return end_of_stream_written_; }
+  bool IsEndOfStreamPlayed() const;
+  bool CanAcceptMoreData() const;
+  bool IsSeekingInProgress() const;
+
+ private:
+  typedef std::list<scoped_refptr<VideoFrame> > Frames;
+
+  // Preroll considered finished after either kPrerollFrames is cached or EOS
+  // is reached.
+  static const size_t kPrerollFrames = 1;
+  // Set a soft limit for the max video frames we can cache so we can:
+  // 1. Avoid using too much memory.
+  // 2. Have the frame cache full to simulate the state that the renderer can
+  //    no longer accept more data.
+  static const size_t kMaxCachedFrames = 8;
+
+  // VideoDecoder::Host method.
+  void OnDecoderStatusUpdate(VideoDecoder::Status status,
+                             const scoped_refptr<VideoFrame>& frame)
+      SB_OVERRIDE;
+
+  ThreadChecker thread_checker_;
+  ::starboard::Mutex mutex_;
+
+  bool seeking_;
+  Frames frames_;
+
+  // During seeking, all frames inside |frames_| will be cleared but the app
+  // should still display the last frame it is rendering.  This frame will be
+  // kept inside |seeking_frame_|.  It is an empty/black frame before the video
+  // is started.
+  scoped_refptr<VideoFrame> seeking_frame_;
+
+  SbMediaTime seeking_to_pts_;
+  bool end_of_stream_written_;
+  bool need_more_input_;
+
+  scoped_ptr<VideoDecoder> decoder_;
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.cc b/src/starboard/shared/starboard/player/input_buffer_internal.cc
index 45a65c9..8431fc2 100644
--- a/src/starboard/shared/starboard/player/input_buffer_internal.cc
+++ b/src/starboard/shared/starboard/player/input_buffer_internal.cc
@@ -49,7 +49,14 @@
       video_sample_info_ = *video_sample_info;

     }

     if (has_drm_info_) {

+      SB_DCHECK(sample_drm_info->subsample_count > 0);

+

+      subsamples_.assign(sample_drm_info->subsample_mapping,

+                         sample_drm_info->subsample_mapping +

+                             sample_drm_info->subsample_count);

       drm_info_ = *sample_drm_info;

+      drm_info_.subsample_mapping =

+          subsamples_.empty() ? NULL : &subsamples_[0];

     }

   }

 

@@ -107,6 +114,7 @@
   bool has_drm_info_;

   SbDrmSampleInfo drm_info_;

   std::vector<uint8_t> decrypted_data_;

+  std::vector<SbDrmSubSampleMapping> subsamples_;

 

   SB_DISALLOW_COPY_AND_ASSIGN(ReferenceCountedBuffer);

 };

diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.h b/src/starboard/shared/starboard/player/input_buffer_internal.h
index 273f873..5cc7af6 100644
--- a/src/starboard/shared/starboard/player/input_buffer_internal.h
+++ b/src/starboard/shared/starboard/player/input_buffer_internal.h
@@ -15,6 +15,7 @@
 #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_INPUT_BUFFER_INTERNAL_H_

 #define STARBOARD_SHARED_STARBOARD_PLAYER_INPUT_BUFFER_INTERNAL_H_

 

+#include "starboard/drm.h"

 #include "starboard/media.h"

 #include "starboard/player.h"

 #include "starboard/shared/internal_only.h"

diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc
index 8489cce..c09f526 100644
--- a/src/starboard/shared/starboard/player/player_create.cc
+++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -14,8 +14,16 @@
 
 #include "starboard/player.h"
 
+#include "starboard/configuration.h"
+#include "starboard/decode_target.h"
 #include "starboard/log.h"
+#include "starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h"
 #include "starboard/shared/starboard/player/player_internal.h"
+#include "starboard/shared/starboard/player/player_worker.h"
+
+using starboard::shared::starboard::player::filter::
+    FilterBasedPlayerWorkerHandler;
+using starboard::shared::starboard::player::PlayerWorker;
 
 SbPlayer SbPlayerCreate(SbWindow window,
                         SbMediaVideoCodec video_codec,
@@ -26,7 +34,17 @@
                         SbPlayerDeallocateSampleFunc sample_deallocate_func,
                         SbPlayerDecoderStatusFunc decoder_status_func,
                         SbPlayerStatusFunc player_status_func,
-                        void* context) {
+                        void* context
+#if SB_VERSION(3)
+                        ,
+                        SbDecodeTargetProvider* provider
+#endif
+                        ) {
+  SB_UNREFERENCED_PARAMETER(window);
+#if SB_VERSION(3)
+  SB_UNREFERENCED_PARAMETER(provider);
+#endif
+
   if (audio_codec != kSbMediaAudioCodecAac) {
     SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec;
     return kSbPlayerInvalid;
@@ -42,7 +60,10 @@
     return kSbPlayerInvalid;
   }
 
-  return new SbPlayerPrivate(window, video_codec, audio_codec, duration_pts,
-                             drm_system, audio_header, sample_deallocate_func,
-                             decoder_status_func, player_status_func, context);
+  starboard::scoped_ptr<PlayerWorker::Handler> handler(
+      new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
+                                         *audio_header));
+  return new SbPlayerPrivate(duration_pts, sample_deallocate_func,
+                             decoder_status_func, player_status_func, context,
+                             handler.Pass());
 }
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc
index 2c7c9f6..bc9d74d 100644
--- a/src/starboard/shared/starboard/player/player_internal.cc
+++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -17,28 +17,26 @@
 #include "starboard/log.h"
 
 using starboard::shared::starboard::player::InputBuffer;
-using starboard::shared::starboard::player::PlayerWorker;
 
 namespace {
 
+const SbTime kUpdateInterval = 5 * kSbTimeMillisecond;
+
 SbMediaTime GetMediaTime(SbMediaTime media_pts,
                          SbTimeMonotonic media_pts_update_time) {
   SbTimeMonotonic elapsed = SbTimeGetMonotonicNow() - media_pts_update_time;
   return media_pts + elapsed * kSbMediaTimeSecond / kSbTimeSecond;
 }
-}
+
+}  // namespace
 
 SbPlayerPrivate::SbPlayerPrivate(
-    SbWindow window,
-    SbMediaVideoCodec video_codec,
-    SbMediaAudioCodec audio_codec,
     SbMediaTime duration_pts,
-    SbDrmSystem drm_system,
-    const SbMediaAudioHeader* audio_header,
     SbPlayerDeallocateSampleFunc sample_deallocate_func,
     SbPlayerDecoderStatusFunc decoder_status_func,
     SbPlayerStatusFunc player_status_func,
-    void* context)
+    void* context,
+    starboard::scoped_ptr<PlayerWorker::Handler> player_worker_handler)
     : sample_deallocate_func_(sample_deallocate_func),
       context_(context),
       ticket_(SB_PLAYER_INITIAL_TICKET),
@@ -50,16 +48,13 @@
       is_paused_(true),
       volume_(1.0),
       total_video_frames_(0),
-      worker_(this,
-              window,
-              video_codec,
-              audio_codec,
-              drm_system,
-              *audio_header,
-              decoder_status_func,
-              player_status_func,
-              this,
-              context) {}
+      worker_(new PlayerWorker(this,
+                               player_worker_handler.Pass(),
+                               decoder_status_func,
+                               player_status_func,
+                               this,
+                               kUpdateInterval,
+                               context)) {}
 
 void SbPlayerPrivate::Seek(SbMediaTime seek_to_pts, int ticket) {
   {
@@ -71,7 +66,7 @@
   }
 
   PlayerWorker::SeekEventData data = {seek_to_pts, ticket};
-  worker_.EnqueueEvent(data);
+  worker_->EnqueueEvent(data);
 }
 
 void SbPlayerPrivate::WriteSample(
@@ -88,18 +83,18 @@
       sample_deallocate_func_, this, context_, sample_buffer,
       sample_buffer_size, sample_pts, video_sample_info, sample_drm_info);
   PlayerWorker::WriteSampleEventData data = {sample_type, input_buffer};
-  worker_.EnqueueEvent(data);
+  worker_->EnqueueEvent(data);
 }
 
 void SbPlayerPrivate::WriteEndOfStream(SbMediaType stream_type) {
   PlayerWorker::WriteEndOfStreamEventData data = {stream_type};
-  worker_.EnqueueEvent(data);
+  worker_->EnqueueEvent(data);
 }
 
 #if SB_IS(PLAYER_PUNCHED_OUT)
 void SbPlayerPrivate::SetBounds(int x, int y, int width, int height) {
   PlayerWorker::SetBoundsEventData data = {x, y, width, height};
-  worker_.EnqueueEvent(data);
+  worker_->EnqueueEvent(data);
   // TODO: Wait until a frame is rendered with the updated bounds.
 }
 #endif
@@ -126,7 +121,7 @@
 
 void SbPlayerPrivate::SetPause(bool pause) {
   PlayerWorker::SetPauseEventData data = {pause};
-  worker_.EnqueueEvent(data);
+  worker_->EnqueueEvent(data);
 }
 
 void SbPlayerPrivate::SetVolume(double volume) {
diff --git a/src/starboard/shared/starboard/player/player_internal.h b/src/starboard/shared/starboard/player/player_internal.h
index 7ecc299..4a8a917 100644
--- a/src/starboard/shared/starboard/player/player_internal.h
+++ b/src/starboard/shared/starboard/player/player_internal.h
@@ -15,6 +15,7 @@
 #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_
 #define STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_
 
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/media.h"
 #include "starboard/player.h"
 #include "starboard/shared/internal_only.h"
@@ -22,20 +23,18 @@
 #include "starboard/time.h"
 #include "starboard/window.h"
 
-// TODO: Implement DRM support
 struct SbPlayerPrivate
     : starboard::shared::starboard::player::PlayerWorker::Host {
  public:
-  SbPlayerPrivate(SbWindow window,
-                  SbMediaVideoCodec video_codec,
-                  SbMediaAudioCodec audio_codec,
-                  SbMediaTime duration_pts,
-                  SbDrmSystem drm_system,
-                  const SbMediaAudioHeader* audio_header,
-                  SbPlayerDeallocateSampleFunc sample_deallocate_func,
-                  SbPlayerDecoderStatusFunc decoder_status_func,
-                  SbPlayerStatusFunc player_status_func,
-                  void* context);
+  typedef starboard::shared::starboard::player::PlayerWorker PlayerWorker;
+
+  SbPlayerPrivate(
+      SbMediaTime duration_pts,
+      SbPlayerDeallocateSampleFunc sample_deallocate_func,
+      SbPlayerDecoderStatusFunc decoder_status_func,
+      SbPlayerStatusFunc player_status_func,
+      void* context,
+      starboard::scoped_ptr<PlayerWorker::Handler> player_worker_handler);
 
   void Seek(SbMediaTime seek_to_pts, int ticket);
   void WriteSample(SbMediaType sample_type,
@@ -71,7 +70,7 @@
   double volume_;
   int total_video_frames_;
 
-  starboard::shared::starboard::player::PlayerWorker worker_;
+  starboard::scoped_ptr<PlayerWorker> worker_;
 };
 
 #endif  // STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index 5bab32b..51c869d 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -14,14 +14,8 @@
 
 #include "starboard/shared/starboard/player/player_worker.h"
 
-#include <algorithm>
-
-#include "starboard/shared/starboard/application.h"
-#include "starboard/shared/starboard/drm/drm_system_internal.h"
-#include "starboard/shared/starboard/player/audio_decoder_internal.h"
-#include "starboard/shared/starboard/player/input_buffer_internal.h"
-#include "starboard/shared/starboard/player/video_decoder_internal.h"
-#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/common/reset_and_return.h"
+#include "starboard/memory.h"
 
 namespace starboard {
 namespace shared {
@@ -29,34 +23,34 @@
 namespace player {
 
 PlayerWorker::PlayerWorker(Host* host,
-                           SbWindow window,
-                           SbMediaVideoCodec video_codec,
-                           SbMediaAudioCodec audio_codec,
-                           SbDrmSystem drm_system,
-                           const SbMediaAudioHeader& audio_header,
+                           scoped_ptr<Handler> handler,
                            SbPlayerDecoderStatusFunc decoder_status_func,
                            SbPlayerStatusFunc player_status_func,
                            SbPlayer player,
+                           SbTime update_interval,
                            void* context)
-    : host_(host),
-      window_(window),
-      video_codec_(video_codec),
-      audio_codec_(audio_codec),
-      drm_system_(drm_system),
-      audio_header_(audio_header),
+    : thread_(kSbThreadInvalid),
+      host_(host),
+      handler_(handler.Pass()),
       decoder_status_func_(decoder_status_func),
       player_status_func_(player_status_func),
       player_(player),
       context_(context),
-      audio_renderer_(NULL),
-      video_renderer_(NULL),
-      audio_decoder_state_(kSbPlayerDecoderStateBufferFull),
-      video_decoder_state_(kSbPlayerDecoderStateBufferFull),
       ticket_(SB_PLAYER_INITIAL_TICKET),
-      paused_(true),
-      player_state_(kSbPlayerStateInitialized) {
-  SB_DCHECK(host != NULL);
+      player_state_(kSbPlayerStateInitialized),
+      update_interval_(update_interval) {
+  SB_DCHECK(host_ != NULL);
+  SB_DCHECK(handler_ != NULL);
+  SB_DCHECK(update_interval_ > 0);
 
+  handler_->Setup(this, player_, &PlayerWorker::UpdateMediaTime,
+                  &PlayerWorker::player_state,
+                  &PlayerWorker::UpdatePlayerState);
+
+  pending_audio_sample_.input_buffer = NULL;
+  pending_video_sample_.input_buffer = NULL;
+
+  SB_DCHECK(!SbThreadIsValid(thread_));
   thread_ =
       SbThreadCreate(0, kSbThreadPriorityHigh, kSbThreadNoAffinity, true,
                      "player_worker", &PlayerWorker::ThreadEntryPoint, this);
@@ -68,9 +62,41 @@
 PlayerWorker::~PlayerWorker() {
   queue_.Put(new Event(Event::kStop));
   SbThreadJoin(thread_, NULL);
+  thread_ = kSbThreadInvalid;
+
   // Now the whole pipeline has been torn down and no callback will be called.
   // The caller can ensure that upon the return of SbPlayerDestroy() all side
   // effects are gone.
+
+  // There can be events inside the queue that is not processed.  Clean them up.
+  while (Event* event = queue_.GetTimed(0)) {
+    if (event->type == Event::kWriteSample) {
+      delete event->data.write_sample.input_buffer;
+    }
+    delete event;
+  }
+
+  if (pending_audio_sample_.input_buffer != NULL) {
+    delete pending_audio_sample_.input_buffer;
+  }
+
+  if (pending_video_sample_.input_buffer != NULL) {
+    delete pending_video_sample_.input_buffer;
+  }
+}
+
+void PlayerWorker::UpdateMediaTime(SbMediaTime time) {
+  host_->UpdateMediaTime(time, ticket_);
+}
+
+void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) {
+  player_state_ = player_state;
+
+  if (!player_status_func_) {
+    return;
+  }
+
+  player_status_func_(player_, context_, player_state_, ticket_);
 }
 
 // static
@@ -85,38 +111,35 @@
   SB_DCHECK(event != NULL);
   SB_DCHECK(event->type == Event::kInit);
   delete event;
-  bool running = ProcessInitEvent();
+  bool running = DoInit();
 
   SetBoundsEventData bounds = {0, 0, 0, 0};
 
   while (running) {
-    Event* event = queue_.GetTimed(kUpdateInterval);
+    Event* event = queue_.GetTimed(update_interval_);
     if (event == NULL) {
-      running &= ProcessUpdateEvent(bounds);
+      running &= DoUpdate(bounds);
       continue;
     }
 
     SB_DCHECK(event->type != Event::kInit);
 
     if (event->type == Event::kSeek) {
-      running &= ProcessSeekEvent(event->data.seek);
+      running &= DoSeek(event->data.seek);
     } else if (event->type == Event::kWriteSample) {
-      bool retry;
-      running &= ProcessWriteSampleEvent(event->data.write_sample, &retry);
-      if (retry && running) {
-        EnqueueEvent(event->data.write_sample);
-      } else {
-        delete event->data.write_sample.input_buffer;
-      }
+      running &= DoWriteSample(event->data.write_sample);
     } else if (event->type == Event::kWriteEndOfStream) {
-      running &= ProcessWriteEndOfStreamEvent(event->data.write_end_of_stream);
+      running &= DoWriteEndOfStream(event->data.write_end_of_stream);
     } else if (event->type == Event::kSetPause) {
-      running &= ProcessSetPauseEvent(event->data.set_pause);
+      running &= handler_->ProcessSetPauseEvent(event->data.set_pause);
     } else if (event->type == Event::kSetBounds) {
-      bounds = event->data.set_bounds;
-      ProcessUpdateEvent(bounds);
+      if (SbMemoryCompare(&bounds, &event->data.set_bounds, sizeof(bounds)) !=
+          0) {
+        bounds = event->data.set_bounds;
+        running &= DoUpdate(bounds);
+      }
     } else if (event->type == Event::kStop) {
-      ProcessStopEvent();
+      DoStop();
       running = false;
     } else {
       SB_NOTREACHED() << "event type " << event->type;
@@ -125,34 +148,17 @@
   }
 }
 
-bool PlayerWorker::ProcessInitEvent() {
-  AudioDecoder* audio_decoder =
-      AudioDecoder::Create(audio_codec_, audio_header_);
-  VideoDecoder* video_decoder = VideoDecoder::Create(video_codec_);
-
-  if (!audio_decoder || !video_decoder) {
-    delete audio_decoder;
-    delete video_decoder;
-    UpdatePlayerState(kSbPlayerStateError);
-    return false;
-  }
-
-  audio_renderer_ = new AudioRenderer(audio_decoder, audio_header_);
-  video_renderer_ = new VideoRenderer(video_decoder);
-  if (audio_renderer_->is_valid() && video_renderer_->is_valid()) {
+bool PlayerWorker::DoInit() {
+  if (handler_->ProcessInitEvent()) {
     UpdatePlayerState(kSbPlayerStateInitialized);
     return true;
   }
 
-  delete audio_renderer_;
-  audio_renderer_ = NULL;
-  delete video_renderer_;
-  video_renderer_ = NULL;
   UpdatePlayerState(kSbPlayerStateError);
   return false;
 }
 
-bool PlayerWorker::ProcessSeekEvent(const SeekEventData& data) {
+bool PlayerWorker::DoSeek(const SeekEventData& data) {
   SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
   SB_DCHECK(player_state_ != kSbPlayerStateError);
   SB_DCHECK(ticket_ != data.ticket);
@@ -160,106 +166,66 @@
   SB_DLOG(INFO) << "Try to seek to timestamp "
                 << data.seek_to_pts / kSbMediaTimeSecond;
 
-  SbMediaTime seek_to_pts = data.seek_to_pts;
-  if (seek_to_pts < 0) {
-    SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_pts;
-    seek_to_pts = 0;
+  if (pending_audio_sample_.input_buffer != NULL) {
+    delete pending_audio_sample_.input_buffer;
+    pending_audio_sample_.input_buffer = NULL;
+  }
+  if (pending_video_sample_.input_buffer != NULL) {
+    delete pending_video_sample_.input_buffer;
+    pending_video_sample_.input_buffer = NULL;
   }
 
-  audio_renderer_->Pause();
-  audio_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-  video_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-  audio_renderer_->Seek(seek_to_pts);
-  video_renderer_->Seek(seek_to_pts);
+  if (!handler_->ProcessSeekEvent(data)) {
+    return false;
+  }
 
   ticket_ = data.ticket;
 
   UpdatePlayerState(kSbPlayerStatePrerolling);
-  UpdateDecoderState(kSbMediaTypeAudio);
-  UpdateDecoderState(kSbMediaTypeVideo);
+  UpdateDecoderState(kSbMediaTypeAudio, kSbPlayerDecoderStateNeedsData);
+  UpdateDecoderState(kSbMediaTypeVideo, kSbPlayerDecoderStateNeedsData);
+
   return true;
 }
 
-bool PlayerWorker::ProcessWriteSampleEvent(const WriteSampleEventData& data,
-                                           bool* retry) {
-  SB_DCHECK(retry != NULL);
+bool PlayerWorker::DoWriteSample(const WriteSampleEventData& data) {
   SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
   SB_DCHECK(player_state_ != kSbPlayerStateError);
 
-  *retry = false;
-
   if (player_state_ == kSbPlayerStateInitialized ||
       player_state_ == kSbPlayerStateEndOfStream ||
       player_state_ == kSbPlayerStateError) {
     SB_LOG(ERROR) << "Try to write sample when |player_state_| is "
                   << player_state_;
+    delete data.input_buffer;
     // Return true so the pipeline will continue running with the particular
     // call ignored.
     return true;
   }
 
   if (data.sample_type == kSbMediaTypeAudio) {
-    if (audio_renderer_->IsEndOfStreamWritten()) {
-      SB_LOG(WARNING) << "Try to write audio sample after EOS is reached";
+    SB_DCHECK(pending_audio_sample_.input_buffer == NULL);
+  } else {
+    SB_DCHECK(pending_video_sample_.input_buffer == NULL);
+  }
+  bool written;
+  bool result = handler_->ProcessWriteSampleEvent(data, &written);
+  if (!written && result) {
+    if (data.sample_type == kSbMediaTypeAudio) {
+      pending_audio_sample_ = data;
     } else {
-      SB_DCHECK(audio_renderer_->CanAcceptMoreData());
-
-      if (data.input_buffer->drm_info()) {
-        if (!SbDrmSystemIsValid(drm_system_)) {
-          return false;
-        }
-        if (drm_system_->Decrypt(data.input_buffer) ==
-            SbDrmSystemPrivate::kRetry) {
-          *retry = true;
-          return true;
-        }
-      }
-      audio_renderer_->WriteSample(*data.input_buffer);
-      if (audio_renderer_->CanAcceptMoreData()) {
-        audio_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-      } else {
-        audio_decoder_state_ = kSbPlayerDecoderStateBufferFull;
-      }
-      UpdateDecoderState(kSbMediaTypeAudio);
+      pending_video_sample_ = data;
     }
   } else {
-    SB_DCHECK(data.sample_type == kSbMediaTypeVideo);
-    if (video_renderer_->IsEndOfStreamWritten()) {
-      SB_LOG(WARNING) << "Try to write video sample after EOS is reached";
-    } else {
-      SB_DCHECK(video_renderer_->CanAcceptMoreData());
-      if (data.input_buffer->drm_info()) {
-        if (!SbDrmSystemIsValid(drm_system_)) {
-          return false;
-        }
-        if (drm_system_->Decrypt(data.input_buffer) ==
-            SbDrmSystemPrivate::kRetry) {
-          *retry = true;
-          return true;
-        }
-      }
-      video_renderer_->WriteSample(*data.input_buffer);
-      if (video_renderer_->CanAcceptMoreData()) {
-        video_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-      } else {
-        video_decoder_state_ = kSbPlayerDecoderStateBufferFull;
-      }
-      UpdateDecoderState(kSbMediaTypeVideo);
+    delete data.input_buffer;
+    if (result) {
+      UpdateDecoderState(data.sample_type, kSbPlayerDecoderStateNeedsData);
     }
   }
-
-  if (player_state_ == kSbPlayerStatePrerolling) {
-    if (!audio_renderer_->IsSeekingInProgress() &&
-        !video_renderer_->IsSeekingInProgress()) {
-      UpdatePlayerState(kSbPlayerStatePresenting);
-    }
-  }
-
-  return true;
+  return result;
 }
 
-bool PlayerWorker::ProcessWriteEndOfStreamEvent(
-    const WriteEndOfStreamEventData& data) {
+bool PlayerWorker::DoWriteEndOfStream(const WriteEndOfStreamEventData& data) {
   SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
 
   if (player_state_ == kSbPlayerStateInitialized ||
@@ -273,124 +239,47 @@
   }
 
   if (data.stream_type == kSbMediaTypeAudio) {
-    if (audio_renderer_->IsEndOfStreamWritten()) {
-      SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued";
-    } else {
-      SB_LOG(INFO) << "Audio EOS enqueued";
-      audio_renderer_->WriteEndOfStream();
-    }
-    audio_decoder_state_ = kSbPlayerDecoderStateBufferFull;
-    UpdateDecoderState(kSbMediaTypeAudio);
+    SB_DCHECK(pending_audio_sample_.input_buffer == NULL);
   } else {
-    if (video_renderer_->IsEndOfStreamWritten()) {
-      SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued";
-    } else {
-      SB_LOG(INFO) << "Video EOS enqueued";
-      video_renderer_->WriteEndOfStream();
-    }
-    video_decoder_state_ = kSbPlayerDecoderStateBufferFull;
-    UpdateDecoderState(kSbMediaTypeVideo);
+    SB_DCHECK(pending_video_sample_.input_buffer == NULL);
   }
 
-  if (player_state_ == kSbPlayerStatePrerolling) {
-    if (!audio_renderer_->IsSeekingInProgress() &&
-        !video_renderer_->IsSeekingInProgress()) {
-      UpdatePlayerState(kSbPlayerStatePresenting);
-    }
-  }
-
-  if (player_state_ == kSbPlayerStatePresenting) {
-    if (audio_renderer_->IsEndOfStreamPlayed() &&
-        video_renderer_->IsEndOfStreamPlayed()) {
-      UpdatePlayerState(kSbPlayerStateEndOfStream);
-    }
+  if (!handler_->ProcessWriteEndOfStreamEvent(data)) {
+    return false;
   }
 
   return true;
 }
 
-bool PlayerWorker::ProcessSetPauseEvent(const SetPauseEventData& data) {
-  // TODO: Check valid
-  paused_ = data.pause;
-  if (data.pause) {
-    audio_renderer_->Pause();
-    SB_DLOG(INFO) << "Playback paused.";
-  } else {
-    audio_renderer_->Play();
-    SB_DLOG(INFO) << "Playback started.";
+bool PlayerWorker::DoUpdate(const SetBoundsEventData& data) {
+  if (pending_audio_sample_.input_buffer != NULL) {
+    if (!DoWriteSample(common::ResetAndReturn(&pending_audio_sample_))) {
+      return false;
+    }
   }
-
-  return true;
+  if (pending_video_sample_.input_buffer != NULL) {
+    if (!DoWriteSample(common::ResetAndReturn(&pending_video_sample_))) {
+      return false;
+    }
+  }
+  return handler_->ProcessUpdateEvent(data);
 }
 
-bool PlayerWorker::ProcessUpdateEvent(const SetBoundsEventData& bounds) {
-  SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
+void PlayerWorker::DoStop() {
+  handler_->ProcessStopEvent();
 
-  if (player_state_ == kSbPlayerStatePrerolling ||
-      player_state_ == kSbPlayerStatePresenting) {
-    if (audio_renderer_->IsEndOfStreamPlayed() &&
-        video_renderer_->IsEndOfStreamPlayed()) {
-      UpdatePlayerState(kSbPlayerStateEndOfStream);
-    }
-
-    const VideoFrame& frame =
-        video_renderer_->GetCurrentFrame(audio_renderer_->GetCurrentTime());
-#if SB_IS(PLAYER_PUNCHED_OUT)
-    shared::starboard::Application::Get()->HandleFrame(
-        player_, frame, bounds.x, bounds.y, bounds.width, bounds.height);
-#endif  // SB_IS(PLAYER_PUNCHED_OUT)
-
-    if (audio_decoder_state_ == kSbPlayerDecoderStateBufferFull &&
-        audio_renderer_->CanAcceptMoreData()) {
-      audio_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-      UpdateDecoderState(kSbMediaTypeAudio);
-    }
-    if (video_decoder_state_ == kSbPlayerDecoderStateBufferFull &&
-        video_renderer_->CanAcceptMoreData()) {
-      video_decoder_state_ = kSbPlayerDecoderStateNeedsData;
-      UpdateDecoderState(kSbMediaTypeVideo);
-    }
-
-    host_->UpdateMediaTime(audio_renderer_->GetCurrentTime(), ticket_);
-  }
-
-  return true;
-}
-
-void PlayerWorker::ProcessStopEvent() {
-  delete audio_renderer_;
-  audio_renderer_ = NULL;
-  delete video_renderer_;
-  video_renderer_ = NULL;
-#if SB_IS(PLAYER_PUNCHED_OUT)
-  // Clear the video frame as we terminate.
-  shared::starboard::Application::Get()->HandleFrame(player_, VideoFrame(), 0,
-                                                     0, 0, 0);
-#endif  // SB_IS(PLAYER_PUNCHED_OUT)
   UpdatePlayerState(kSbPlayerStateDestroyed);
 }
 
-void PlayerWorker::UpdateDecoderState(SbMediaType type) {
+void PlayerWorker::UpdateDecoderState(SbMediaType type,
+                                      SbPlayerDecoderState state) {
+  SB_DCHECK(type == kSbMediaTypeAudio || type == kSbMediaTypeVideo);
+
   if (!decoder_status_func_) {
     return;
   }
-  SB_DCHECK(type == kSbMediaTypeAudio || type == kSbMediaTypeVideo);
-  decoder_status_func_(
-      player_, context_, type,
-      type == kSbMediaTypeAudio ? audio_decoder_state_ : video_decoder_state_,
-      ticket_);
-}
 
-void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) {
-  if (!player_status_func_) {
-    return;
-  }
-
-  player_state_ = player_state;
-  if (player_state == kSbPlayerStatePresenting && !paused_) {
-    audio_renderer_->Play();
-  }
-  player_status_func_(player_, context_, player_state_, ticket_);
+  decoder_status_func_(player_, context_, type, state, ticket_);
 }
 
 }  // namespace player
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h
index 843806f..d8bbb70 100644
--- a/src/starboard/shared/starboard/player/player_worker.h
+++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -15,13 +15,13 @@
 #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_WORKER_H_
 #define STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_WORKER_H_
 
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/log.h"
 #include "starboard/media.h"
 #include "starboard/player.h"
 #include "starboard/queue.h"
 #include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/audio_renderer_internal.h"
-#include "starboard/shared/starboard/player/video_renderer_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
 #include "starboard/thread.h"
 #include "starboard/time.h"
 #include "starboard/window.h"
@@ -31,6 +31,12 @@
 namespace starboard {
 namespace player {
 
+// This class creates a thread that executes events posted to an internally
+// created queue. This guarantees that all such events are processed on the same
+// thread.
+//
+// This class serves as the base class for platform specific PlayerWorkers so
+// they needn't maintain the thread and queue internally.
 class PlayerWorker {
  public:
   class Host {
@@ -78,6 +84,9 @@
       kStop,
     };
 
+    // TODO: No longer use a union so individual members can have non trivial
+    // ctor and WriteSampleEventData::input_buffer no longer has to be a
+    // pointer.
     union Data {
       SeekEventData seek;
       WriteSampleEventData write_sample;
@@ -116,63 +125,90 @@
     Data data;
   };
 
-  static const SbTime kUpdateInterval = 5 * kSbTimeMillisecond;
+  class Handler {
+   public:
+    typedef void (PlayerWorker::*UpdateMediaTimeCB)(SbMediaTime media_time);
+    typedef SbPlayerState (PlayerWorker::*GetPlayerStateCB)() const;
+    typedef void (PlayerWorker::*UpdatePlayerStateCB)(
+        SbPlayerState player_state);
+
+    typedef PlayerWorker::SeekEventData SeekEventData;
+    typedef PlayerWorker::WriteSampleEventData WriteSampleEventData;
+    typedef PlayerWorker::WriteEndOfStreamEventData WriteEndOfStreamEventData;
+    typedef PlayerWorker::SetPauseEventData SetPauseEventData;
+    typedef PlayerWorker::SetBoundsEventData SetBoundsEventData;
+
+    virtual ~Handler() {}
+
+    // This function will be called once inside PlayerWorker's ctor to setup the
+    // callbacks required by the Handler.
+    virtual void Setup(PlayerWorker* player_worker,
+                       SbPlayer player,
+                       UpdateMediaTimeCB update_media_time_cb,
+                       GetPlayerStateCB get_player_state_cb,
+                       UpdatePlayerStateCB update_player_state_cb) = 0;
+
+    // All the Process* functions return false to signal a fatal error.  The
+    // event processing loop in PlayerWorker will termimate in this case.
+    virtual bool ProcessInitEvent() = 0;
+    virtual bool ProcessSeekEvent(const SeekEventData& data) = 0;
+    virtual bool ProcessWriteSampleEvent(const WriteSampleEventData& data,
+                                         bool* written) = 0;
+    virtual bool ProcessWriteEndOfStreamEvent(
+        const WriteEndOfStreamEventData& data) = 0;
+    virtual bool ProcessSetPauseEvent(const SetPauseEventData& data) = 0;
+    virtual bool ProcessUpdateEvent(const SetBoundsEventData& data) = 0;
+    virtual void ProcessStopEvent() = 0;
+  };
 
   PlayerWorker(Host* host,
-               SbWindow window,
-               SbMediaVideoCodec video_codec,
-               SbMediaAudioCodec audio_codec,
-               SbDrmSystem drm_system,
-               const SbMediaAudioHeader& audio_header,
+               scoped_ptr<Handler> handler,
                SbPlayerDecoderStatusFunc decoder_status_func,
                SbPlayerStatusFunc player_status_func,
                SbPlayer player,
+               SbTime update_interval,
                void* context);
-  ~PlayerWorker();
+  virtual ~PlayerWorker();
 
   template <typename EventData>
   void EnqueueEvent(const EventData& event_data) {
+    SB_DCHECK(SbThreadIsValid(thread_));
     queue_.Put(new Event(event_data));
   }
 
  private:
+  void UpdateMediaTime(SbMediaTime time);
+
+  SbPlayerState player_state() const { return player_state_; }
+  void UpdatePlayerState(SbPlayerState player_state);
+
   static void* ThreadEntryPoint(void* context);
   void RunLoop();
+  bool DoInit();
+  bool DoSeek(const SeekEventData& data);
+  bool DoWriteSample(const WriteSampleEventData& data);
+  bool DoWriteEndOfStream(const WriteEndOfStreamEventData& data);
+  bool DoUpdate(const SetBoundsEventData& data);
+  void DoStop();
 
-  bool ProcessInitEvent();
-  bool ProcessSeekEvent(const SeekEventData& data);
-  bool ProcessWriteSampleEvent(const WriteSampleEventData& data, bool* retry);
-  bool ProcessWriteEndOfStreamEvent(const WriteEndOfStreamEventData& data);
-  bool ProcessSetPauseEvent(const SetPauseEventData& data);
-  bool ProcessUpdateEvent(const SetBoundsEventData& bounds);
-  void ProcessStopEvent();
-
-  void UpdateDecoderState(SbMediaType type);
-  void UpdatePlayerState(SbPlayerState player_state);
+  void UpdateDecoderState(SbMediaType type, SbPlayerDecoderState state);
 
   SbThread thread_;
   Queue<Event*> queue_;
 
   Host* host_;
+  scoped_ptr<Handler> handler_;
 
-  SbWindow window_;
-  SbMediaVideoCodec video_codec_;
-  SbMediaAudioCodec audio_codec_;
-  SbDrmSystem drm_system_;
-  SbMediaAudioHeader audio_header_;
   SbPlayerDecoderStatusFunc decoder_status_func_;
   SbPlayerStatusFunc player_status_func_;
   SbPlayer player_;
   void* context_;
-
-  AudioRenderer* audio_renderer_;
-  VideoRenderer* video_renderer_;
-  SbPlayerDecoderState audio_decoder_state_;
-  SbPlayerDecoderState video_decoder_state_;
-
-  bool paused_;
   int ticket_;
+
   SbPlayerState player_state_;
+  const SbTime update_interval_;
+  WriteSampleEventData pending_audio_sample_;
+  WriteSampleEventData pending_video_sample_;
 };
 
 }  // namespace player
diff --git a/src/starboard/shared/starboard/player/video_decoder_internal.h b/src/starboard/shared/starboard/player/video_decoder_internal.h
deleted file mode 100644
index 444f049..0000000
--- a/src/starboard/shared/starboard/player/video_decoder_internal.h
+++ /dev/null
@@ -1,72 +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_PLAYER_VIDEO_DECODER_INTERNAL_H_
-#define STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_DECODER_INTERNAL_H_
-
-#include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/input_buffer_internal.h"
-#include "starboard/shared/starboard/player/video_frame_internal.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-// This class decodes encoded video stream into video frames.
-class VideoDecoder {
- public:
-  enum Status { kNeedMoreInput, kBufferFull, kFatalError };
-
-  class Host {
-   public:
-    // |frame| can contain a decoded frame or be NULL when |status| is not
-    // kFatalError.   When status is kFatalError, |frame| will be NULL.  Its
-    // user should only call WriteInputFrame() when |status| is kNeedMoreInput
-    // or when the instance is just created.  Also note that calling Reset() or
-    // dtor from this callback will result in deadlock.
-    virtual void OnDecoderStatusUpdate(Status status, VideoFrame* frame) = 0;
-
-   protected:
-    ~Host() {}
-  };
-
-  virtual ~VideoDecoder() {}
-
-  virtual void SetHost(Host* host) = 0;
-
-  // Send encoded video frame stored in |input_buffer| to decode.
-  virtual void WriteInputBuffer(const InputBuffer& input_buffer) = 0;
-  // Note that there won't be more input data unless Reset() is called.
-  // OnDecoderStatusUpdate will still be called on Host during flushing until
-  // the |frame| is an EOS frame.
-  virtual void WriteEndOfStream() = 0;
-  // Clear any cached buffer of the codec and reset the state of the codec.
-  // This function will be called during seek to ensure that there is no left
-  // over data from previous buffers.  No DecoderStatusFunc call will be made
-  // after this function returns unless WriteInputFrame() or WriteEndOfStream()
-  // is called again.
-  virtual void Reset() = 0;
-
-  // Individual implementation has to implement this function to create a video
-  // decoder.
-  static VideoDecoder* Create(SbMediaVideoCodec video_codec);
-};
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/video_frame_internal.cc b/src/starboard/shared/starboard/player/video_frame_internal.cc
index f13dc3b..7bee2c4 100644
--- a/src/starboard/shared/starboard/player/video_frame_internal.cc
+++ b/src/starboard/shared/starboard/player/video_frame_internal.cc
@@ -30,7 +30,7 @@
 int s_u_to_g[256];

 int s_v_to_g[256];

 int s_u_to_b[256];

-uint8_t s_clamp_table[256 * 3];

+uint8_t s_clamp_table[256 * 5];

 

 void EnsureYUVToRGBLookupTableInitialized() {

   if (s_yuv_to_rgb_lookup_table_initialized) {

@@ -47,10 +47,13 @@
   //

   // We optimize the conversion algorithm by creating two kinds of lookup

   // tables.  The color component table contains pre-calculated color component

-  // values.  The clamp table contains a map between |v| + 256 to the clamped

+  // values.  The clamp table contains a map between |v| + 512 to the clamped

   // |v| to avoid conditional operation.

-  SbMemorySet(s_clamp_table, 0, 256);

-  SbMemorySet(s_clamp_table + 512, 0xff, 256);

+  // The minimum value of |v| can be 2.112f * (-128) = -271, the maximum value

+  // of |v| can be 1.164f * 255 + 2.112f * 127 = 565.  So we need 512 bytes at

+  // each side of the clamp buffer.

+  SbMemorySet(s_clamp_table, 0, 512);

+  SbMemorySet(s_clamp_table + 768, 0xff, 512);

 

   for (int i = 0; i < 256; ++i) {

     s_y_to_rgb[i] = (static_cast<uint8_t>(i) - 16) * 1.164f;

@@ -58,65 +61,88 @@
     s_u_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.213;

     s_v_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.533f;

     s_u_to_b[i] = (static_cast<uint8_t>(i) - 128) * 2.112f;

-    s_clamp_table[256 + i] = i;

+    s_clamp_table[512 + i] = i;

   }

 

   s_yuv_to_rgb_lookup_table_initialized = true;

 }

 

 uint8_t ClampColorComponent(int component) {

-  return s_clamp_table[component + 256];

+  return s_clamp_table[component + 512];

 }

 

 }  // namespace

 

-VideoFrame::VideoFrame(const VideoFrame& that) {

-  *this = that;

+VideoFrame::VideoFrame() {

+  InitializeToInvalidFrame();

 }

 

-VideoFrame& VideoFrame::operator=(const VideoFrame& that) {

-  this->format_ = that.format_;

+VideoFrame::VideoFrame(int width,

+                       int height,

+                       SbMediaTime pts,

+                       void* native_texture,

+                       void* native_texture_context,

+                       FreeNativeTextureFunc free_native_texture_func) {

+  SB_DCHECK(native_texture != NULL);

+  SB_DCHECK(free_native_texture_func != NULL);

 

-  this->pts_ = that.pts_;

-  this->planes_ = that.planes_;

-  this->pixel_buffer_ = that.pixel_buffer_;

+  InitializeToInvalidFrame();

 

-  for (int i = 0; i < GetPlaneCount(); ++i) {

-    const uint8_t* data = that.GetPlane(i).data;

-    const uint8_t* base = &that.pixel_buffer_[0];

-    ptrdiff_t offset = data - base;

-    SB_DCHECK(offset >= 0);

-    SB_DCHECK(offset < static_cast<ptrdiff_t>(that.pixel_buffer_.size()));

-    planes_[i].data = &pixel_buffer_[0] + offset;

+  format_ = kNativeTexture;

+  width_ = width;

+  height_ = height;

+  pts_ = pts;

+  native_texture_ = native_texture;

+  native_texture_context_ = native_texture_context;

+  free_native_texture_func_ = free_native_texture_func;

+}

+

+VideoFrame::~VideoFrame() {

+  if (format_ == kNativeTexture) {

+    free_native_texture_func_(native_texture_context_, native_texture_);

   }

+}

 

-  return *this;

+int VideoFrame::GetPlaneCount() const {

+  SB_DCHECK(format_ != kInvalid);

+  SB_DCHECK(format_ != kNativeTexture);

+

+  return static_cast<int>(planes_.size());

 }

 

 const VideoFrame::Plane& VideoFrame::GetPlane(int index) const {

+  SB_DCHECK(format_ != kInvalid);

+  SB_DCHECK(format_ != kNativeTexture);

   SB_DCHECK(index >= 0 && index < GetPlaneCount()) << "Invalid index: "

                                                    << index;

   return planes_[index];

 }

 

-VideoFrame VideoFrame::ConvertTo(Format target_format) const {

+void* VideoFrame::native_texture() const {

+  SB_DCHECK(format_ == kNativeTexture);

+  return native_texture_;

+}

+

+scoped_refptr<VideoFrame> VideoFrame::ConvertTo(Format target_format) const {

   SB_DCHECK(format_ == kYV12);

   SB_DCHECK(target_format == kBGRA32);

 

   EnsureYUVToRGBLookupTableInitialized();

 

-  VideoFrame target_frame;

+  scoped_refptr<VideoFrame> target_frame(new VideoFrame);

 

-  target_frame.format_ = target_format;

-  target_frame.pts_ = pts_;

-  target_frame.pixel_buffer_.resize(width() * height() * 4);

-  target_frame.planes_.push_back(

-      Plane(width(), height(), width() * 4, &target_frame.pixel_buffer_[0]));

+  target_frame->format_ = target_format;

+  target_frame->width_ = width();

+  target_frame->height_ = height();

+  target_frame->pts_ = pts_;

+  target_frame->pixel_buffer_.reset(new uint8_t[width() * height() * 4]);

+  target_frame->planes_.push_back(

+      Plane(width(), height(), width() * 4, target_frame->pixel_buffer_.get()));

 

   const uint8_t* y_data = GetPlane(0).data;

   const uint8_t* u_data = GetPlane(1).data;

   const uint8_t* v_data = GetPlane(2).data;

-  uint8_t* bgra_data = &target_frame.pixel_buffer_[0];

+  uint8_t* bgra_data = target_frame->pixel_buffer_.get();

 

   int height = this->height();

   int width = this->width();

@@ -155,21 +181,23 @@
 }

 

 // static

-VideoFrame VideoFrame::CreateEOSFrame() {

-  return VideoFrame();

+scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() {

+  return new VideoFrame;

 }

 

 // static

-VideoFrame VideoFrame::CreateYV12Frame(int width,

-                                       int height,

-                                       int pitch_in_bytes,

-                                       SbMediaTime pts,

-                                       const uint8_t* y,

-                                       const uint8_t* u,

-                                       const uint8_t* v) {

-  VideoFrame frame;

-  frame.format_ = kYV12;

-  frame.pts_ = pts;

+scoped_refptr<VideoFrame> VideoFrame::CreateYV12Frame(int width,

+                                                      int height,

+                                                      int pitch_in_bytes,

+                                                      SbMediaTime pts,

+                                                      const uint8_t* y,

+                                                      const uint8_t* u,

+                                                      const uint8_t* v) {

+  scoped_refptr<VideoFrame> frame(new VideoFrame);

+  frame->format_ = kYV12;

+  frame->width_ = width;

+  frame->height_ = height;

+  frame->pts_ = pts;

 

   // U/V planes generally have half resolution of the Y plane.  However, in the

   // extreme case that any dimension of Y plane is odd, we want to have an

@@ -180,24 +208,38 @@
 

   int y_plane_size_in_bytes = height * pitch_in_bytes;

   int uv_plane_size_in_bytes = uv_height * uv_pitch_in_bytes;

-  frame.pixel_buffer_.assign(y, y + y_plane_size_in_bytes);

-  frame.pixel_buffer_.insert(frame.pixel_buffer_.end(), u,

-                             u + uv_plane_size_in_bytes);

-  frame.pixel_buffer_.insert(frame.pixel_buffer_.end(), v,

-                             v + uv_plane_size_in_bytes);

+  frame->pixel_buffer_.reset(

+      new uint8_t[y_plane_size_in_bytes + uv_plane_size_in_bytes * 2]);

+  SbMemoryCopy(frame->pixel_buffer_.get(), y, y_plane_size_in_bytes);

+  SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes, u,

+               uv_plane_size_in_bytes);

+  SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes +

+                   uv_plane_size_in_bytes,

+               v, uv_plane_size_in_bytes);

 

-  frame.planes_.push_back(

-      Plane(width, height, pitch_in_bytes, &frame.pixel_buffer_[0]));

-  frame.planes_.push_back(

+  frame->planes_.push_back(

+      Plane(width, height, pitch_in_bytes, frame->pixel_buffer_.get()));

+  frame->planes_.push_back(

       Plane(uv_width, uv_height, uv_pitch_in_bytes,

-            &frame.pixel_buffer_[0] + y_plane_size_in_bytes));

-  frame.planes_.push_back(Plane(uv_width, uv_height, uv_pitch_in_bytes,

-                                &frame.pixel_buffer_[0] +

-                                    y_plane_size_in_bytes +

-                                    uv_plane_size_in_bytes));

+            frame->pixel_buffer_.get() + y_plane_size_in_bytes));

+  frame->planes_.push_back(Plane(uv_width, uv_height, uv_pitch_in_bytes,

+                                 frame->pixel_buffer_.get() +

+                                     y_plane_size_in_bytes +

+                                     uv_plane_size_in_bytes));

   return frame;

 }

 

+void VideoFrame::InitializeToInvalidFrame() {

+  format_ = kInvalid;

+  width_ = 0;

+  height_ = 0;

+

+  pts_ = 0;

+  native_texture_ = NULL;

+  native_texture_context_ = NULL;

+  free_native_texture_func_ = NULL;

+}

+

 }  // namespace player

 }  // namespace starboard

 }  // namespace shared

diff --git a/src/starboard/shared/starboard/player/video_frame_internal.h b/src/starboard/shared/starboard/player/video_frame_internal.h
index 745ec31..c956e90 100644
--- a/src/starboard/shared/starboard/player/video_frame_internal.h
+++ b/src/starboard/shared/starboard/player/video_frame_internal.h
@@ -17,6 +17,9 @@
 

 #include <vector>

 

+#include "starboard/common/ref_counted.h"

+#include "starboard/common/scoped_ptr.h"

+#include "starboard/configuration.h"

 #include "starboard/media.h"

 #include "starboard/shared/internal_only.h"

 

@@ -26,10 +29,12 @@
 namespace player {

 

 // A video frame produced by a video decoder.

-class VideoFrame {

+class VideoFrame : public RefCountedThreadSafe<VideoFrame> {

  public:

+  typedef void (*FreeNativeTextureFunc)(void* context, void* textue);

+

   enum Format {

-    kInvalid,

+    kInvalid,  // A VideoFrame in this format can be used to indicate EOS.

     // This is the native format supported by XComposite (PictStandardARGB32

     // with bytes swapped).  Remove this once we are able to pass out frames

     // as YV12 textures.

@@ -50,37 +55,55 @@
     const uint8_t* data;

   };

 

-  VideoFrame() : format_(kInvalid) {}

-  VideoFrame(const VideoFrame& that);

-

-  VideoFrame& operator=(const VideoFrame& that);

+  VideoFrame();  // Create an EOS frame.

+  VideoFrame(int width,

+             int height,

+             SbMediaTime pts,

+             void* native_texture,

+             void* native_texture_context,

+             FreeNativeTextureFunc free_native_texture_func);

+  ~VideoFrame();

 

   Format format() const { return format_; }

-  int width() const { return GetPlaneCount() == 0 ? 0 : GetPlane(0).width; }

-  int height() const { return GetPlaneCount() == 0 ? 0 : GetPlane(0).height; }

-

   bool IsEndOfStream() const { return format_ == kInvalid; }

   SbMediaTime pts() const { return pts_; }

-  int GetPlaneCount() const { return static_cast<int>(planes_.size()); }

+  int width() const { return width_; }

+  int height() const { return height_; }

+

+  int GetPlaneCount() const;

   const Plane& GetPlane(int index) const;

 

-  VideoFrame ConvertTo(Format target_format) const;

+  void* native_texture() const;

 

-  static VideoFrame CreateEOSFrame();

-  static VideoFrame CreateYV12Frame(int width,

-                                    int height,

-                                    int pitch_in_bytes,

-                                    SbMediaTime pts,

-                                    const uint8_t* y,

-                                    const uint8_t* u,

-                                    const uint8_t* v);

+  scoped_refptr<VideoFrame> ConvertTo(Format target_format) const;

+

+  static scoped_refptr<VideoFrame> CreateEOSFrame();

+  static scoped_refptr<VideoFrame> CreateYV12Frame(int width,

+                                                   int height,

+                                                   int pitch_in_bytes,

+                                                   SbMediaTime pts,

+                                                   const uint8_t* y,

+                                                   const uint8_t* u,

+                                                   const uint8_t* v);

 

  private:

-  Format format_;

+  void InitializeToInvalidFrame();

 

+  Format format_;

+  int width_;

+  int height_;

   SbMediaTime pts_;

+

+  // The following two variables are valid when the frame contains pixel data.

   std::vector<Plane> planes_;

-  std::vector<uint8_t> pixel_buffer_;

+  scoped_array<uint8_t> pixel_buffer_;

+

+  // The following three variables are valid when |format_| is `kNativeTexture`.

+  void* native_texture_;

+  void* native_texture_context_;

+  FreeNativeTextureFunc free_native_texture_func_;

+

+  SB_DISALLOW_COPY_AND_ASSIGN(VideoFrame);

 };

 

 }  // namespace player

diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.cc b/src/starboard/shared/starboard/player/video_renderer_internal.cc
deleted file mode 100644
index da9ccf8..0000000
--- a/src/starboard/shared/starboard/player/video_renderer_internal.cc
+++ /dev/null
@@ -1,136 +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/player/video_renderer_internal.h"
-
-#include <algorithm>
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-VideoRenderer::VideoRenderer(VideoDecoder* decoder)
-    : seeking_(false),
-      seeking_to_pts_(0),
-      end_of_stream_written_(false),
-      need_more_input_(true),
-      decoder_(decoder) {
-  SB_DCHECK(decoder_ != NULL);
-  decoder_->SetHost(this);
-}
-
-VideoRenderer::~VideoRenderer() {
-  delete decoder_;
-}
-
-void VideoRenderer::WriteSample(const InputBuffer& input_buffer) {
-  ScopedLock lock(mutex_);
-
-  if (end_of_stream_written_) {
-    SB_LOG(ERROR) << "Appending video sample at " << input_buffer.pts()
-                  << " after EOS reached.";
-    return;
-  }
-  need_more_input_ = false;
-  decoder_->WriteInputBuffer(input_buffer);
-}
-
-void VideoRenderer::WriteEndOfStream() {
-  ScopedLock lock(mutex_);
-
-  SB_LOG_IF(WARNING, end_of_stream_written_)
-      << "Try to write EOS after EOS is reached";
-  if (end_of_stream_written_) {
-    return;
-  }
-  end_of_stream_written_ = true;
-  decoder_->WriteEndOfStream();
-}
-
-void VideoRenderer::Seek(SbMediaTime seek_to_pts) {
-  SB_DCHECK(seek_to_pts >= 0);
-
-  decoder_->Reset();
-
-  ScopedLock lock(mutex_);
-
-  seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
-  seeking_ = true;
-  end_of_stream_written_ = false;
-
-  frames_.clear();
-}
-
-const VideoFrame& VideoRenderer::GetCurrentFrame(SbMediaTime media_time) {
-  ScopedLock lock(mutex_);
-
-  if (frames_.empty()) {
-    // We cannot paint anything if there is no frames.
-    return empty_frame_;
-  }
-  // Remove any frames with timestamps earlier than |media_time|, but always
-  // keep at least one of the frames.
-  while (frames_.size() > 1 && frames_.front().pts() < media_time) {
-    frames_.pop_front();
-  }
-
-  return frames_.front();
-}
-
-bool VideoRenderer::IsEndOfStreamPlayed() const {
-  ScopedLock lock(mutex_);
-  return end_of_stream_written_ && frames_.size() <= 1;
-}
-
-bool VideoRenderer::CanAcceptMoreData() const {
-  ScopedLock lock(mutex_);
-  return frames_.size() < kMaxCachedFrames && !end_of_stream_written_ &&
-         need_more_input_;
-}
-
-bool VideoRenderer::IsSeekingInProgress() const {
-  ScopedLock lock(mutex_);
-  return seeking_;
-}
-
-void VideoRenderer::OnDecoderStatusUpdate(VideoDecoder::Status status,
-                                          VideoFrame* frame) {
-  ScopedLock lock(mutex_);
-
-  if (frame) {
-    bool frame_too_early = false;
-    if (seeking_) {
-      if (frame->IsEndOfStream()) {
-        seeking_ = false;
-      } else if (frame->pts() < seeking_to_pts_) {
-        frame_too_early = true;
-      }
-    }
-    if (!frame_too_early) {
-      frames_.push_back(*frame);
-    }
-
-    if (seeking_ && frames_.size() > kPrerollFrames) {
-      seeking_ = false;
-    }
-  }
-
-  need_more_input_ = (status == VideoDecoder::kNeedMoreInput);
-}
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.h b/src/starboard/shared/starboard/player/video_renderer_internal.h
deleted file mode 100644
index f62cfda..0000000
--- a/src/starboard/shared/starboard/player/video_renderer_internal.h
+++ /dev/null
@@ -1,87 +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_PLAYER_VIDEO_RENDERER_INTERNAL_H_
-#define STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_RENDERER_INTERNAL_H_
-
-#include <list>
-
-#include "starboard/log.h"
-#include "starboard/media.h"
-#include "starboard/mutex.h"
-#include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/player/input_buffer_internal.h"
-#include "starboard/shared/starboard/player/video_decoder_internal.h"
-#include "starboard/shared/starboard/player/video_frame_internal.h"
-
-namespace starboard {
-namespace shared {
-namespace starboard {
-namespace player {
-
-class VideoRenderer : private VideoDecoder::Host {
- public:
-  explicit VideoRenderer(VideoDecoder* decoder);
-  ~VideoRenderer();
-
-  bool is_valid() const { return true; }
-
-  void WriteSample(const InputBuffer& input_buffer);
-  void WriteEndOfStream();
-
-  void Seek(SbMediaTime seek_to_pts);
-
-  const VideoFrame& GetCurrentFrame(SbMediaTime media_time);
-
-  bool IsEndOfStreamWritten() const { return end_of_stream_written_; }
-  bool IsEndOfStreamPlayed() const;
-  bool CanAcceptMoreData() const;
-  bool IsSeekingInProgress() const;
-
- private:
-  typedef std::list<VideoFrame> Frames;
-
-  // Preroll considered finished after either kPrerollFrames is cached or EOS
-  // is reached.
-  static const size_t kPrerollFrames = 1;
-  // Set a soft limit for the max video frames we can cache so we can:
-  // 1. Avoid using too much memory.
-  // 2. Have the frame cache full to simulate the state that the renderer can
-  //    no longer accept more data.
-  static const size_t kMaxCachedFrames = 8;
-
-  // VideoDecoder::Host method.
-  void OnDecoderStatusUpdate(VideoDecoder::Status status,
-                             VideoFrame* frame) SB_OVERRIDE;
-
-  ::starboard::Mutex mutex_;
-
-  bool seeking_;
-  Frames frames_;
-
-  VideoFrame empty_frame_;
-
-  SbMediaTime seeking_to_pts_;
-  bool end_of_stream_written_;
-  bool need_more_input_;
-
-  VideoDecoder* decoder_;
-};
-
-}  // namespace player
-}  // namespace starboard
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/shared_main_adapter.cc b/src/starboard/shared/starboard/shared_main_adapter.cc
new file mode 100644
index 0000000..2a6a911
--- /dev/null
+++ b/src/starboard/shared/starboard/shared_main_adapter.cc
@@ -0,0 +1,31 @@
+// 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.
+
+// Imports and calls into StarboardMain() from main(). Can be used to
+// link into a shared Starboard executable to turn it into a traditional
+// executable.
+
+#include "starboard/configuration.h"
+
+#undef main
+
+extern "C" {
+
+SB_IMPORT_PLATFORM int StarboardMain(int argc, char** argv);
+
+int main(int argc, char** argv) {
+  return StarboardMain(argc, argv);
+}
+
+}  // extern "C"
diff --git a/src/starboard/shared/stub/decode_target_create_blitter.cc b/src/starboard/shared/stub/decode_target_create_blitter.cc
new file mode 100644
index 0000000..019658d
--- /dev/null
+++ b/src/starboard/shared/stub/decode_target_create_blitter.cc
@@ -0,0 +1,20 @@
+// 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"
+
+SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat /*format*/,
+                                    SbBlitterSurface* /*planes*/) {
+  return kSbDecodeTargetInvalid;
+}
diff --git a/src/starboard/shared/stub/decode_target_create_egl.cc b/src/starboard/shared/stub/decode_target_create_egl.cc
new file mode 100644
index 0000000..d43b91b
--- /dev/null
+++ b/src/starboard/shared/stub/decode_target_create_egl.cc
@@ -0,0 +1,22 @@
+// 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"
+
+SbDecodeTarget SbDecodeTargetCreate(EGLDisplay /*display*/,
+                                    EGLContext /*context*/,
+                                    SbDecodeTargetFormat /*format*/,
+                                    GLuint* /*planes*/) {
+  return kSbDecodeTargetInvalid;
+}
diff --git a/src/starboard/shared/stub/decode_target_destroy.cc b/src/starboard/shared/stub/decode_target_destroy.cc
new file mode 100644
index 0000000..6762123
--- /dev/null
+++ b/src/starboard/shared/stub/decode_target_destroy.cc
@@ -0,0 +1,17 @@
+// 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"
+
+void SbDecodeTargetDestroy(SbDecodeTarget /*decode_target*/) {}
diff --git a/src/starboard/shared/stub/decode_target_get_format.cc b/src/starboard/shared/stub/decode_target_get_format.cc
new file mode 100644
index 0000000..e8f9ea7
--- /dev/null
+++ b/src/starboard/shared/stub/decode_target_get_format.cc
@@ -0,0 +1,19 @@
+// 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 req