Import Cobalt 12.71685
diff --git a/src/base/logging.h b/src/base/logging.h
index 2fb626e..238db8c 100644
--- a/src/base/logging.h
+++ b/src/base/logging.h
@@ -308,18 +308,19 @@
 // by LOG() and LOG_IF, etc. Since these are used all over our code, it's
 // better to have compact code for these operations.
 #define COMPACT_GOOGLE_LOG_EX_INFO(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, logging::LOG_INFO , ##__VA_ARGS__)
-#define COMPACT_GOOGLE_LOG_EX_WARNING(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, logging::LOG_WARNING , ##__VA_ARGS__)
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_INFO, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_WARNING(ClassName, ...)              \
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_WARNING, \
+                       ##__VA_ARGS__)
 #define COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR , ##__VA_ARGS__)
-#define COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, \
-                     logging::LOG_ERROR_REPORT , ##__VA_ARGS__)
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_ERROR, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(ClassName, ...)            \
+  ::logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR_REPORT, \
+                       ##__VA_ARGS__)
 #define COMPACT_GOOGLE_LOG_EX_FATAL(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, logging::LOG_FATAL , ##__VA_ARGS__)
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_FATAL, ##__VA_ARGS__)
 #define COMPACT_GOOGLE_LOG_EX_DFATAL(ClassName, ...) \
-  logging::ClassName(__FILE__, __LINE__, logging::LOG_DFATAL , ##__VA_ARGS__)
+  ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_DFATAL, ##__VA_ARGS__)
 
 #define COMPACT_GOOGLE_LOG_INFO \
   COMPACT_GOOGLE_LOG_EX_INFO(LogMessage)
@@ -384,7 +385,7 @@
 
 // The VLOG macros log with negative verbosities.
 #define VLOG_STREAM(verbose_level) \
-  logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream()
+  ::logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream()
 
 #define VLOG(verbose_level) \
   LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level))
@@ -394,17 +395,20 @@
       VLOG_IS_ON(verbose_level) && (condition))
 
 #if defined (OS_WIN)
-#define VPLOG_STREAM(verbose_level) \
-  logging::Win32ErrorLogMessage(__FILE__, __LINE__, -verbose_level, \
-    ::logging::GetLastSystemErrorCode()).stream()
+#define VPLOG_STREAM(verbose_level)                                    \
+  ::logging::Win32ErrorLogMessage(__FILE__, __LINE__, -verbose_level,  \
+                                  ::logging::GetLastSystemErrorCode()) \
+      .stream()
 #elif defined(OS_POSIX)
-#define VPLOG_STREAM(verbose_level) \
-  logging::ErrnoLogMessage(__FILE__, __LINE__, -verbose_level, \
-    ::logging::GetLastSystemErrorCode()).stream()
+#define VPLOG_STREAM(verbose_level)                               \
+  ::logging::ErrnoLogMessage(__FILE__, __LINE__, -verbose_level,  \
+                             ::logging::GetLastSystemErrorCode()) \
+      .stream()
 #elif defined(OS_STARBOARD)
-#define VPLOG_STREAM(verbose_level) \
-  logging::StarboardLogMessage(__FILE__, __LINE__, -verbose_level, \
-    ::logging::GetLastSystemErrorCode()).stream()
+#define VPLOG_STREAM(verbose_level)                                   \
+  ::logging::StarboardLogMessage(__FILE__, __LINE__, -verbose_level,  \
+                                 ::logging::GetLastSystemErrorCode()) \
+      .stream()
 #endif
 
 #define VPLOG(verbose_level) \
@@ -526,11 +530,10 @@
 //
 // TODO(akalin): Rewrite this so that constructs like if (...)
 // CHECK_EQ(...) else { ... } work properly.
-#define CHECK_OP(name, op, val1, val2)                          \
-  if (std::string* _result =                                    \
-      logging::Check##name##Impl((val1), (val2),                \
-                                 #val1 " " #op " " #val2))      \
-    logging::LogMessage(__FILE__, __LINE__, _result).stream()
+#define CHECK_OP(name, op, val1, val2)                     \
+  if (std::string* _result = ::logging::Check##name##Impl( \
+          (val1), (val2), #val1 " " #op " " #val2))        \
+  ::logging::LogMessage(__FILE__, __LINE__, _result).stream()
 
 #endif
 
@@ -779,14 +782,12 @@
 
 // Helper macro for binary operators.
 // Don't use this macro directly in your code, use DCHECK_EQ et al below.
-#define DCHECK_OP(name, op, val1, val2)                         \
-  if (DCHECK_IS_ON())                                           \
-    if (std::string* _result =                                  \
-        logging::Check##name##Impl((val1), (val2),              \
-                                   #val1 " " #op " " #val2))    \
-      logging::LogMessage(                                      \
-          __FILE__, __LINE__, ::logging::LOG_DCHECK,            \
-          _result).stream()
+#define DCHECK_OP(name, op, val1, val2)                                     \
+  if (DCHECK_IS_ON())                                                       \
+    if (std::string* _result = ::logging::Check##name##Impl(                \
+            (val1), (val2), #val1 " " #op " " #val2))                       \
+  ::logging::LogMessage(__FILE__, __LINE__, ::logging::LOG_DCHECK, _result) \
+      .stream()
 
 // Equality/Inequality checks - compare two values, and log a
 // LOG_DCHECK message including the two values when the result is not
@@ -1005,12 +1006,14 @@
 // Async signal safe logging mechanism.
 BASE_EXPORT void RawLog(int level, const char* message);
 
-#define RAW_LOG(level, message) logging::RawLog(logging::LOG_ ## level, message)
+#define RAW_LOG(level, message) \
+  ::logging::RawLog(::logging::LOG_##level, message)
 
-#define RAW_CHECK(condition)                                                   \
-  do {                                                                         \
-    if (!(condition))                                                          \
-      logging::RawLog(logging::LOG_FATAL, "Check failed: " #condition "\n");   \
+#define RAW_CHECK(condition)                               \
+  do {                                                     \
+    if (!(condition))                                      \
+      ::logging::RawLog(::logging::LOG_FATAL,              \
+                        "Check failed: " #condition "\n"); \
   } while (0)
 
 }  // namespace logging
diff --git a/src/base/message_loop.cc b/src/base/message_loop.cc
index 1d07390..9d209cd 100644
--- a/src/base/message_loop.cc
+++ b/src/base/message_loop.cc
@@ -19,6 +19,7 @@
 #include "base/metrics/histogram.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/thread_task_runner_handle.h"
 #include "base/threading/thread_local.h"
@@ -328,6 +329,35 @@
   AddToIncomingQueue(&pending_task);
 }
 
+#if defined(COBALT)
+namespace {
+// Runs the given task, and then signals the given WaitableEvent.
+void RunAndSignal(const base::Closure& task, base::WaitableEvent* event) {
+  TRACE_EVENT0("task", "RunAndSignal");
+  task.Run();
+  event->Signal();
+}
+}  // namespace
+
+void MessageLoop::PostBlockingTask(
+    const tracked_objects::Location& from_here, const base::Closure& task) {
+  TRACE_EVENT0("task", "MessageLoop::PostBlockingTask");
+  DCHECK_NE(this, current())
+      << "PostBlockingTask can't be called from the MessageLoop's own thread. "
+      << from_here.ToString();
+  DCHECK(!task.is_null()) << from_here.ToString();
+
+  base::WaitableEvent task_finished(true /* manual reset */,
+                                    false /* initially unsignaled */);
+  PostTask(
+      from_here,
+      base::Bind(&RunAndSignal, task, base::Unretained(&task_finished)));
+
+  // Wait for the task to complete before proceeding.
+  task_finished.Wait();
+}
+#endif
+
 void MessageLoop::Run() {
   base::RunLoop run_loop;
   run_loop.Run();
diff --git a/src/base/message_loop.h b/src/base/message_loop.h
index 70a15b7..912898b 100644
--- a/src/base/message_loop.h
+++ b/src/base/message_loop.h
@@ -19,8 +19,8 @@
 #include "base/pending_task.h"
 #include "base/sequenced_task_runner_helpers.h"
 #include "base/synchronization/lock.h"
-#include "base/tracking_info.h"
 #include "base/time.h"
+#include "base/tracking_info.h"
 
 #if defined(OS_WIN)
 // We need this to declare base::MessagePumpWin::Dispatcher, which we should
@@ -202,6 +202,27 @@
       const base::Closure& task,
       base::TimeDelta delay);
 
+#if defined(COBALT)
+  // Posts an immediate task to this MessageLoop, and blocks until it has
+  // run. It is forbidden to call this method from the thread of the MessageLoop
+  // being posted to. One should exercise extreme caution when using this, as
+  // blocking between MessageLoops can cause deadlocks and is contraindicated in
+  // the Actor model of multiprogramming.
+  void PostBlockingTask(
+      const tracked_objects::Location& from_here,
+      const base::Closure& task);
+
+  // Adds a fence at the end of this MessageLoop's task queue, and then blocks
+  // until it has been reached. It is forbidden to call this method from the
+  // thread of the MessageLoop being posted to. One should exercise extreme
+  // caution when using this, as blocking between MessageLoops can cause
+  // deadlocks and is contraindicated in the Actor model of multiprogramming.
+  void WaitForFence() {
+    struct Fence { static void Task() {} };
+    PostBlockingTask(FROM_HERE, base::Bind(&Fence::Task));
+  }
+#endif
+
   // A variant on PostTask that deletes the given object.  This is useful
   // if the object needs to live until the next run of the MessageLoop (for
   // example, deleting a RenderProcessHost from within an IPC callback is not
diff --git a/src/base/message_loop_proxy_impl.cc b/src/base/message_loop_proxy_impl.cc
index c3efe26..c224f4a 100644
--- a/src/base/message_loop_proxy_impl.cc
+++ b/src/base/message_loop_proxy_impl.cc
@@ -5,6 +5,7 @@
 #include "base/message_loop_proxy_impl.h"
 
 #include "base/location.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_restrictions.h"
 
 namespace base {
@@ -26,6 +27,37 @@
   return PostTaskHelper(from_here, task, delay, false);
 }
 
+#if defined(COBALT)
+namespace {
+// Runs the given task, and then signals the given WaitableEvent.
+void RunAndSignal(const Closure& task, WaitableEvent* event) {
+  task.Run();
+  event->Signal();
+}
+}  // namespace
+
+bool MessageLoopProxyImpl::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  WaitableEvent task_finished(true /* manual reset */,
+                              false /* initially unsignaled */);
+  {
+    AutoLock lock(message_loop_lock_);
+    if (!target_message_loop_) {
+      return false;
+    }
+
+    target_message_loop_->PostTask(
+        from_here,
+        Bind(&RunAndSignal, task, Unretained(&task_finished)));
+  }
+
+  // Outside of the lock, wait for the task to complete before proceeding.
+  task_finished.Wait();
+  return true;
+}
+#endif
+
 bool MessageLoopProxyImpl::RunsTasksOnCurrentThread() const {
   return thread_id_ == PlatformThread::CurrentId();
 }
diff --git a/src/base/message_loop_proxy_impl.h b/src/base/message_loop_proxy_impl.h
index a01b415..4675a9e 100644
--- a/src/base/message_loop_proxy_impl.h
+++ b/src/base/message_loop_proxy_impl.h
@@ -27,6 +27,10 @@
       const base::Closure& task,
       base::TimeDelta delay) OVERRIDE;
   virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+#if defined(COBALT)
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) OVERRIDE;
+#endif
 
  protected:
   virtual ~MessageLoopProxyImpl();
diff --git a/src/base/message_loop_unittest.cc b/src/base/message_loop_unittest.cc
index b4bf2f3..2f49451 100644
--- a/src/base/message_loop_unittest.cc
+++ b/src/base/message_loop_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <limits.h>
+
 #include <vector>
 
 #include "base/bind.h"
@@ -332,6 +334,55 @@
   EXPECT_FALSE(run_time2.is_null());
 }
 
+#if defined(COBALT)
+void RunTest_PostBlockingTask(MessageLoop::Type message_loop_type) {
+  base::Thread thread("PostBlockingTsk");
+  thread.StartWithOptions(base::Thread::Options(message_loop_type, 0));
+
+  const TimeDelta kPause = TimeDelta::FromMilliseconds(50);
+
+  int num_tasks = INT_MAX;
+
+  TimeTicks time_before_post_1 = TimeTicks::Now();
+  thread.message_loop()->PostTask(
+      FROM_HERE, base::Bind(&SlowFunc, kPause, &num_tasks));
+  TimeTicks time_before_post_2 = TimeTicks::Now();
+  thread.message_loop()->PostBlockingTask(
+      FROM_HERE, base::Bind(&SlowFunc, kPause, &num_tasks));
+  TimeTicks time_after_post_2 = TimeTicks::Now();
+
+  // Not much time should have passed during the regular PostTask.
+  EXPECT_GT(kPause, time_before_post_2 - time_before_post_1);
+
+  // The PostBlockingTask should wait for both.
+  EXPECT_LE(kPause * 2, time_after_post_2 - time_before_post_1);
+}
+
+void RunTest_WaitForFence(MessageLoop::Type message_loop_type) {
+  base::Thread thread("WaitForFence");
+  thread.StartWithOptions(base::Thread::Options(message_loop_type, 0));
+
+  const TimeDelta kPause = TimeDelta::FromMilliseconds(50);
+
+  int num_tasks = INT_MAX;
+
+  TimeTicks time_before_post = TimeTicks::Now();
+  thread.message_loop()->PostTask(
+      FROM_HERE, base::Bind(&SlowFunc, kPause, &num_tasks));
+  thread.message_loop()->PostTask(
+      FROM_HERE, base::Bind(&SlowFunc, kPause, &num_tasks));
+  TimeTicks time_before_wait = TimeTicks::Now();
+  thread.message_loop()->WaitForFence();
+  TimeTicks time_after_wait = TimeTicks::Now();
+
+  // Not much time should have passed during the regular PostTask.
+  EXPECT_GT(kPause, time_before_wait - time_before_post);
+
+  // The WaitForFence should wait for the tasks to finish.
+  EXPECT_LE(kPause * 2, time_after_wait - time_before_post);
+}
+#endif
+
 #if defined(OS_WIN)
 
 void SubPumpFunc() {
@@ -1682,6 +1733,24 @@
   RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_IO);
 }
 
+#if defined(COBALT)
+TEST(MessageLoopTest, PostBlockingTask) {
+  RunTest_PostBlockingTask(MessageLoop::TYPE_DEFAULT);
+#if !defined(OS_STARBOARD)
+  RunTest_PostBlockingTask(MessageLoop::TYPE_UI);
+#endif
+  RunTest_PostBlockingTask(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, WaitForFence) {
+  RunTest_WaitForFence(MessageLoop::TYPE_DEFAULT);
+#if !defined(OS_STARBOARD)
+  RunTest_WaitForFence(MessageLoop::TYPE_UI);
+#endif
+  RunTest_WaitForFence(MessageLoop::TYPE_IO);
+}
+#endif
+
 #if defined(OS_WIN)
 TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) {
   RunTest_PostDelayedTask_SharedTimer_SubPump();
diff --git a/src/base/task_runner.h b/src/base/task_runner.h
index 4fbea4d..06aded1 100644
--- a/src/base/task_runner.h
+++ b/src/base/task_runner.h
@@ -7,7 +7,9 @@
 
 #include "base/base_export.h"
 #include "base/basictypes.h"
+#include "base/bind.h"
 #include "base/callback_forward.h"
+#include "base/location.h"
 #include "base/memory/ref_counted.h"
 #include "base/time.h"
 
@@ -75,6 +77,20 @@
                                const Closure& task,
                                base::TimeDelta delay) = 0;
 
+#if defined(COBALT)
+  // Like PostTask, but blocks until the posted task completes. Returns false
+  // and does not block if task was not posted.
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) = 0;
+
+  // Places a fence in the SequencedTaskRunner's queue and blocks until it is
+  // hit. Returns false if the fence was not set and no blocking was done.
+  bool WaitForFence() {
+    struct Fence { static void Task() {} };
+    return PostBlockingTask(FROM_HERE, base::Bind(&Fence::Task));
+  }
+#endif
+
   // Returns true if the current thread is a thread on which a task
   // may be run, and false if no task will be run on the current
   // thread.
diff --git a/src/base/threading/sequenced_worker_pool.cc b/src/base/threading/sequenced_worker_pool.cc
index 56f908b..59e684f 100644
--- a/src/base/threading/sequenced_worker_pool.cc
+++ b/src/base/threading/sequenced_worker_pool.cc
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/atomicops.h"
+#include "base/bind.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/critical_closure.h"
@@ -23,6 +24,7 @@
 #include "base/stringprintf.h"
 #include "base/synchronization/condition_variable.h"
 #include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/simple_thread.h"
 #include "base/threading/thread_restrictions.h"
@@ -94,6 +96,10 @@
   virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                                const Closure& task,
                                TimeDelta delay) OVERRIDE;
+#if defined(COBALT)
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) OVERRIDE;
+#endif
   virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
 
  private:
@@ -127,6 +133,14 @@
   return pool_->PostDelayedWorkerTask(from_here, task, delay);
 }
 
+#if defined(COBALT)
+bool SequencedWorkerPoolTaskRunner::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return pool_->PostBlockingTask(from_here, task);
+}
+#endif
+
 bool SequencedWorkerPoolTaskRunner::RunsTasksOnCurrentThread() const {
   return pool_->RunsTasksOnCurrentThread();
 }
@@ -147,6 +161,10 @@
   virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                                const Closure& task,
                                TimeDelta delay) OVERRIDE;
+#if defined(COBALT)
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) OVERRIDE;
+#endif
   virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
 
   // SequencedTaskRunner implementation
@@ -191,6 +209,14 @@
   return pool_->PostDelayedSequencedWorkerTask(token_, from_here, task, delay);
 }
 
+#if defined(COBALT)
+bool SequencedWorkerPoolSequencedTaskRunner::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return pool_->PostBlockingTask(from_here, task);
+}
+#endif
+
 bool SequencedWorkerPoolSequencedTaskRunner::RunsTasksOnCurrentThread() const {
   return pool_->IsRunningSequenceOnCurrentThread(token_);
 }
@@ -270,6 +296,14 @@
                 const Closure& task,
                 TimeDelta delay);
 
+#if defined(COBALT)
+  bool PostBlockingTask(const std::string* optional_token_name,
+                        SequenceToken sequence_token,
+                        WorkerShutdown shutdown_behavior,
+                        const tracked_objects::Location& from_here,
+                        const Closure& task);
+#endif
+
   bool RunsTasksOnCurrentThread() const;
 
   bool IsRunningSequenceOnCurrentThread(SequenceToken sequence_token) const;
@@ -568,6 +602,43 @@
   return true;
 }
 
+#if defined(COBALT)
+namespace {
+// Runs the given task, and then signals the given WaitableEvent.
+void RunAndSignal(const base::Closure& task, base::WaitableEvent* event) {
+  task.Run();
+  event->Signal();
+}
+}  // namespace
+
+bool SequencedWorkerPool::Inner::PostBlockingTask(
+    const std::string* optional_token_name,
+    SequenceToken sequence_token,
+    WorkerShutdown shutdown_behavior,
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  DCHECK(!IsRunningSequenceOnCurrentThread(sequence_token))
+      << "PostBlockingTask cannot be called from a SequenceWorkerPool's own "
+      << "thread." << from_here.ToString();
+  WaitableEvent task_finished(false /* automatic reset */,
+                              false /* initially unsignaled */);
+  bool posted = PostTask(
+      optional_token_name,
+      sequence_token,
+      shutdown_behavior,
+      from_here,
+      Bind(&RunAndSignal, task, Unretained(&task_finished)),
+      TimeDelta());
+  if (!posted) {
+    return false;
+  }
+
+  // Wait for the task to complete before proceeding.
+  task_finished.Wait();
+  return true;
+}
+#endif
+
 bool SequencedWorkerPool::Inner::RunsTasksOnCurrentThread() const {
   AutoLock lock(lock_);
   return ContainsKey(threads_, PlatformThread::CurrentId());
@@ -1045,6 +1116,15 @@
                           from_here, task, delay);
 }
 
+#if defined(COBALT)
+bool SequencedWorkerPool::PostBlockingWorkerTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return inner_->PostBlockingTask(NULL, SequenceToken(), BLOCK_SHUTDOWN,
+                                  from_here, task);
+}
+#endif
+
 bool SequencedWorkerPool::PostWorkerTaskWithShutdownBehavior(
     const tracked_objects::Location& from_here,
     const Closure& task,
@@ -1097,6 +1177,14 @@
   return PostDelayedWorkerTask(from_here, task, delay);
 }
 
+#if defined(COBALT)
+bool SequencedWorkerPool::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return PostBlockingWorkerTask(from_here, task);
+}
+#endif
+
 bool SequencedWorkerPool::RunsTasksOnCurrentThread() const {
   return inner_->RunsTasksOnCurrentThread();
 }
diff --git a/src/base/threading/sequenced_worker_pool.h b/src/base/threading/sequenced_worker_pool.h
index 1a26a4a..cca19f9 100644
--- a/src/base/threading/sequenced_worker_pool.h
+++ b/src/base/threading/sequenced_worker_pool.h
@@ -231,6 +231,13 @@
                              const Closure& task,
                              TimeDelta delay);
 
+#if defined(COBALT)
+  // Like PostWorkerTask, but blocks until the posted task completes. Returns
+  // false and does not block if task was not posted.
+  bool PostBlockingWorkerTask(const tracked_objects::Location& from_here,
+                              const Closure& task);
+#endif
+
   // Same as PostWorkerTask but allows specification of the shutdown behavior.
   bool PostWorkerTaskWithShutdownBehavior(
       const tracked_objects::Location& from_here,
@@ -285,6 +292,10 @@
   virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                                const Closure& task,
                                TimeDelta delay) OVERRIDE;
+#if defined(COBALT)
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) OVERRIDE;
+#endif
   virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
 
   // Returns true if the current thread is processing a task with the given
diff --git a/src/base/threading/worker_pool.cc b/src/base/threading/worker_pool.cc
index 16cc061..9aa7ba5 100644
--- a/src/base/threading/worker_pool.cc
+++ b/src/base/threading/worker_pool.cc
@@ -42,6 +42,10 @@
   virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                                const Closure& task,
                                TimeDelta delay) OVERRIDE;
+#if defined(COBALT)
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const Closure& task) OVERRIDE;
+#endif
   virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
 
  private:
@@ -73,6 +77,14 @@
   return PostDelayedTaskAssertZeroDelay(from_here, task, delay);
 }
 
+#if defined(COBALT)
+bool WorkerPoolTaskRunner::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return WorkerPool::PostBlockingTask(from_here, task, tasks_are_slow_);
+}
+#endif
+
 bool WorkerPoolTaskRunner::RunsTasksOnCurrentThread() const {
   return WorkerPool::RunsTasksOnCurrentThread();
 }
diff --git a/src/base/threading/worker_pool.h b/src/base/threading/worker_pool.h
index 333b495..16e1d93 100644
--- a/src/base/threading/worker_pool.h
+++ b/src/base/threading/worker_pool.h
@@ -36,6 +36,13 @@
   static bool PostTask(const tracked_objects::Location& from_here,
                        const base::Closure& task, bool task_is_slow);
 
+#if defined(COBALT)
+  // Like PostTask, but blocks until the posted task completes. Returns false
+  // and does not block if task was not posted.
+  static bool PostBlockingTask(const tracked_objects::Location& from_here,
+                               const base::Closure& task, bool task_is_slow);
+#endif
+
   // Just like MessageLoopProxy::PostTaskAndReply, except the destination
   // for |task| is a worker thread and you can specify |task_is_slow| just
   // like you can for PostTask above.
diff --git a/src/base/threading/worker_pool_posix.cc b/src/base/threading/worker_pool_posix.cc
index 2ad3925..f15250f 100644
--- a/src/base/threading/worker_pool_posix.cc
+++ b/src/base/threading/worker_pool_posix.cc
@@ -11,6 +11,7 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread_local.h"
 #include "base/threading/worker_pool.h"
@@ -42,6 +43,10 @@
 
   void PostTask(const tracked_objects::Location& from_here,
                 const base::Closure& task, bool task_is_slow);
+#if defined(COBALT)
+  void PostBlockingTask(const tracked_objects::Location& from_here,
+                        const base::Closure& task, bool task_is_slow);
+#endif
 
  private:
   scoped_refptr<base::PosixDynamicThreadPool> pool_;
@@ -61,6 +66,14 @@
   pool_->PostTask(from_here, task);
 }
 
+#if defined(COBALT)
+void WorkerPoolImpl::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const base::Closure& task, bool task_is_slow) {
+  pool_->PostBlockingTask(from_here, task);
+}
+#endif
+
 base::LazyInstance<WorkerPoolImpl> g_lazy_worker_pool =
     LAZY_INSTANCE_INITIALIZER;
 
@@ -118,6 +131,16 @@
   return true;
 }
 
+#if defined(COBALT)
+// static
+bool WorkerPool::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const base::Closure& task, bool task_is_slow) {
+  g_lazy_worker_pool.Pointer()->PostBlockingTask(from_here, task, task_is_slow);
+  return true;
+}
+#endif
+
 // static
 bool WorkerPool::RunsTasksOnCurrentThread() {
   return g_worker_pool_running_on_this_thread.Get().Get();
@@ -154,6 +177,29 @@
   AddTask(&pending_task);
 }
 
+#if defined(COBALT)
+namespace {
+// Runs the given task, and then signals the given WaitableEvent.
+void RunAndSignal(const Closure& task, WaitableEvent* event) {
+  task.Run();
+  event->Signal();
+}
+}  // namespace
+
+void PosixDynamicThreadPool::PostBlockingTask(
+    const tracked_objects::Location& from_here,
+    const base::Closure& task) {
+  base::WaitableEvent task_finished(false /* automatic reset */,
+                                    false /* initially unsignaled */);
+  PostTask(
+      from_here,
+      base::Bind(&RunAndSignal, task, base::Unretained(&task_finished)));
+
+  // Wait for the task to complete before proceeding.
+  task_finished.Wait();
+}
+#endif
+
 void PosixDynamicThreadPool::AddTask(PendingTask* pending_task) {
   AutoLock locked(lock_);
   DCHECK(!terminated_) <<
diff --git a/src/base/threading/worker_pool_posix.h b/src/base/threading/worker_pool_posix.h
index dd0ffb6..9b6e6ce 100644
--- a/src/base/threading/worker_pool_posix.h
+++ b/src/base/threading/worker_pool_posix.h
@@ -60,6 +60,12 @@
   void PostTask(const tracked_objects::Location& from_here,
                 const Closure& task);
 
+#if defined(COBALT)
+  // Like PostTask, but blocks until the posted task completes.
+  void PostBlockingTask(const tracked_objects::Location& from_here,
+                        const Closure& task);
+#endif
+
   // Worker thread method to wait for up to |idle_seconds_before_exit| for more
   // work from the thread pool.  Returns NULL if no work is available.
   PendingTask WaitForTask();
diff --git a/src/base/time_starboard.cc b/src/base/time_starboard.cc
index 0327166..9225951 100644
--- a/src/base/time_starboard.cc
+++ b/src/base/time_starboard.cc
@@ -98,7 +98,7 @@
 
 // static
 TimeTicks TimeTicks::ThreadNow() {
-#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
   return TimeTicks(SbTimeGetMonotonicThreadNow());
 #else
   return HighResNow();
@@ -107,7 +107,7 @@
 
 // static
 bool TimeTicks::HasThreadNow() {
-#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
   return true;
 #else
   return false;
diff --git a/src/build/common.gypi b/src/build/common.gypi
index 0164104..6f02d54 100644
--- a/src/build/common.gypi
+++ b/src/build/common.gypi
@@ -1978,29 +1978,6 @@
               '-Wno-unused-result',
             ],
           }],
-          [ 'OS=="win"', {
-            'defines': [
-              '_CRT_SECURE_NO_DEPRECATE',
-              '_CRT_NONSTDC_NO_WARNINGS',
-              '_CRT_NONSTDC_NO_DEPRECATE',
-              '_SCL_SECURE_NO_DEPRECATE',
-            ],
-            'msvs_disabled_warnings': [4800],
-            'msvs_settings': {
-              'VCCLCompilerTool': {
-                'WarningLevel': '3',
-                'WarnAsError': '<(win_third_party_warn_as_error)',
-                'Detect64BitPortabilityProblems': 'false',
-              },
-            },
-            'conditions': [
-              ['buildtype=="Official"', {
-                'msvs_settings': {
-                  'VCCLCompilerTool': { 'WarnAsError': 'false' },
-                }
-              }],
-            ],
-          }],
           # TODO(darin): Unfortunately, some third_party code depends on base.
           [ 'target_os=="win" and component=="shared_library"', {
             'msvs_disabled_warnings': [
@@ -2041,14 +2018,6 @@
           '__STDC_FORMAT_MACROS',
         ],
         'conditions': [
-          ['OS=="win"', {
-            # turn on warnings for signed/unsigned mismatch on chromium code.
-            'msvs_settings': {
-              'VCCLCompilerTool': {
-                'AdditionalOptions': ['/we4389'],
-              },
-            },
-          }],
           ['(OS == "win" or target_arch=="xb1") and component=="shared_library"', {
             'msvs_disabled_warnings': [
               4251,  # class 'std::xx' needs to have dll-interface.
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 1c6e3c6..3deb938 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -2,11 +2,77 @@
 
 This document records all notable changes made to Cobalt since the last release.
 
-## Version 10
+## Version 11
 
-### AutoMem - Memory Configuration
-AutoMem has been added which assists developers in tuning the memory
-settings of the Cobalt app. On startup, memory settings are now printed
-for all builds except gold. Memory settings can be altered via the
-command line, build files and in some instances the Starboard API.
-*For more information, see [doc/memory_tuning.md](doc/memory_tuning.md)
+ - **Introduce C\+\+11**
+
+   C\+\+11 is now used within Cobalt code, and so Cobalt must be compiled using
+   C\+\+11 toolchains.
+
+ - **Update SpiderMonkey from version 24 to 45; support ECMAScript 6**
+
+   The Mozilla SpiderMonkey JavaScript engine is rebased up to version 45, and
+   thus ECMAScript 6 is now supported by Cobalt.  You will need to modify your
+   `gyp_configuration.gypi` file to set `'javascript_engine': 'mozjs-45'` to
+   enable this.
+
+ - **Fetch/Stream API**
+
+   Cobalt now supports the Fetch/Stream API.
+
+ - **On-device Speech-to-Text support**
+
+   Support for utilizing the new Starboard speech recognizer interface in order
+   to allow for on-device speech-to-text support is now added.  The Starboard
+   interface is defined in
+   [starboard/speech_recognizer.h](../starboard/speech_recognizer.h).
+
+ - **Mouse pointer support**
+
+   Cobalt now supports pointer events and will respond to Starboard
+   `kSbInputEventTypeMove` events.  These will be passed into the WebModule to
+   be processed by JavaScript.
+
+ - **Playback Rate**
+
+   Cobalt now supports adjusting the video playback rate.
+
+ - **AutoMem - Memory Configuration**
+
+   AutoMem has been added which assists developers in tuning the memory
+   settings of the Cobalt app. On startup, memory settings are now printed
+   for all builds except gold. Memory settings can be altered via the
+   command line, build files and in some instances the Starboard API.
+   For more information, see [cobalt/doc/memory_tuning.md](doc/memory_tuning.md).
+
+ - **Page Visibility API Support**
+
+   Cobalt now supports the page visibility API, and will signal visibility and
+   focus changes to the web app as the process transitions between Starboard
+   lifecycle states.
+
+ - **Opus Support**
+
+   Added support for providing Opus audio-specific config to Starboard. Requires
+   Starboard 6.
+
+ - **Linux build now supports 360 video**
+
+   The Linux build linux-x64x11 now supports 360 video, and can be used as a
+   reference implementation.
+
+ - **Stop the application if not retrying after a connection error**
+
+   A positive response from `kSbSystemPlatformErrorTypeConnectionError` now
+   indicates that Cobalt should retry the failed request. Any other response now
+   causes Cobalt to call `SbSystemRequestStop`.
+
+ - **Frame rate counter**
+
+   A frame rate counter is now made accessible.  It actually displays frame
+   times, the inverse of frame rate.  In this case, 16.6ms corresponds to 60fps.
+   It is accessible both as an overlay on the display by the command line
+   option, "--fps_overlay".  The data can also be printed to stdout with the
+   command line option "--fps_stdout".  The frame rate statistics will be
+   updated each time an animation ends, or after 60 frames have been processed.
+   Both command line flags are available in Gold builds.
diff --git a/src/cobalt/accessibility/internal/text_alternative_helper.cc b/src/cobalt/accessibility/internal/text_alternative_helper.cc
index 8c803da..56845a4 100644
--- a/src/cobalt/accessibility/internal/text_alternative_helper.cc
+++ b/src/cobalt/accessibility/internal/text_alternative_helper.cc
@@ -110,7 +110,7 @@
   // Rule 1
   // Skip hidden elements unless the author specifies to use them via an
   // aria-labelledby or aria-describedby being used in the current computation.
-  if (!in_labelled_by_ && IsAriaHidden(element)) {
+  if (!in_labelled_by_or_described_by_ && IsAriaHidden(element)) {
     return;
   }
 
@@ -118,8 +118,9 @@
   // The aria-labelledby attribute takes precedence as the element's text
   // alternative unless this computation is already occurring as the result of a
   // recursive aria-labelledby declaration.
-  if (!in_labelled_by_) {
-    if (TryAppendFromLabelledBy(element)) {
+  if (!in_labelled_by_or_described_by_) {
+    if (TryAppendFromLabelledByOrDescribedBy(element,
+                                             base::Tokens::aria_labelledby())) {
       return;
     }
   }
@@ -180,6 +181,16 @@
     scoped_refptr<dom::Node> child = children->Item(i);
     AppendTextAlternative(child);
   }
+
+  // 5.6.1.2. Description Compution
+  // If aria-describedby is present, user agents MUST compute the accessible
+  // description by concatenating the text alternatives for nodes referenced by
+  // an aria-describedby attribute on the current node. The text alternatives
+  // for the referenced nodes are computed using a number of methods.
+  if (!in_labelled_by_or_described_by_) {
+    TryAppendFromLabelledByOrDescribedBy(element,
+                                         base::Tokens::aria_describedby());
+  }
 }
 
 std::string TextAlternativeHelper::GetTextAlternative() {
@@ -199,36 +210,34 @@
   return IsAriaHidden(element->parent_element());
 }
 
-bool TextAlternativeHelper::TryAppendFromLabelledBy(
-    const scoped_refptr<dom::Element>& element) {
-  DCHECK(!in_labelled_by_);
-  base::optional<std::string> labelled_by_attribute =
-      element->GetAttribute(base::Tokens::aria_labelledby().c_str());
-  std::vector<std::string> labelled_by_ids;
+bool TextAlternativeHelper::TryAppendFromLabelledByOrDescribedBy(
+    const scoped_refptr<dom::Element>& element, const base::Token& token) {
+  DCHECK(!in_labelled_by_or_described_by_);
+  base::optional<std::string> attributes = element->GetAttribute(token.c_str());
+  std::vector<std::string> ids;
   // If aria-labelledby is empty or undefined, the aria-label attribute ... is
   // used (defined below).
-  base::SplitStringAlongWhitespace(labelled_by_attribute.value_or(""),
-                                   &labelled_by_ids);
+  base::SplitStringAlongWhitespace(attributes.value_or(""), &ids);
   const size_t current_num_alternatives = alternatives_.size();
-  if (!labelled_by_ids.empty()) {
-    in_labelled_by_ = true;
-    for (int i = 0; i < labelled_by_ids.size(); ++i) {
-      if (visited_element_ids_.find(base::Token(labelled_by_ids[i])) !=
+  if (!ids.empty()) {
+    in_labelled_by_or_described_by_ = true;
+    for (int i = 0; i < ids.size(); ++i) {
+      if (visited_element_ids_.find(base::Token(ids[i])) !=
           visited_element_ids_.end()) {
-        DLOG(WARNING) << "Skipping reference to ID: " << labelled_by_ids[i]
+        DLOG(WARNING) << "Skipping reference to ID: " << ids[i]
                       << " to prevent reference loop.";
         continue;
       }
-      scoped_refptr<dom::Element> labelled_by_element =
-          GetElementById(element, labelled_by_ids[i]);
-      if (!labelled_by_element) {
-        DLOG(WARNING) << "Could not find aria-labelledby target: "
-                      << labelled_by_ids[i];
+      scoped_refptr<dom::Element> target_element =
+          GetElementById(element, ids[i]);
+      if (!target_element) {
+        DLOG(WARNING) << "Could not find " << token.c_str()
+                      << " target: " << ids[i];
         continue;
       }
-      AppendTextAlternative(labelled_by_element);
+      AppendTextAlternative(target_element);
     }
-    in_labelled_by_ = false;
+    in_labelled_by_or_described_by_ = false;
   }
   // Check if any of these recursive calls to AppendTextAlternative actually
   // ended up appending something.
diff --git a/src/cobalt/accessibility/internal/text_alternative_helper.h b/src/cobalt/accessibility/internal/text_alternative_helper.h
index 721a589..51fbfb9 100644
--- a/src/cobalt/accessibility/internal/text_alternative_helper.h
+++ b/src/cobalt/accessibility/internal/text_alternative_helper.h
@@ -31,15 +31,16 @@
 // algorithm, made public for testing. This class should not be used directly.
 class TextAlternativeHelper {
  public:
-  TextAlternativeHelper() : in_labelled_by_(false) {}
+  TextAlternativeHelper() : in_labelled_by_or_described_by_(false) {}
   void AppendTextAlternative(const scoped_refptr<dom::Node>& node);
 
   // Return the accumulated alternatives joined by a single space character.
   std::string GetTextAlternative();
 
-  // Append the text alternative from a aria-labelledby property. Returns true
-  // if text alternative(s) were successfully appended.
-  bool TryAppendFromLabelledBy(const scoped_refptr<dom::Element>& element);
+  // Append the text alternative from a aria-labelledby or aria-describedby
+  // property. Returns true if text alternative(s) were successfully appended.
+  bool TryAppendFromLabelledByOrDescribedBy(
+      const scoped_refptr<dom::Element>& element, const base::Token& token);
 
   // Append the text alternative from a aria-label property. Returns true
   // if the aria-label property exists and has a non-empty value.
@@ -63,7 +64,7 @@
  private:
   typedef base::hash_set<base::Token> TokenSet;
 
-  bool in_labelled_by_;
+  bool in_labelled_by_or_described_by_;
   std::vector<std::string> alternatives_;
   TokenSet visited_element_ids_;
 };
diff --git a/src/cobalt/accessibility/internal/text_alternative_helper_test.cc b/src/cobalt/accessibility/internal/text_alternative_helper_test.cc
index 5ce980a..0c6c7db 100644
--- a/src/cobalt/accessibility/internal/text_alternative_helper_test.cc
+++ b/src/cobalt/accessibility/internal/text_alternative_helper_test.cc
@@ -139,8 +139,8 @@
                                    "target_element");
   document()->AppendChild(labelledby_element);
 
-  EXPECT_TRUE(
-      text_alternative_helper_.TryAppendFromLabelledBy(labelledby_element));
+  EXPECT_TRUE(text_alternative_helper_.TryAppendFromLabelledByOrDescribedBy(
+      labelledby_element, base::Tokens::aria_labelledby()));
   EXPECT_STREQ("labelledby target",
                text_alternative_helper_.GetTextAlternative().c_str());
 }
@@ -157,8 +157,8 @@
                                    "bad_reference");
   document()->AppendChild(labelledby_element);
 
-  EXPECT_FALSE(
-      text_alternative_helper_.TryAppendFromLabelledBy(labelledby_element));
+  EXPECT_FALSE(text_alternative_helper_.TryAppendFromLabelledByOrDescribedBy(
+      labelledby_element, base::Tokens::aria_labelledby()));
   EXPECT_TRUE(text_alternative_helper_.GetTextAlternative().empty());
 }
 
@@ -183,8 +183,8 @@
       "target_element1 target_element2 bad_reference target_element3");
   document()->AppendChild(labelledby_element);
 
-  EXPECT_TRUE(
-      text_alternative_helper_.TryAppendFromLabelledBy(labelledby_element));
+  EXPECT_TRUE(text_alternative_helper_.TryAppendFromLabelledByOrDescribedBy(
+      labelledby_element, base::Tokens::aria_labelledby()));
   EXPECT_STREQ("target1 target2 target3",
                text_alternative_helper_.GetTextAlternative().c_str());
 }
@@ -204,8 +204,8 @@
                                    "other_id self_id");
   document()->AppendChild(labelledby_element);
 
-  EXPECT_TRUE(
-      text_alternative_helper_.TryAppendFromLabelledBy(labelledby_element));
+  EXPECT_TRUE(text_alternative_helper_.TryAppendFromLabelledByOrDescribedBy(
+      labelledby_element, base::Tokens::aria_labelledby()));
   EXPECT_STREQ("other-text self-label",
                text_alternative_helper_.GetTextAlternative().c_str());
 }
@@ -289,6 +289,50 @@
                text_alternative_helper_.GetTextAlternative().c_str());
 }
 
+TEST_F(TextAlternativeHelperTest, GetTextFromAriaDescribedBy) {
+  scoped_refptr<dom::Element> target_element = document()->CreateElement("div");
+  target_element->AppendChild(document()->CreateTextNode("describedby target"));
+  target_element->set_id("target_element");
+  document()->AppendChild(target_element);
+
+  scoped_refptr<dom::Element> describedby_element =
+      document()->CreateElement("div");
+  describedby_element->SetAttribute(base::Tokens::aria_describedby().c_str(),
+                                    "target_element");
+  document()->AppendChild(describedby_element);
+
+  EXPECT_TRUE(text_alternative_helper_.TryAppendFromLabelledByOrDescribedBy(
+      describedby_element, base::Tokens::aria_describedby()));
+  EXPECT_STREQ("describedby target",
+               text_alternative_helper_.GetTextAlternative().c_str());
+}
+
+TEST_F(TextAlternativeHelperTest, HasBothLabelledByAndDescribedBy) {
+  // aria-describedby is ignored in this case.
+  scoped_refptr<dom::Element> element = document()->CreateElement("div");
+  document()->AppendChild(element);
+
+  element->SetAttribute(base::Tokens::aria_labelledby().c_str(), "calendar");
+  element->SetAttribute(base::Tokens::aria_describedby().c_str(), "info");
+
+  scoped_refptr<dom::Element> labelledby_element =
+      document()->CreateElement("div");
+  labelledby_element->set_id("calendar");
+  labelledby_element->AppendChild(document()->CreateTextNode("Calendar"));
+  element->AppendChild(labelledby_element);
+
+  scoped_refptr<dom::Element> describedby_element =
+      document()->CreateElement("div");
+  describedby_element->set_id("info");
+  describedby_element->AppendChild(
+      document()->CreateTextNode("Calendar content description."));
+  element->AppendChild(describedby_element);
+
+  text_alternative_helper_.AppendTextAlternative(element);
+  EXPECT_STREQ("Calendar",
+               text_alternative_helper_.GetTextAlternative().c_str());
+}
+
 }  // namespace internal
 }  // namespace accessibility
 }  // namespace cobalt
diff --git a/src/cobalt/accessibility/screen_reader_tests.cc b/src/cobalt/accessibility/screen_reader_tests.cc
index 64a0893..1319c0d 100644
--- a/src/cobalt/accessibility/screen_reader_tests.cc
+++ b/src/cobalt/accessibility/screen_reader_tests.cc
@@ -161,7 +161,8 @@
   // Create the webmodule and let it run.
   DLOG(INFO) << url.spec();
   browser::WebModule web_module(
-      url, base::Bind(&LiveRegionMutationTest::OnRenderTreeProducedStub),
+      url, base::kApplicationStateStarted,
+      base::Bind(&LiveRegionMutationTest::OnRenderTreeProducedStub),
       base::Bind(&LiveRegionMutationTest::OnError, base::Unretained(this)),
       base::Bind(&LiveRegionMutationTest::Quit, base::Unretained(this)),
       base::Closure(), /* window_minimize_callback */
diff --git a/src/cobalt/account/account.gyp b/src/cobalt/account/account.gyp
index 6a561d2..21fcce3 100644
--- a/src/cobalt/account/account.gyp
+++ b/src/cobalt/account/account.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index aac40c9..b0ad972 100644
--- a/src/cobalt/audio/async_audio_decoder.cc
+++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -49,6 +49,8 @@
   thread_.Start();
 }
 
+void AsyncAudioDecoder::Stop() { thread_.Stop(); }
+
 void AsyncAudioDecoder::AsyncDecode(
     const uint8* audio_data, size_t size,
     const DecodeFinishCallback& decode_finish_callback) {
diff --git a/src/cobalt/audio/async_audio_decoder.h b/src/cobalt/audio/async_audio_decoder.h
index b82c0db..1e8a025 100644
--- a/src/cobalt/audio/async_audio_decoder.h
+++ b/src/cobalt/audio/async_audio_decoder.h
@@ -35,6 +35,7 @@
 
   void AsyncDecode(const uint8* audio_data, size_t size,
                    const DecodeFinishCallback& decode_finish_callback);
+  void Stop();
 
  private:
   // The decoder thread.
diff --git a/src/cobalt/audio/audio.gyp b/src/cobalt/audio/audio.gyp
index e55e00b..f7065ff 100644
--- a/src/cobalt/audio/audio.gyp
+++ b/src/cobalt/audio/audio.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc
index b0c2ddd..0a6075d 100644
--- a/src/cobalt/audio/audio_context.cc
+++ b/src/cobalt/audio/audio_context.cc
@@ -33,6 +33,21 @@
   DCHECK(main_message_loop_);
 }
 
+AudioContext::~AudioContext() {
+  DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+  // Before releasing any |pending_decode_callbacks_|, stop audio decoder
+  // explicitly to ensure that all the decoding works are done.
+  audio_decoder_.Stop();
+
+  // It is possible that the callbacks in |pending_decode_callbacks_| have not
+  // been called when destroying AudioContext.
+  for (DecodeCallbacks::iterator it = pending_decode_callbacks_.begin();
+       it != pending_decode_callbacks_.end(); ++it) {
+    delete it->second;
+  }
+}
+
 scoped_refptr<AudioBufferSourceNode> AudioContext::CreateBufferSource() {
   DCHECK(main_message_loop_->BelongsToCurrentThread());
 
diff --git a/src/cobalt/audio/audio_context.h b/src/cobalt/audio/audio_context.h
index 2fb310d..2e37fed 100644
--- a/src/cobalt/audio/audio_context.h
+++ b/src/cobalt/audio/audio_context.h
@@ -88,6 +88,7 @@
   typedef DecodeErrorCallbackArg::Reference DecodeErrorCallbackReference;
 
   AudioContext();
+  ~AudioContext();
 
   // Web API: AudioContext
   //
diff --git a/src/cobalt/base/application_state.h b/src/cobalt/base/application_state.h
new file mode 100644
index 0000000..a7fbc19
--- /dev/null
+++ b/src/cobalt/base/application_state.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_BASE_APPLICATION_STATE_H_
+#define COBALT_BASE_APPLICATION_STATE_H_
+
+namespace base {
+
+// Application states that Cobalt can be in, as derived from Starboard lifecycle
+// states as described in starboard/events.h. These may deviate from the
+// Starboard lifecycle states in order to explicitly represent Cobalt-specific
+// substates that Starboard does not enforce.
+enum ApplicationState {
+  // The application is still visible, and therefore still requires graphics
+  // resources, but the web application may wish to take actions such as pause
+  // video. The application is expected to be able to move back into the Started
+  // state very quickly
+  kApplicationStatePaused,
+
+  // A possible initial state where the web application can be running, loading
+  // data, and so on, but is not visible to the user, and has not ever been
+  // given any graphics resources.
+  kApplicationStatePreloading,
+
+  // The state where the application is running in the foreground, fully
+  // visible, with all necessary graphics resources available. A possible
+  // initial state, where loading happens while in the foreground.
+  kApplicationStateStarted,
+
+  // Representation of a idle/terminal/shutdown state with no resources.
+  kApplicationStateStopped,
+
+  // The application was running at some point, but has been backgrounded to the
+  // point where graphics resources are invalid and execution should be halted
+  // until resumption.
+  kApplicationStateSuspended,
+};
+
+}  // namespace base
+
+#endif  // COBALT_BASE_APPLICATION_STATE_H_
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index 4753208..f4f9f34 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -58,6 +58,8 @@
         'polymorphic_equatable.h',
         'ref_counted_lock.h',
         'source_location.h',
+        'startup_timer.cc',
+        'startup_timer.h',
         'stop_watch.cc',
         'stop_watch.h',
         'token.cc',
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index 48227bf..a589822 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -21,6 +21,7 @@
 #include <set>
 #include <sstream>
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include "base/compiler_specific.h"
@@ -31,6 +32,7 @@
 #include "base/synchronization/lock.h"
 #include "base/time.h"
 #include "cobalt/base/ref_counted_lock.h"
+#include "cobalt/base/type_id.h"
 
 // The CVal system allows you to mark certain variables to be part of the
 // CVal system and therefore analyzable and trackable by other systems.  All
@@ -92,21 +94,6 @@
 
 }  // namespace CValDetail
 
-// An enumeration to allow CVals to track the type that they hold in a run-time
-// variable.
-enum CValType {
-  kSize,
-  kBool,
-  kU32,
-  kU64,
-  kS32,
-  kS64,
-  kFloat,
-  kDouble,
-  kString,
-  kTimeDelta,
-};
-
 // CVals are commonly used for values that are in units of bytes.  By making
 // a CVal of type SizeInBytes, this can be made explicit, and allows the CVal
 // system to use KB/MB suffixes instead of K/M.
@@ -140,64 +127,6 @@
 }  // namespace cval
 
 namespace CValDetail {
-// Introduce a Traits class so that we can convert from C++ type to
-// CValType.
-template <typename T>
-struct Traits {
-  // If you get a compiler error here, you must add a Traits class specific to
-  // the variable type you would like to support.
-  int UnsupportedCValType[0];
-};
-template <>
-struct Traits<cval::SizeInBytes> {
-  static const CValType kTypeVal = kSize;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<bool> {
-  static const CValType kTypeVal = kBool;
-  static const bool kIsNumerical = false;
-};
-template <>
-struct Traits<uint32_t> {
-  static const CValType kTypeVal = kU32;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<uint64_t> {
-  static const CValType kTypeVal = kU64;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<int32_t> {
-  static const CValType kTypeVal = kS32;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<int64_t> {
-  static const CValType kTypeVal = kS64;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<float> {
-  static const CValType kTypeVal = kFloat;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<double> {
-  static const CValType kTypeVal = kDouble;
-  static const bool kIsNumerical = true;
-};
-template <>
-struct Traits<std::string> {
-  static const CValType kTypeVal = kString;
-  static const bool kIsNumerical = false;
-};
-template <>
-struct Traits<base::TimeDelta> {
-  static const CValType kTypeVal = kTimeDelta;
-  static const bool kIsNumerical = true;
-};
 
 // Provide methods to convert from an arbitrary type to a string, useful for
 // systems that want to read the value of a CVal without caring about its type.
@@ -340,13 +269,21 @@
   static std::string Call(const T& value) { return ValToString(value); }
 };
 
+template <typename T>
+struct IsNumerical {
+  static const bool value =
+      (std::is_arithmetic<T>::value && !std::is_same<T, bool>::value) ||
+      std::is_same<T, base::TimeDelta>::value ||
+      std::is_same<T, cval::SizeInBytes>::value;
+};
+
 // As opposed to ValToString, here we take the opportunity to clean up the
 // number a bit to make it easier for humans to digest.  For example, if a
 // number is much larger than one million, we can divide it by one million
 // and postfix it with an 'M'.
 template <typename T>
 std::string ValToPrettyString(const T& value) {
-  return ValToPrettyStringHelper<T, Traits<T>::kIsNumerical>::Call(value);
+  return ValToPrettyStringHelper<T, IsNumerical<T>::value>::Call(value);
 }
 
 // Provides methods for converting the type to and from a double.
@@ -398,7 +335,7 @@
 // retrieve the string version of the value.
 class CValGenericValue {
  public:
-  CValType GetType() const { return type_; }
+  TypeId GetTypeId() const { return type_id_; }
 
   // Return the value casted to the specified type.  The requested type must
   // match the contained value's actual native type.
@@ -411,19 +348,19 @@
   // Returns true if the type of this value is exactly T.
   template <typename T>
   bool IsNativeType() const {
-    return type_ == CValDetail::Traits<T>::kTypeVal;
+    return type_id_ == base::GetTypeId<T>();
   }
 
   virtual std::string AsString() const = 0;
   virtual std::string AsPrettyString() const = 0;
 
  protected:
-  CValGenericValue(CValType type, void* value_mem)
-      : type_(type), generic_value_(value_mem) {}
+  CValGenericValue(TypeId type_id, void* value_mem)
+      : type_id_(type_id), generic_value_(value_mem) {}
   virtual ~CValGenericValue() {}
 
  private:
-  CValType type_;
+  TypeId type_id_;
   void* generic_value_;
 };
 
@@ -439,9 +376,9 @@
 class CValSpecificValue : public CValGenericValue {
  public:
   explicit CValSpecificValue(const T& value)
-      : CValGenericValue(Traits<T>::kTypeVal, &value_), value_(value) {}
+      : CValGenericValue(base::GetTypeId<T>(), &value_), value_(value) {}
   CValSpecificValue(const CValSpecificValue<T>& other)
-      : CValGenericValue(Traits<T>::kTypeVal, &value_), value_(other.value_) {}
+      : CValGenericValue(base::GetTypeId<T>(), &value_), value_(other.value_) {}
   virtual ~CValSpecificValue() {}
 
   std::string AsString() const { return ValToString(value_); }
@@ -549,13 +486,13 @@
 
 class CValBase {
  public:
-  CValBase(const std::string& name, CValType type,
+  CValBase(const std::string& name, TypeId type_id,
            const std::string& description)
-      : name_(name), description_(description), type_(type) {}
+      : name_(name), description_(description), type_id_(type_id) {}
 
   const std::string& GetName() const { return name_; }
   const std::string& GetDescription() const { return description_; }
-  CValType GetType() const { return type_; }
+  TypeId GetTypeId() const { return type_id_; }
 
   virtual std::string GetValueAsString() const = 0;
   virtual std::string GetValueAsPrettyString() const = 0;
@@ -565,7 +502,7 @@
 
   std::string name_;
   std::string description_;
-  CValType type_;
+  TypeId type_id_;
 
   friend CValManager;
 };
@@ -577,12 +514,12 @@
  public:
   CValImpl(const std::string& name, const T& initial_value,
            const std::string& description)
-      : CValBase(name, Traits<T>::kTypeVal, description),
+      : CValBase(name, base::GetTypeId<T>(), description),
         value_(initial_value) {
     CommonConstructor();
   }
   CValImpl(const std::string& name, const std::string& description)
-      : CValBase(name, Traits<T>::kTypeVal, description) {
+      : CValBase(name, base::GetTypeId<T>(), description) {
     CommonConstructor();
   }
   virtual ~CValImpl() {
diff --git a/src/cobalt/base/c_val_collection_entry_stats.h b/src/cobalt/base/c_val_collection_entry_stats.h
index 28cd938..faf7ad1 100644
--- a/src/cobalt/base/c_val_collection_entry_stats.h
+++ b/src/cobalt/base/c_val_collection_entry_stats.h
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/logging.h"
 #include "base/stringprintf.h"
 #include "base/time.h"
@@ -50,9 +51,38 @@
  public:
   static const size_t kNoMaxSize = 0;
 
-  CValCollectionEntryStatsImpl(const std::string& name,
-                               size_t max_size = kNoMaxSize,
-                               bool enable_entry_list_c_val = false);
+  struct FlushResults {
+    FlushResults(size_t sample_count, EntryType average, EntryType minimum,
+                 EntryType maximum, EntryType standard_deviation,
+                 EntryType percentile_25th, EntryType percentile_50th,
+                 EntryType percentile_75th, EntryType percentile_95th)
+        : sample_count(sample_count),
+          average(average),
+          minimum(minimum),
+          maximum(maximum),
+          standard_deviation(standard_deviation),
+          percentile_25th(percentile_25th),
+          percentile_50th(percentile_50th),
+          percentile_75th(percentile_75th),
+          percentile_95th(percentile_95th) {}
+
+    size_t sample_count;
+    EntryType average;
+    EntryType minimum;
+    EntryType maximum;
+    EntryType standard_deviation;
+    EntryType percentile_25th;
+    EntryType percentile_50th;
+    EntryType percentile_75th;
+    EntryType percentile_95th;
+  };
+
+  typedef base::Callback<void(const FlushResults&)> OnFlushCallback;
+
+  CValCollectionEntryStatsImpl(
+      const std::string& name, size_t max_size = kNoMaxSize,
+      bool enable_entry_list_c_val = false,
+      const OnFlushCallback& on_flush = OnFlushCallback());
 
   // Add an entry to the collection. This may trigger a Flush() if adding the
   // entry causes the max size to be reached.
@@ -76,6 +106,10 @@
 
   // The maximum size of the collection before Flush() is automatically called.
   const size_t max_size_;
+
+  // Callback to call whenever the values are flushed.
+  const OnFlushCallback on_flush_;
+
   // The current collection of entries. These will be used to generate the cval
   // stats during the next call of Flush().
   CollectionType collection_;
@@ -96,10 +130,12 @@
 
 template <typename EntryType, typename Visibility>
 CValCollectionEntryStatsImpl<EntryType, Visibility>::
-    CValCollectionEntryStatsImpl(const std::string& name,
-                                 size_t max_size /*=kNoMaxSize*/,
-                                 bool enable_entry_list_c_val /*=false*/)
+    CValCollectionEntryStatsImpl(
+        const std::string& name, size_t max_size /*=kNoMaxSize*/,
+        bool enable_entry_list_c_val /*=false*/,
+        const OnFlushCallback& on_flush /*=OnFlushCallback()*/)
     : max_size_(max_size),
+      on_flush_(on_flush),
       count_(StringPrintf("%s.Cnt", name.c_str()), 0, "Total entries."),
       average_(StringPrintf("%s.Avg", name.c_str()), EntryType(),
                "Average value."),
@@ -152,18 +188,36 @@
   double mean = CValDetail::ToDouble<EntryType>(sum) / collection_.size();
 
   // Update the collection stat cvals.
-  count_ = collection_.size();
-  average_ = CValDetail::FromDouble<EntryType>(mean);
-  minimum_ = collection_.front();
-  maximum_ = collection_.back();
-  percentile_25th_ = CalculatePercentile(collection_, 25);
-  percentile_50th_ = CalculatePercentile(collection_, 50);
-  percentile_75th_ = CalculatePercentile(collection_, 75);
-  percentile_95th_ = CalculatePercentile(collection_, 95);
-  standard_deviation_ = CValDetail::FromDouble<EntryType>(
+  size_t count = collection_.size();
+  EntryType average = CValDetail::FromDouble<EntryType>(mean);
+  EntryType minimum = collection_.front();
+  EntryType maximum = collection_.back();
+  EntryType percentile_25th = CalculatePercentile(collection_, 25);
+  EntryType percentile_50th = CalculatePercentile(collection_, 50);
+  EntryType percentile_75th = CalculatePercentile(collection_, 75);
+  EntryType percentile_95th = CalculatePercentile(collection_, 95);
+  EntryType standard_deviation = CValDetail::FromDouble<EntryType>(
       CalculateStandardDeviation(collection_, mean));
 
+  // Flush the computed values out to the CVals.
+  count_ = count;
+  average_ = average;
+  minimum_ = minimum;
+  maximum_ = maximum;
+  percentile_25th_ = percentile_25th;
+  percentile_50th_ = percentile_50th;
+  percentile_75th_ = percentile_75th;
+  percentile_95th_ = percentile_95th;
+  standard_deviation_ = standard_deviation;
+
   collection_.clear();
+
+  // Callback to any listeners with the flush values.
+  if (!on_flush_.is_null()) {
+    on_flush_.Run(FlushResults(
+        count, average, minimum, maximum, standard_deviation, percentile_25th,
+        percentile_50th, percentile_75th, percentile_95th));
+  }
 }
 
 template <typename EntryType, typename Visibility>
@@ -219,7 +273,6 @@
 
   double variance =
       dif_squared_sum / static_cast<double>(collection.size() - 1);
-  variance = std::max(variance, 0.0);
 
   return std::sqrt(variance);
 }
@@ -290,11 +343,15 @@
 #endif  // ENABLE_DEBUG_C_VAL
 
  public:
+  typedef typename CValParent::OnFlushCallback OnFlushCallback;
+  typedef typename CValParent::FlushResults FlushResults;
+
   explicit CValCollectionEntryStats(const std::string& name)
       : CValParent(name) {}
   CValCollectionEntryStats(const std::string& name, size_t max_size,
-                           bool enable_entry_list_c_val)
-      : CValParent(name, max_size, enable_entry_list_c_val) {}
+                           bool enable_entry_list_c_val,
+                           const OnFlushCallback& on_flush = OnFlushCallback())
+      : CValParent(name, max_size, enable_entry_list_c_val, on_flush) {}
 };
 
 // CVals with visibility set to CValPublic are always tracked though the CVal
@@ -306,11 +363,15 @@
       CValParent;
 
  public:
+  typedef typename CValParent::OnFlushCallback OnFlushCallback;
+  typedef typename CValParent::FlushResults FlushResults;
+
   explicit CValCollectionEntryStats(const std::string& name)
       : CValParent(name) {}
   CValCollectionEntryStats(const std::string& name, size_t max_size,
-                           bool enable_entry_list_c_val)
-      : CValParent(name, max_size, enable_entry_list_c_val) {}
+                           bool enable_entry_list_c_val,
+                           const OnFlushCallback& on_flush = OnFlushCallback())
+      : CValParent(name, max_size, enable_entry_list_c_val, on_flush) {}
 };
 
 }  // namespace base
diff --git a/src/cobalt/base/c_val_collection_timer_stats.h b/src/cobalt/base/c_val_collection_timer_stats.h
index eda96b9..a40fb20 100644
--- a/src/cobalt/base/c_val_collection_timer_stats.h
+++ b/src/cobalt/base/c_val_collection_timer_stats.h
@@ -31,11 +31,17 @@
 template <typename Visibility = CValDebug>
 class CValCollectionTimerStats {
  public:
+  typedef typename base::CValCollectionEntryStats<
+      base::TimeDelta, Visibility>::OnFlushCallback OnFlushCallback;
+  typedef typename base::CValCollectionEntryStats<
+      base::TimeDelta, Visibility>::FlushResults FlushResults;
+
   explicit CValCollectionTimerStats(const std::string& name)
       : entry_stats_(name) {}
   CValCollectionTimerStats(const std::string& name, size_t max_size,
-                           bool enable_entry_list_c_val)
-      : entry_stats_(name, max_size, enable_entry_list_c_val) {}
+                           bool enable_entry_list_c_val,
+                           const OnFlushCallback& on_flush = OnFlushCallback())
+      : entry_stats_(name, max_size, enable_entry_list_c_val, on_flush) {}
 
   // Start the timer. If the timer is currently running, it is stopped and
   // re-started. If no time is provided, then |base::TimeTicks::Now()| is used.
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc
index 3dc416d..1087ad2 100644
--- a/src/cobalt/base/c_val_test.cc
+++ b/src/cobalt/base/c_val_test.cc
@@ -458,7 +458,7 @@
     void OnValueChanged(const std::string& name,
                         const base::CValGenericValue& value) OVERRIDE {
       EXPECT_EQ(name, "S32");
-      EXPECT_EQ(value.GetType(), base::kS32);
+      EXPECT_EQ(value.GetTypeId(), GetTypeId<int32_t>());
       EXPECT_EQ(value.IsNativeType<int32_t>(), true);
       EXPECT_EQ(value.AsNativeType<int32_t>(), 1);
       EXPECT_EQ(value.AsString(), "1");
diff --git a/src/cobalt/base/camera_transform.h b/src/cobalt/base/camera_transform.h
index ef0fafa..875e7f7 100644
--- a/src/cobalt/base/camera_transform.h
+++ b/src/cobalt/base/camera_transform.h
@@ -50,15 +50,6 @@
   }
 };
 
-// Structure to keep track of the current orientation state.
-struct CameraOrientation {
-  CameraOrientation() : roll(0.0f), pitch(0.0f), yaw(0.0f) {}
-
-  float roll;
-  float pitch;
-  float yaw;
-};
-
 }  // namespace base
 
 #endif  // COBALT_BASE_CAMERA_TRANSFORM_H_
diff --git a/src/cobalt/base/message_queue.h b/src/cobalt/base/message_queue.h
index 26d0619..4f6569c 100644
--- a/src/cobalt/base/message_queue.h
+++ b/src/cobalt/base/message_queue.h
@@ -17,6 +17,7 @@
 
 #include <queue>
 #include "base/callback.h"
+#include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "base/synchronization/lock.h"
 
@@ -35,6 +36,7 @@
 
   // Add a message to the end of the queue.
   void AddMessage(const base::Closure& message) {
+    TRACE_EVENT0("cobalt::base", "MessageQueue::AddMessage()");
     base::AutoLock lock(mutex_);
     DCHECK(!message.is_null());
     queue_.push(message);
@@ -43,11 +45,16 @@
   // Execute all messages in the MessageQueue until the queue is empty and then
   // return.
   void ProcessAll() {
-    base::AutoLock lock(mutex_);
+    TRACE_EVENT0("cobalt::base", "MessageQueue::ProcessAll()");
+    std::queue<base::Closure> work_queue;
+    {
+      base::AutoLock lock(mutex_);
+      work_queue.swap(queue_);
+    }
 
-    while (!queue_.empty()) {
-      queue_.front().Run();
-      queue_.pop();
+    while (!work_queue.empty()) {
+      work_queue.front().Run();
+      work_queue.pop();
     }
   }
 
diff --git a/src/cobalt/base/poller.h b/src/cobalt/base/poller.h
index b63ff9c..25e104e 100644
--- a/src/cobalt/base/poller.h
+++ b/src/cobalt/base/poller.h
@@ -17,7 +17,6 @@
 
 #include "base/callback.h"
 #include "base/message_loop.h"
-#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "base/timer.h"
 
@@ -46,15 +45,9 @@
     if (!message_loop_) {
       StopTimer();
     } else {
-      message_loop_->PostTask(
-          FROM_HERE, base::Bind(&Poller::StopTimer, base::Unretained(this)));
-
       // Wait for the timer to actually be stopped.
-      base::WaitableEvent timer_stopped(true, false);
-      message_loop_->PostTask(FROM_HERE,
-                              base::Bind(&base::WaitableEvent::Signal,
-                                         base::Unretained(&timer_stopped)));
-      timer_stopped.Wait();
+      message_loop_->PostBlockingTask(
+          FROM_HERE, base::Bind(&Poller::StopTimer, base::Unretained(this)));
     }
   }
 
diff --git a/src/cobalt/base/startup_timer.cc b/src/cobalt/base/startup_timer.cc
new file mode 100644
index 0000000..ef01092
--- /dev/null
+++ b/src/cobalt/base/startup_timer.cc
@@ -0,0 +1,48 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/base/startup_timer.h"
+
+#include "starboard/once.h"
+
+namespace base {
+namespace StartupTimer {
+namespace {
+
+// StartupTimer is designed to measure time since the startup of the app.
+// It is loader initialized to have the most accurate start time possible.
+class Impl {
+ public:
+  static Impl* Instance();
+  base::TimeTicks StartTime() const { return start_time_; }
+  base::TimeDelta TimeElapsed() const {
+    return base::TimeTicks::Now() - start_time_;
+  }
+
+ private:
+  Impl() : start_time_(base::TimeTicks::Now()) {}
+
+  base::TimeTicks start_time_;
+};
+
+SB_ONCE_INITIALIZE_FUNCTION(Impl, Impl::Instance);
+Impl* s_on_startup_init_dont_use = Impl::Instance();
+
+}  // namespace
+
+TimeTicks StartTime() { return Impl::Instance()->StartTime(); }
+TimeDelta TimeElapsed() { return Impl::Instance()->TimeElapsed(); }
+
+}  // namespace StartupTimer
+}  // namespace base
diff --git a/src/cobalt/base/startup_timer.h b/src/cobalt/base/startup_timer.h
new file mode 100644
index 0000000..b9600e8
--- /dev/null
+++ b/src/cobalt/base/startup_timer.h
@@ -0,0 +1,32 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_BASE_STARTUP_TIMER_H_
+#define COBALT_BASE_STARTUP_TIMER_H_
+
+#include "base/time.h"
+
+namespace base {
+namespace StartupTimer {
+
+// Returns the time when the app started.
+TimeTicks StartTime();
+
+// Returns the time elapsed since the app started.
+TimeDelta TimeElapsed();
+
+}  // namespace StartupTimer
+}  // namespace base
+
+#endif  // COBALT_BASE_STARTUP_TIMER_H_
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index 8929dd1..079661b 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -50,7 +50,9 @@
     MacroOpWithNameOnly(change)                                      \
     MacroOpWithNameOnly(characterData)                               \
     MacroOpWithNameOnly(childList)                                   \
+    MacroOpWithNameOnly(click)                                       \
     MacroOpWithNameOnly(close)                                       \
+    MacroOpWithNameOnly(deviceorientation)                           \
     MacroOpWithNameOnly(durationchange)                              \
     MacroOpWithNameOnly(emptied)                                     \
     MacroOpWithNameOnly(end)                                         \
@@ -70,9 +72,23 @@
     MacroOpWithNameOnly(loadstart)                                   \
     MacroOpWithNameOnly(mark)                                        \
     MacroOpWithNameOnly(message)                                     \
+    MacroOpWithNameOnly(mousedown)                                   \
+    MacroOpWithNameOnly(mouseenter)                                  \
+    MacroOpWithNameOnly(mouseleave)                                  \
+    MacroOpWithNameOnly(mousemove)                                   \
+    MacroOpWithNameOnly(mouseout)                                    \
+    MacroOpWithNameOnly(mouseover)                                   \
+    MacroOpWithNameOnly(mouseup)                                     \
     MacroOpWithNameOnly(nomatch)                                     \
     MacroOpWithNameOnly(off)                                         \
     MacroOpWithNameOnly(open)                                        \
+    MacroOpWithNameOnly(pointerdown)                                 \
+    MacroOpWithNameOnly(pointerenter)                                \
+    MacroOpWithNameOnly(pointerleave)                                \
+    MacroOpWithNameOnly(pointermove)                                 \
+    MacroOpWithNameOnly(pointerout)                                  \
+    MacroOpWithNameOnly(pointerover)                                 \
+    MacroOpWithNameOnly(pointerup)                                   \
     MacroOpWithNameOnly(pause)                                       \
     MacroOpWithNameOnly(play)                                        \
     MacroOpWithNameOnly(playing)                                     \
@@ -105,15 +121,18 @@
     MacroOpWithNameOnly(update)                                      \
     MacroOpWithNameOnly(updateend)                                   \
     MacroOpWithNameOnly(updatestart)                                 \
+    MacroOpWithNameOnly(visibilitychange)                            \
     MacroOpWithNameOnly(voiceschanged)                               \
     MacroOpWithNameOnly(volumechange)                                \
-    MacroOpWithNameOnly(waiting)
+    MacroOpWithNameOnly(waiting)                                     \
+    MacroOpWithNameOnly(wheel)
 
 #define TOKENS_FOR_EACH_WITH_NAME_AND_VALUE(MacroOpWithNameAndValue)    \
     MacroOpWithNameAndValue(active_pseudo_class_selector, "active")     \
     MacroOpWithNameAndValue(after_pseudo_element_selector, "after")     \
     MacroOpWithNameAndValue(aria_atomic, "aria-atomic")                 \
     MacroOpWithNameAndValue(aria_busy, "aria-busy")                     \
+    MacroOpWithNameAndValue(aria_describedby, "aria-describedby")       \
     MacroOpWithNameAndValue(aria_hidden, "aria-hidden")                 \
     MacroOpWithNameAndValue(aria_label, "aria-label")                   \
     MacroOpWithNameAndValue(aria_labelledby, "aria-labelledby")         \
diff --git a/src/cobalt/base/type_id.h b/src/cobalt/base/type_id.h
index de6036a..5d4ae41 100644
--- a/src/cobalt/base/type_id.h
+++ b/src/cobalt/base/type_id.h
@@ -52,6 +52,9 @@
   bool operator==(const TypeId& other) const { return value_ == other.value_; }
   bool operator!=(const TypeId& other) const { return !(*this == other); }
   bool operator<(const TypeId& other) const { return value_ < other.value_; }
+  bool operator<=(const TypeId& other) const { return value_ <= other.value_; }
+  bool operator>(const TypeId& other) const { return value_ > other.value_; }
+  bool operator>=(const TypeId& other) const { return value_ >= other.value_; }
 
  private:
   explicit TypeId(intptr_t value) : value_(value) {}
diff --git a/src/cobalt/bindings/templates/dictionary.h.template b/src/cobalt/bindings/templates/dictionary.h.template
index 686ed73..20c58ff 100644
--- a/src/cobalt/bindings/templates/dictionary.h.template
+++ b/src/cobalt/bindings/templates/dictionary.h.template
@@ -43,6 +43,7 @@
 {% endif %}
 #include <string>
 
+#include "base/optional.h"
 #include "cobalt/script/sequence.h"
 {% for include in includes %}
 #include "{{include}}"
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 5c3231b..02597f5 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -32,6 +32,7 @@
 #include "cobalt/base/init_cobalt.h"
 #include "cobalt/base/language.h"
 #include "cobalt/base/localized_strings.h"
+#include "cobalt/base/startup_timer.h"
 #include "cobalt/base/user_log.h"
 #include "cobalt/browser/memory_settings/auto_mem.h"
 #include "cobalt/browser/memory_settings/checker.h"
@@ -41,6 +42,7 @@
 #include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/math/size.h"
 #include "cobalt/network/network_event.h"
+#include "cobalt/script/javascript_engine.h"
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
 #include "cobalt/storage/savegame_fake.h"
 #endif
@@ -53,21 +55,14 @@
 #endif  // defined(__LB_SHELL__FOR_RELEASE__)
 #include "lbshell/src/lb_memory_pages.h"
 #endif  // defined(__LB_SHELL__)
-#if defined(OS_STARBOARD)
 #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";
 
@@ -345,10 +340,12 @@
 
   // Right now the bytes_per_pixel is assumed in the engine. Any other value
   // is currently forbidden.
-  DCHECK_EQ(2, skia_glyph_atlas_texture_dimensions.bytes_per_pixel());
-  options->renderer_module_options.skia_glyph_texture_atlas_dimensions =
-      math::Size(skia_glyph_atlas_texture_dimensions.width(),
-                 skia_glyph_atlas_texture_dimensions.height());
+  if (skia_glyph_atlas_texture_dimensions.bytes_per_pixel() > 0) {
+    DCHECK_EQ(2, skia_glyph_atlas_texture_dimensions.bytes_per_pixel());
+    options->renderer_module_options.skia_glyph_texture_atlas_dimensions =
+        math::Size(skia_glyph_atlas_texture_dimensions.width(),
+                   skia_glyph_atlas_texture_dimensions.height());
+  }
 
   options->web_module_options.remote_typeface_cache_capacity =
       static_cast<int>(
@@ -365,9 +362,7 @@
 Application::Application(const base::Closure& quit_closure)
     : message_loop_(MessageLoop::current()),
       quit_closure_(quit_closure),
-      start_time_(base::TimeTicks::Now()),
-      stats_update_timer_(true, true),
-      lite_stats_update_timer_(true, true) {
+      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();
@@ -392,10 +387,6 @@
   stats_update_timer_.Start(
       FROM_HERE, base::TimeDelta::FromMilliseconds(kStatUpdatePeriodMs),
       base::Bind(&Application::UpdatePeriodicStats, base::Unretained(this)));
-  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();
@@ -416,6 +407,13 @@
   options.initial_deep_link = GetInitialDeepLink();
   options.network_module_options.preferred_language = language;
 
+  if (command_line->HasSwitch(browser::switches::kFPSPrint)) {
+    options.renderer_module_options.enable_fps_stdout = true;
+  }
+  if (command_line->HasSwitch(browser::switches::kFPSOverlay)) {
+    options.renderer_module_options.enable_fps_overlay = true;
+  }
+
   ApplyCommandLineSettingsToRendererOptions(&options.renderer_module_options);
 
   if (command_line->HasSwitch(browser::switches::kDisableJavaScriptJit)) {
@@ -523,8 +521,9 @@
   }
 
   account_manager_.reset(new account::AccountManager());
-  browser_module_.reset(new BrowserModule(initial_url, system_window_.get(),
-                                          account_manager_.get(), options));
+  browser_module_.reset(
+      new BrowserModule(initial_url, base::kApplicationStateStarted,
+                        system_window_.get(), account_manager_.get(), options));
   UpdateAndMaybeRegisterUserAgent();
 
   app_status_ = kRunningAppStatus;
@@ -650,10 +649,12 @@
     DLOG(INFO) << "Got pause event.";
     app_status_ = kPausedAppStatus;
     ++app_pause_count_;
+    browser_module_->Pause();
   } else if (app_event->type() == system_window::ApplicationEvent::kUnpause) {
     DLOG(INFO) << "Got unpause event.";
     app_status_ = kRunningAppStatus;
     ++app_unpause_count_;
+    browser_module_->Unpause();
   } else if (app_event->type() == system_window::ApplicationEvent::kSuspend) {
     DLOG(INFO) << "Got suspend event.";
     app_status_ = kSuspendedAppStatus;
@@ -693,20 +694,21 @@
                       "Total free application CPU memory remaining."),
       used_cpu_memory("Memory.CPU.Used", 0,
                       "Total CPU memory allocated via the app's allocators."),
-#if !defined(__LB_SHELL__FOR_RELEASE__)
-      exe_memory("Memory.CPU.Exe", 0,
-                 "Total memory occupied by the size of the executable."),
-#endif
+      js_reserved_memory("Memory.JS", 0,
+                         "The total memory that is reserved by the engine, "
+                         "including the part that is actually occupied by "
+                         "JS objects, and the part that is not yet."),
+      app_start_time("Time.Cobalt.Start",
+                     base::StartupTimer::StartTime().ToInternalValue(),
+                     "Start time of the application in microseconds."),
       app_lifetime("Cobalt.Lifetime", base::TimeDelta(),
-                   "Application lifetime.") {
-#if defined(OS_STARBOARD)
+                   "Application lifetime in microseconds.") {
   if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
     free_gpu_memory.emplace("Memory.GPU.Free", 0,
                             "Total free application GPU memory remaining.");
     used_gpu_memory.emplace("Memory.GPU.Used", 0,
                             "Total GPU memory allocated by the application.");
   }
-#endif  // defined(OS_STARBOARD)
 }
 
 void Application::RegisterUserLogs() {
@@ -757,10 +759,6 @@
   }
 }
 
-void Application::UpdatePeriodicLiteStats() {
-  c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_;
-}
-
 math::Size Application::InitSystemWindow(CommandLine* command_line) {
   base::optional<math::Size> viewport_size;
   if (command_line->HasSwitch(browser::switches::kViewport)) {
@@ -814,6 +812,8 @@
 
 void Application::UpdatePeriodicStats() {
   TRACE_EVENT0("cobalt::browser", "Application::UpdatePeriodicStats()");
+  c_val_stats_.app_lifetime = base::StartupTimer::TimeElapsed();
+
   int64_t used_cpu_memory = SbSystemGetUsedCPUMemory();
   base::optional<int64_t> used_gpu_memory;
   if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
@@ -831,6 +831,9 @@
     *c_val_stats_.used_gpu_memory = *used_gpu_memory;
   }
 
+  c_val_stats_.js_reserved_memory =
+      script::JavaScriptEngine::UpdateMemoryStatsAndReturnReserved();
+
   memory_settings_checker_.RunChecks(
       *auto_mem_, used_cpu_memory, used_gpu_memory);
 }
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 47da7ec..6da8a33 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -96,9 +96,6 @@
   base::ThreadChecker network_event_thread_checker_;
   base::ThreadChecker application_event_thread_checker_;
 
-  // Time the application started
-  base::TimeTicks start_time_;
-
 #if defined(ENABLE_WEBDRIVER)
   // WebDriver implementation with embedded HTTP server.
   scoped_ptr<webdriver::WebDriverModule> web_driver_module_;
@@ -141,10 +138,11 @@
     base::optional<base::CVal<base::cval::SizeInBytes, base::CValPublic> >
         used_gpu_memory;
 
-#if !defined(__LB_SHELL__FOR_RELEASE__)
-    base::CVal<base::cval::SizeInBytes, base::CValPublic> exe_memory;
-#endif
+    // The total memory that is reserved by the engine, including the part that
+    // is actually occupied by JS objects, and the part that is not yet.
+    base::CVal<base::cval::SizeInBytes, base::CValPublic> js_reserved_memory;
 
+    base::CVal<int64> app_start_time;
     base::CVal<base::TimeDelta, base::CValPublic> app_lifetime;
   };
 
@@ -152,7 +150,6 @@
   void UpdateAndMaybeRegisterUserAgent();
 
   void UpdatePeriodicStats();
-  void UpdatePeriodicLiteStats();
 
   math::Size InitSystemWindow(CommandLine* command_line);
 
@@ -172,7 +169,6 @@
   CValStats c_val_stats_;
 
   base::Timer stats_update_timer_;
-  base::Timer lite_stats_update_timer_;
 
   scoped_ptr<memory_tracker::Tool> memory_tracker_tool_;
 
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp
index 85a49b2..0741ae3 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
@@ -29,6 +29,7 @@
         'debug_console.h',
         'h5vcc_url_handler.cc',
         'h5vcc_url_handler.h',
+        'lifecycle_observer.h',
         'memory_settings/auto_mem.cc',
         'memory_settings/auto_mem.h',
         'memory_settings/build_settings.cc',
@@ -84,6 +85,8 @@
         'splash_screen.h',
         'storage_upgrade_handler.cc',
         'storage_upgrade_handler.h',
+        'suspend_fuzzer.cc',
+        'suspend_fuzzer.h',
         'switches.cc',
         'switches.h',
         'trace_manager.cc',
@@ -126,6 +129,7 @@
         '<(DEPTH)/cobalt/math/math.gyp:math',
         '<(DEPTH)/cobalt/media_session/media_session.gyp:media_session',
         '<(DEPTH)/cobalt/network/network.gyp:network',
+        '<(DEPTH)/cobalt/page_visibility/page_visibility.gyp:page_visibility',
         '<(DEPTH)/cobalt/renderer/renderer.gyp:renderer',
         '<(DEPTH)/cobalt/script/engine.gyp:engine',
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
@@ -195,7 +199,7 @@
       'variables': {
         # This target includes non-Cobalt code that produces pendantic
         # warnings as errors.
-        'cobalt_code': 0,
+        'sb_pedantic_warnings': 0,
       },
       'conditions': [
         ['enable_screenshot==1', {
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index b954983..0ff0e7b 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -66,6 +66,7 @@
         '../dom/console.idl',
         '../dom/crypto.idl',
         '../dom/data_view.idl',
+        '../dom/device_orientation_event.idl',
         '../dom/document.idl',
         '../dom/document_timeline.idl',
         '../dom/document_type.idl',
@@ -113,6 +114,7 @@
         '../dom/memory_info.idl',
         '../dom/message_event.idl',
         '../dom/mime_type_array.idl',
+        '../dom/mouse_event.idl',
         '../dom/mutation_observer.idl',
         '../dom/mutation_record.idl',
         '../dom/named_node_map.idl',
@@ -122,6 +124,7 @@
         '../dom/performance.idl',
         '../dom/performance_timing.idl',
         '../dom/plugin_array.idl',
+        '../dom/pointer_event.idl',
         '../dom/progress_event.idl',
         '../dom/screen.idl',
         '../dom/security_policy_violation_event.idl',
@@ -144,6 +147,7 @@
         '../dom/video_playback_quality.idl',
         '../dom/video_track.idl',
         '../dom/video_track_list.idl',
+        '../dom/wheel_event.idl',
         '../dom/window.idl',
         '../dom/xml_document.idl',
         '../dom/xml_serializer.idl',
@@ -212,17 +216,26 @@
         '../audio/audio_node_channel_count_mode.idl',
         '../audio/audio_node_channel_interpretation.idl',
         '../dom/blob_property_bag.idl',
+        '../dom/device_orientation_event_init.idl',
         '../dom/dom_parser_supported_type.idl',
         '../dom/event_init.idl',
+        '../dom/event_modifier_init.idl',
+        '../dom/focus_event_init.idl',
+        '../dom/keyboard_event_init.idl',
+        '../dom/ui_event_init.idl',
         '../dom/media_source_end_of_stream_error.idl',
         '../dom/media_source_ready_state.idl',
+        '../dom/mouse_event_init.idl',
         '../dom/mutation_observer_init.idl',
+        '../dom/pointer_event_init.idl',
         '../dom/source_buffer_append_mode.idl',
         '../dom/track_default_type.idl',
+        '../dom/wheel_event_init.idl',
         '../media_session/media_image.idl',
         '../media_session/media_metadata_init.idl',
         '../media_session/media_session_action.idl',
         '../media_session/media_session_playback_state.idl',
+        '../page_visibility/visibility_state.idl',
         '../speech/speech_recognition_error_code.idl',
         '../speech/speech_synthesis_error_code.idl',
         '../web_animations/animation_fill_mode.idl',
@@ -266,6 +279,7 @@
         '../dom/window_session_storage.idl',
         '../dom/window_timers.idl',
         '../media_session/navigator_media_session.idl',
+        '../page_visibility/document.idl',
     ],
 
     'conditions': [
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 50c5e80..d5c6719 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -209,6 +209,7 @@
 }  // namespace
 
 BrowserModule::BrowserModule(const GURL& url,
+                             base::ApplicationState initial_application_state,
                              system_window::SystemWindow* system_window,
                              account::AccountManager* account_manager,
                              const Options& options)
@@ -226,13 +227,17 @@
       input_device_manager_(input::InputDeviceManager::CreateFromWindow(
           base::Bind(&BrowserModule::OnKeyEventProduced,
                      base::Unretained(this)),
+          base::Bind(&BrowserModule::OnPointerEventProduced,
+                     base::Unretained(this)),
+          base::Bind(&BrowserModule::OnWheelEventProduced,
+                     base::Unretained(this)),
           system_window)),
       renderer_module_(system_window, RendererModuleWithCameraOptions(
                                           options.renderer_module_options,
                                           input_device_manager_->camera_3d())),
 #if defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
-      array_buffer_allocator_(new ResourceProviderArrayBufferAllocator(
-          renderer_module_.pipeline()->GetResourceProvider())),
+      array_buffer_allocator_(
+          new ResourceProviderArrayBufferAllocator(GetResourceProvider())),
       array_buffer_cache_(new dom::ArrayBuffer::Cache(3 * 1024 * 1024)),
 #endif  // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
       network_module_(&storage_manager_, system_window->event_dispatcher(),
@@ -245,6 +250,10 @@
       web_module_loaded_(true /* manually_reset */,
                          false /* initially_signalled */),
       web_module_recreated_callback_(options.web_module_recreated_callback),
+      navigate_time_("Time.Browser.Navigate", 0,
+                     "The last time a navigation occurred."),
+      on_load_event_time_("Time.Browser.OnLoadEvent", 0,
+                          "The last time the window.OnLoad event fired."),
 #if defined(ENABLE_DEBUG_CONSOLE)
       ALLOW_THIS_IN_INITIALIZER_LIST(fuzzer_toggle_command_handler_(
           kFuzzerToggleCommand,
@@ -269,8 +278,8 @@
       render_timeout_count_(0),
 #endif
       will_quit_(false),
-      suspended_(false),
-      system_window_(system_window) {
+      system_window_(system_window),
+      application_state_(initial_application_state) {
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
   on_error_triggered_count_ = 0;
@@ -304,10 +313,9 @@
     GetVideoContainerSizeOverride(&output_size);
 #endif
 
-    media_module_ = (media::MediaModule::Create(
-        system_window, output_size,
-        renderer_module_.pipeline()->GetResourceProvider(),
-        options.media_module_options));
+    media_module_ = (media::MediaModule::Create(system_window, output_size,
+                                                GetResourceProvider(),
+                                                options.media_module_options));
   }
 
   // Setup our main web module to have the H5VCC API injected into it.
@@ -326,18 +334,24 @@
   if (command_line->HasSwitch(switches::kInputFuzzer)) {
     OnFuzzerToggle(std::string());
   }
+  if (command_line->HasSwitch(switches::kSuspendFuzzer)) {
+#if SB_API_VERSION >= 4
+    suspend_fuzzer_.emplace();
+#endif
+  }
 #endif  // ENABLE_DEBUG_CONSOLE && ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   debug_console_.reset(new DebugConsole(
+      application_state_,
       base::Bind(&BrowserModule::QueueOnDebugConsoleRenderTreeProduced,
                  base::Unretained(this)),
       media_module_.get(), &network_module_,
-      renderer_module_.render_target()->GetSize(),
-      renderer_module_.pipeline()->GetResourceProvider(),
+      renderer_module_.render_target()->GetSize(), GetResourceProvider(),
       kLayoutMaxRefreshFrequencyInHz,
       base::Bind(&BrowserModule::GetDebugServer, base::Unretained(this)),
       web_module_options_.javascript_options));
+  lifecycle_observers_.AddObserver(debug_console_.get());
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
   // Always render the debug console. It will draw nothing if disabled.
@@ -405,17 +419,25 @@
   // Destroy old WebModule first, so we don't get a memory high-watermark after
   // the second WebModule's constructor runs, but before scoped_ptr::reset() is
   // run.
+  if (web_module_) {
+    lifecycle_observers_.RemoveObserver(web_module_.get());
+  }
   web_module_.reset(NULL);
 
+  // Wait until after the old WebModule is destroyed before setting the navigate
+  // time so that it won't be included in the time taken to load the URL.
+  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
+
   // Show a splash screen while we're waiting for the web page to load.
   const math::Size& viewport_size = renderer_module_.render_target()->GetSize();
   DestroySplashScreen();
   splash_screen_.reset(
-      new SplashScreen(base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
+      new SplashScreen(application_state_,
+                       base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
                                   base::Unretained(this)),
-                       &network_module_, viewport_size,
-                       renderer_module_.pipeline()->GetResourceProvider(),
+                       &network_module_, viewport_size, GetResourceProvider(),
                        kLayoutMaxRefreshFrequencyInHz));
+  lifecycle_observers_.AddObserver(splash_screen_.get());
 
   // Create new WebModule.
 #if !defined(COBALT_FORCE_CSP)
@@ -444,14 +466,16 @@
       COBALT_IMAGE_CACHE_CAPACITY_MULTIPLIER_WHEN_PLAYING_VIDEO;
   options.camera_3d = input_device_manager_->camera_3d();
   web_module_.reset(new WebModule(
-      url, base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
-                      base::Unretained(this)),
+      url, application_state_,
+      base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
+                 base::Unretained(this)),
       base::Bind(&BrowserModule::OnError, base::Unretained(this)),
       base::Bind(&BrowserModule::OnWindowClose, base::Unretained(this)),
       base::Bind(&BrowserModule::OnWindowMinimize, base::Unretained(this)),
       media_module_.get(), &network_module_, viewport_size,
-      renderer_module_.pipeline()->GetResourceProvider(), system_window_,
-      kLayoutMaxRefreshFrequencyInHz, options));
+      GetResourceProvider(), system_window_, kLayoutMaxRefreshFrequencyInHz,
+      options));
+  lifecycle_observers_.AddObserver(web_module_.get());
   if (!web_module_recreated_callback_.is_null()) {
     web_module_recreated_callback_.Run();
   }
@@ -473,6 +497,7 @@
   // changed unless the corresponding benchmark logic is changed as well.
   LOG(INFO) << "Loaded WebModule";
 
+  on_load_event_time_ = base::TimeTicks::Now().ToInternalValue();
   web_module_loaded_.Signal();
 }
 
@@ -632,40 +657,77 @@
 
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
-void BrowserModule::OnKeyEventProduced(const dom::KeyboardEvent::Data& event) {
+void BrowserModule::OnKeyEventProduced(base::Token type,
+                                       const dom::KeyboardEventInit& event) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::OnKeyEventProduced()");
   if (MessageLoop::current() != self_message_loop_) {
     self_message_loop_->PostTask(
-        FROM_HERE,
-        base::Bind(&BrowserModule::OnKeyEventProduced, weak_this_, event));
+        FROM_HERE, base::Bind(&BrowserModule::OnKeyEventProduced, weak_this_,
+                              type, event));
     return;
   }
 
   // Filter the key event.
-  if (!FilterKeyEvent(event)) {
+  if (!FilterKeyEvent(type, event)) {
+    return;
+  }
+
+  InjectKeyEventToMainWebModule(type, event);
+}
+
+void BrowserModule::OnPointerEventProduced(base::Token type,
+                                           const dom::PointerEventInit& event) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::OnPointerEventProduced()");
+  if (MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->PostTask(
+        FROM_HERE, base::Bind(&BrowserModule::OnPointerEventProduced,
+                              weak_this_, type, event));
     return;
   }
 
 #if defined(ENABLE_DEBUG_CONSOLE)
-  trace_manager_.OnKeyEventProduced();
+  trace_manager_.OnInputEventProduced();
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
-  InjectKeyEventToMainWebModule(event);
+  DCHECK(web_module_);
+  web_module_->InjectPointerEvent(type, event);
+}
+
+void BrowserModule::OnWheelEventProduced(base::Token type,
+                                         const dom::WheelEventInit& event) {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::OnWheelEventProduced()");
+  if (MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->PostTask(
+        FROM_HERE, base::Bind(&BrowserModule::OnWheelEventProduced, weak_this_,
+                              type, event));
+    return;
+  }
+
+#if defined(ENABLE_DEBUG_CONSOLE)
+  trace_manager_.OnInputEventProduced();
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+
+  DCHECK(web_module_);
+  web_module_->InjectWheelEvent(type, event);
 }
 
 void BrowserModule::InjectKeyEventToMainWebModule(
-    const dom::KeyboardEvent::Data& event) {
+    base::Token type, const dom::KeyboardEventInit& event) {
   TRACE_EVENT0("cobalt::browser",
                "BrowserModule::InjectKeyEventToMainWebModule()");
   if (MessageLoop::current() != self_message_loop_) {
     self_message_loop_->PostTask(
         FROM_HERE, base::Bind(&BrowserModule::InjectKeyEventToMainWebModule,
-                              weak_this_, event));
+                              weak_this_, type, event));
     return;
   }
 
+#if defined(ENABLE_DEBUG_CONSOLE)
+  trace_manager_.OnInputEventProduced();
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
+
   DCHECK(web_module_);
-  web_module_->InjectKeyboardEvent(event);
+  web_module_->InjectKeyboardEvent(type, event);
 }
 
 void BrowserModule::OnError(const GURL& url, const std::string& error) {
@@ -682,10 +744,11 @@
   Navigate(GURL(url_string));
 }
 
-bool BrowserModule::FilterKeyEvent(const dom::KeyboardEvent::Data& event) {
+bool BrowserModule::FilterKeyEvent(base::Token type,
+                                   const dom::KeyboardEventInit& event) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::FilterKeyEvent()");
   // Check for hotkeys first. If it is a hotkey, no more processing is needed.
-  if (!FilterKeyEventForHotkeys(event)) {
+  if (!FilterKeyEventForHotkeys(type, event)) {
     return false;
   }
 
@@ -693,7 +756,7 @@
   // If the debug console is fully visible, it gets the next chance to handle
   // key events.
   if (debug_console_->GetMode() >= debug::DebugHub::kDebugConsoleOn) {
-    if (!debug_console_->FilterKeyEvent(event)) {
+    if (!debug_console_->FilterKeyEvent(type, event)) {
       return false;
     }
   }
@@ -703,14 +766,14 @@
 }
 
 bool BrowserModule::FilterKeyEventForHotkeys(
-    const dom::KeyboardEvent::Data& event) {
+    base::Token type, const dom::KeyboardEventInit& event) {
 #if !defined(ENABLE_DEBUG_CONSOLE)
+  UNREFERENCED_PARAMETER(type);
   UNREFERENCED_PARAMETER(event);
 #else
-  if (event.key_code == dom::keycode::kF1 ||
-      (event.modifiers & dom::UIEventWithKeyState::kCtrlKey &&
-       event.key_code == dom::keycode::kO)) {
-    if (event.type == dom::KeyboardEvent::kTypeKeyDown) {
+  if (event.key_code() == dom::keycode::kF1 ||
+      (event.ctrl_key() && event.key_code() == dom::keycode::kO)) {
+    if (type == base::Tokens::keydown()) {
       // Ctrl+O toggles the debug console display.
       debug_console_->CycleMode();
     }
@@ -751,6 +814,9 @@
 
 void BrowserModule::DestroySplashScreen() {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroySplashScreen()");
+  if (splash_screen_) {
+    lifecycle_observers_.RemoveObserver(splash_screen_.get());
+  }
   splash_screen_.reset(NULL);
 }
 
@@ -767,17 +833,11 @@
     const webdriver::protocol::WindowId& window_id) {
   // Repost to our message loop to ensure synchronous access to |web_module_|.
   scoped_ptr<webdriver::WindowDriver> window_driver;
-  self_message_loop_->PostTask(
+  self_message_loop_->PostBlockingTask(
       FROM_HERE, base::Bind(&BrowserModule::CreateWindowDriverInternal,
                             base::Unretained(this), window_id,
                             base::Unretained(&window_driver)));
 
-  // Wait for the result and return it.
-  base::WaitableEvent got_window_driver(true, false);
-  self_message_loop_->PostTask(
-      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
-                            base::Unretained(&got_window_driver)));
-  got_window_driver.Wait();
   // This log is relied on by the webdriver benchmark tests, so it shouldn't be
   // changed unless the corresponding benchmark logic is changed as well.
   LOG(INFO) << "Created WindowDriver: ID=" << window_id.id();
@@ -798,17 +858,10 @@
 debug::DebugServer* BrowserModule::GetDebugServer() {
   // Repost to our message loop to ensure synchronous access to |web_module_|.
   debug::DebugServer* debug_server = NULL;
-  self_message_loop_->PostTask(
+  self_message_loop_->PostBlockingTask(
       FROM_HERE,
       base::Bind(&BrowserModule::GetDebugServerInternal, base::Unretained(this),
                  base::Unretained(&debug_server)));
-
-  // Wait for the result and return it.
-  base::WaitableEvent got_debug_server(true, false);
-  self_message_loop_->PostTask(FROM_HERE,
-                               base::Bind(&base::WaitableEvent::Signal,
-                                          base::Unretained(&got_debug_server)));
-  got_debug_server.Wait();
   DCHECK(debug_server);
   return debug_server;
 }
@@ -826,24 +879,36 @@
   network_module_.SetProxy(proxy_rules);
 }
 
+void BrowserModule::Start() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Start()");
+  DCHECK(application_state_ == base::kApplicationStatePreloading);
+  render_tree::ResourceProvider* resource_provider = GetResourceProvider();
+  FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_,
+                    Start(resource_provider));
+  application_state_ = base::kApplicationStateStarted;
+}
+
+void BrowserModule::Pause() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Pause()");
+  DCHECK(application_state_ == base::kApplicationStateStarted);
+  FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_, Pause());
+  application_state_ = base::kApplicationStatePaused;
+}
+
+void BrowserModule::Unpause() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::Unpause()");
+  DCHECK(application_state_ == base::kApplicationStatePaused);
+  FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_, Unpause());
+  application_state_ = base::kApplicationStateStarted;
+}
+
 void BrowserModule::Suspend() {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::Suspend()");
-  DCHECK_EQ(MessageLoop::current(), self_message_loop_);
-  DCHECK(!suspended_);
+  DCHECK(application_state_ == base::kApplicationStatePaused);
 
-// 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();
-  }
-#endif
-  if (splash_screen_) {
-    splash_screen_->Suspend();
-  }
-  if (web_module_) {
-    web_module_->Suspend();
-  }
+  // First suspend all our web modules which implies that they will release
+  // their resource provider and all resources created through it.
+  FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_, Suspend());
 
   // Flush out any submitted render trees pushed since we started shutting down
   // the web modules above.
@@ -877,21 +942,19 @@
   // graphical resources.
   renderer_module_.Suspend();
 
-  suspended_ = true;
+  application_state_ = base::kApplicationStateSuspended;
 }
 
 void BrowserModule::Resume() {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::Resume()");
-  DCHECK_EQ(MessageLoop::current(), self_message_loop_);
-  DCHECK(suspended_);
+  DCHECK(application_state_ == base::kApplicationStateSuspended);
 
   renderer_module_.Resume();
 
   // Note that at this point, it is probable that this resource provider is
   // different than the one that was managed in the associated call to
   // Suspend().
-  render_tree::ResourceProvider* resource_provider =
-      renderer_module_.pipeline()->GetResourceProvider();
+  render_tree::ResourceProvider* resource_provider = GetResourceProvider();
 
   media_module_->Resume(resource_provider);
 
@@ -901,19 +964,10 @@
   NOTREACHED();
 #endif  // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
 
-#if defined(ENABLE_DEBUG_CONSOLE)
-  if (debug_console_) {
-    debug_console_->Resume(resource_provider);
-  }
-#endif
-  if (splash_screen_) {
-    splash_screen_->Resume(resource_provider);
-  }
-  if (web_module_) {
-    web_module_->Resume(resource_provider);
-  }
+  FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_,
+                    Resume(resource_provider));
 
-  suspended_ = false;
+  application_state_ = base::kApplicationStatePaused;
 }
 
 #if defined(OS_STARBOARD)
@@ -975,5 +1029,9 @@
 }
 #endif
 
+render_tree::ResourceProvider* BrowserModule::GetResourceProvider() {
+  return renderer_module_.resource_provider();
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 8147240..2f12650 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -19,19 +19,25 @@
 #include <vector>
 
 #include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "cobalt/account/account_manager.h"
+#include "cobalt/base/application_state.h"
 #include "cobalt/base/message_queue.h"
 #include "cobalt/browser/h5vcc_url_handler.h"
+#include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/browser/render_tree_combiner.h"
 #include "cobalt/browser/screen_shot_writer.h"
 #include "cobalt/browser/splash_screen.h"
+#include "cobalt/browser/suspend_fuzzer.h"
 #include "cobalt/browser/url_handler.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/dom/array_buffer.h"
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/input/input_device_manager.h"
 #include "cobalt/layout/layout_manager.h"
 #include "cobalt/network/network_module.h"
@@ -75,7 +81,9 @@
   // a URL before using it to initialize a new WebModule.
   typedef std::vector<URLHandler::URLHandlerCallback> URLHandlerCollection;
 
-  BrowserModule(const GURL& url, system_window::SystemWindow* system_window,
+  BrowserModule(const GURL& url,
+                base::ApplicationState initial_application_state,
+                system_window::SystemWindow* system_window,
                 account::AccountManager* account_manager,
                 const Options& options);
   ~BrowserModule();
@@ -115,11 +123,11 @@
   // Change the network proxy settings while the application is running.
   void SetProxy(const std::string& proxy_rules);
 
-  // Suspends the browser module from activity, and releases all graphical
-  // resources, placing the application into a low-memory state.
+  // LifecycleObserver-similar interface.
+  void Start();
+  void Pause();
+  void Unpause();
   void Suspend();
-
-  // Undoes the call to Suspend(), returning to normal functionality.
   void Resume();
 
  private:
@@ -154,15 +162,28 @@
   // persist the user's preference.
   void SaveDebugConsoleMode();
 
-  // Glue function to deal with the production of an input event from the
-  // input device, and manage handing it off to the web module for
+  // Glue function to deal with the production of a keyboard input event from a
+  // keyboard input device, and manage handing it off to the web module for
   // interpretation.
-  void OnKeyEventProduced(const dom::KeyboardEvent::Data& event);
+  void OnKeyEventProduced(base::Token type,
+                          const dom::KeyboardEventInit& event);
+
+  // Glue function to deal with the production of a pointer input event from a
+  // pointer input device, and manage handing it off to the web module for
+  // interpretation.
+  void OnPointerEventProduced(base::Token type,
+                              const dom::PointerEventInit& event);
+
+  // Glue function to deal with the production of a wheel input event from a
+  // wheel input device, and manage handing it off to the web module for
+  // interpretation.
+  void OnWheelEventProduced(base::Token type, const dom::WheelEventInit& event);
 
   // Injects a key event directly into the main web module, useful for setting
   // up an input fuzzer whose input should be sent directly to the main
   // web module and not filtered into the debug console.
-  void InjectKeyEventToMainWebModule(const dom::KeyboardEvent::Data& event);
+  void InjectKeyEventToMainWebModule(base::Token type,
+                                     const dom::KeyboardEventInit& event);
 
   // Error callback for any error that stops the program.
   void OnError(const GURL& url, const std::string& error);
@@ -170,12 +191,13 @@
   // Filters a key event.
   // Returns true if the event should be passed on to other handlers,
   // false if it was consumed within this function.
-  bool FilterKeyEvent(const dom::KeyboardEvent::Data& event);
+  bool FilterKeyEvent(base::Token type, const dom::KeyboardEventInit& event);
 
   // Filters a key event for hotkeys.
   // Returns true if the event should be passed on to other handlers,
   // false if it was consumed within this function.
-  bool FilterKeyEventForHotkeys(const dom::KeyboardEvent::Data& event);
+  bool FilterKeyEventForHotkeys(base::Token type,
+                                const dom::KeyboardEventInit& event);
 
   // Tries all registered URL handlers for a URL. Returns true if one of the
   // handlers handled the URL, false if otherwise.
@@ -229,6 +251,8 @@
   void OnPollForRenderTimeout(const GURL& url);
 #endif
 
+  render_tree::ResourceProvider* GetResourceProvider();
+
   // TODO:
   //     WeakPtr usage here can be avoided if BrowserModule has a thread to
   //     own where it can ensure that its tasks are all resolved when it is
@@ -310,6 +334,13 @@
   // which could occur on navigation.
   base::Closure web_module_recreated_callback_;
 
+  // The time when a URL navigation starts. This is recorded after the previous
+  // WebModule is destroyed.
+  base::CVal<int64> navigate_time_;
+
+  // The time when the WebModule's Window.onload event is fired.
+  base::CVal<int64> on_load_event_time_;
+
 #if defined(ENABLE_DEBUG_CONSOLE)
   // Possibly null, but if not, will contain a reference to an instance of
   // a debug fuzzer input device manager.
@@ -330,6 +361,8 @@
   // Command handler object for screenshot command from the debug console.
   base::ConsoleCommandManager::CommandHandler screenshot_command_handler_;
 #endif  // defined(ENABLE_SCREENSHOT)
+
+  base::optional<SuspendFuzzer> suspend_fuzzer_;
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
   // Handler object for h5vcc URLs.
@@ -362,9 +395,13 @@
   // ensure synchronous access.
   base::Lock quit_lock_;
 
-  bool suspended_;
-
   system_window::SystemWindow* system_window_;
+
+  // The current application state.
+  base::ApplicationState application_state_;
+
+  // The list of LifecycleObserver that need to be managed.
+  ObserverList<LifecycleObserver> lifecycle_observers_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/cobalt.gyp b/src/cobalt/browser/cobalt.gyp
index d858eac..d2d2f29 100644
--- a/src/cobalt/browser/cobalt.gyp
+++ b/src/cobalt/browser/cobalt.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/browser/debug_console.cc b/src/cobalt/browser/debug_console.cc
index fea12f6..eaa2b2d 100644
--- a/src/cobalt/browser/debug_console.cc
+++ b/src/cobalt/browser/debug_console.cc
@@ -159,6 +159,7 @@
 }  // namespace
 
 DebugConsole::DebugConsole(
+    base::ApplicationState initial_application_state,
     const WebModule::OnRenderTreeProducedCallback&
         render_tree_produced_callback,
     media::MediaModule* media_module, network::NetworkModule* network_module,
@@ -187,7 +188,8 @@
                  base::Bind(&DebugConsole::GetMode, base::Unretained(this)),
                  get_debug_server_callback);
   web_module_.reset(new WebModule(
-      GURL(kInitialDebugConsoleUrl), render_tree_produced_callback,
+      GURL(kInitialDebugConsoleUrl), initial_application_state,
+      render_tree_produced_callback,
       base::Bind(&DebugConsole::OnError, base::Unretained(this)),
       base::Closure(), /* window_close_callback */
       base::Closure(), /* window_minimize_callback */
@@ -197,10 +199,11 @@
 
 DebugConsole::~DebugConsole() {}
 
-bool DebugConsole::FilterKeyEvent(const dom::KeyboardEvent::Data& event) {
+bool DebugConsole::FilterKeyEvent(base::Token type,
+                                  const dom::KeyboardEventInit& event) {
   // Assume here the full debug console is visible - pass all events to its
   // web module, and return false to indicate the event has been consumed.
-  web_module_->InjectKeyboardEvent(event);
+  web_module_->InjectKeyboardEvent(type, event);
   return false;
 }
 
@@ -229,12 +232,6 @@
   return mode_;
 }
 
-void DebugConsole::Suspend() { web_module_->Suspend(); }
-
-void DebugConsole::Resume(render_tree::ResourceProvider* resource_provider) {
-  web_module_->Resume(resource_provider);
-}
-
 }  // namespace browser
 }  // namespace cobalt
 
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index 7d87f00..0a55b35 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -21,9 +21,11 @@
 
 #include "base/callback.h"
 #include "base/logging.h"
+#include "cobalt/browser/lifecycle_observer.h"
+#include "cobalt/base/token.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/debug/debug_hub.h"
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "googleurl/src/gurl.h"
 
 namespace cobalt {
@@ -31,9 +33,10 @@
 
 // DebugConsole wraps the web module and all components used to implement the
 // debug console.
-class DebugConsole {
+class DebugConsole : public LifecycleObserver {
  public:
   DebugConsole(
+      base::ApplicationState initial_application_state,
       const WebModule::OnRenderTreeProducedCallback&
           render_tree_produced_callback,
       media::MediaModule* media_module, network::NetworkModule* network_module,
@@ -47,7 +50,7 @@
   // Filters a key event.
   // Returns true if the event should be passed on to other handlers,
   // false if it was consumed within this function.
-  bool FilterKeyEvent(const dom::KeyboardEvent::Data& event);
+  bool FilterKeyEvent(base::Token type, const dom::KeyboardEventInit& event);
 
   const WebModule& web_module() const { return *web_module_; }
   WebModule& web_module() { return *web_module_; }
@@ -59,8 +62,16 @@
   // Returns the currently set debug console visibility mode.
   int GetMode();
 
-  void Suspend();
-  void Resume(render_tree::ResourceProvider* resource_provider);
+  // LifecycleObserver implementation.
+  void Start(render_tree::ResourceProvider* resource_provider) OVERRIDE {
+    web_module_->Start(resource_provider);
+  }
+  void Pause() OVERRIDE { web_module_->Pause(); }
+  void Unpause() OVERRIDE { web_module_->Unpause(); }
+  void Suspend() OVERRIDE { web_module_->Suspend(); }
+  void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE {
+    web_module_->Resume(resource_provider);
+  }
 
  private:
   void OnError(const GURL& /* url */, const std::string& error) {
diff --git a/src/cobalt/browser/h5vcc_url_handler.cc b/src/cobalt/browser/h5vcc_url_handler.cc
index 16825aa..94a8ed9 100644
--- a/src/cobalt/browser/h5vcc_url_handler.cc
+++ b/src/cobalt/browser/h5vcc_url_handler.cc
@@ -108,14 +108,19 @@
 
 void H5vccURLHandler::OnNetworkFailureDialogResponse(
     system_window::SystemWindow::DialogResponse response) {
-  UNREFERENCED_PARAMETER(response);
   const std::string retry_url = GetH5vccUrlQueryParam(url_, kRetryParam);
-  if (retry_url.length() > 0) {
+  // A positive response means we should retry.
+  if (response == system_window::SystemWindow::kDialogPositiveResponse &&
+      retry_url.length() > 0) {
     GURL url(retry_url);
     if (url.is_valid()) {
       browser_module()->Navigate(GURL(retry_url));
+      return;
     }
   }
+  // We were told not to retry, or don't have a retry URL, so leave the app.
+  LOG(ERROR) << "Stop after network error";
+  SbSystemRequestStop(0);
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/lifecycle_observer.h b/src/cobalt/browser/lifecycle_observer.h
new file mode 100644
index 0000000..fb69e9e
--- /dev/null
+++ b/src/cobalt/browser/lifecycle_observer.h
@@ -0,0 +1,56 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_LIFECYCLE_OBSERVER_H_
+#define COBALT_BROWSER_LIFECYCLE_OBSERVER_H_
+
+#include "cobalt/render_tree/resource_provider.h"
+#include "starboard/configuration.h"
+
+namespace cobalt {
+namespace browser {
+
+// A pure virtual interface for observers of the application lifecycle.
+class LifecycleObserver {
+ public:
+  // Start running visibly with the given graphics ResourceProvider, loading if
+  // necessary. This represents a transition from Preloading to Started, so it
+  // only makes sense if the object is in the Preloading state.
+  virtual void Start(render_tree::ResourceProvider* resource_provider) = 0;
+
+  // Pauses from Started, staying visible and retaining graphics resources.
+  virtual void Pause() = 0;
+
+  // Unpauses, going back to to the Started state, and continuing to
+  // use the same ResourceProvider and graphics resources.
+  virtual void Unpause() = 0;
+
+  // Suspends from Paused, and releases its reference to the ResourceProvider,
+  // additionally releasing all references to any resources created from
+  // it. This method must only be called if the object has previously been
+  // Paused.
+  virtual void Suspend() = 0;
+
+  // Resumes to Paused from Suspended, with a new ResourceProvider. This method
+  // must only be called if the object has previously been Suspended.
+  virtual void Resume(render_tree::ResourceProvider* resource_provider) = 0;
+
+ protected:
+  virtual ~LifecycleObserver() {}
+};
+
+}  // namespace browser
+}  // namespace cobalt
+
+#endif  // COBALT_BROWSER_LIFECYCLE_OBSERVER_H_
diff --git a/src/cobalt/browser/memory_settings/memory_settings.cc b/src/cobalt/browser/memory_settings/memory_settings.cc
index 5da12a6..caabaf3 100644
--- a/src/cobalt/browser/memory_settings/memory_settings.cc
+++ b/src/cobalt/browser/memory_settings/memory_settings.cc
@@ -17,6 +17,7 @@
 #include "cobalt/browser/memory_settings/memory_settings.h"
 
 #include <algorithm>
+#include <locale>
 #include <string>
 #include <vector>
 
@@ -46,11 +47,21 @@
   bool error_;  // true if there was a parse error.
 };
 
-// Parses a string like "1234x5678" to vector of parsed int values.
-std::vector<ParsedIntValue> ParseDimensions(const std::string& input) {
+char ToLowerCharTypesafe(int c) {
+  return static_cast<char>(::tolower(c));
+}
+
+std::string ToLower(const std::string& input) {
   std::string value_str = input;
   std::transform(value_str.begin(), value_str.end(),
-                 value_str.begin(), ::tolower);
+                 value_str.begin(), ToLowerCharTypesafe);
+
+  return value_str;
+}
+
+// Parses a string like "1234x5678" to vector of parsed int values.
+std::vector<ParsedIntValue> ParseDimensions(const std::string& input) {
+  std::string value_str = ToLower(input);
   std::vector<ParsedIntValue> output;
 
   std::vector<std::string> lengths;
@@ -86,10 +97,7 @@
   }
 
   // Lowercasing the string makes the units easier to detect.
-  std::string value_lower_case = value;
-  std::transform(value_lower_case.begin(), value_lower_case.end(),
-                 value_lower_case.begin(),
-                 ::tolower);
+  std::string value_lower_case = ToLower(value);
 
   if (StringEndsWith(value_lower_case, "kb")) {
     numerical_value *= 1024;  // convert kb -> bytes.
diff --git a/src/cobalt/browser/memory_tracker/tool/compressed_time_series_tool.cc b/src/cobalt/browser/memory_tracker/tool/compressed_time_series_tool.cc
index 2942749..c89f3aa 100644
--- a/src/cobalt/browser/memory_tracker/tool/compressed_time_series_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/compressed_time_series_tool.cc
@@ -136,7 +136,7 @@
     for (MapIt it = samples.begin(); it != samples.end(); ++it) {
       const int64 alloc_bytes = it->second.allocated_bytes_[i];
       // Convert to float megabytes with decimals of precision.
-      double n = alloc_bytes / (1000 * 10);
+      double n = static_cast<double>(alloc_bytes / (1000 * 10));
       n = n / (100.);
       ss << n << kDelimiter;
     }
diff --git a/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc b/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
index 65ad229..428c0af 100644
--- a/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/log_writer_tool.cc
@@ -75,7 +75,7 @@
   // this loop only iterates once.
   for (size_t i = kStartIndex; i < end_index; ++i) {
     void* p = addresses[i];
-    int bytes_written =
+    bytes_written =
         SbStringFormatF(buff + buff_pos, sizeof(buff) - buff_pos,
                         " %" PRIXPTR "", reinterpret_cast<uintptr_t>(p));
     DCHECK_GE(bytes_written, 0);
diff --git a/src/cobalt/browser/memory_tracker/tool/print_csv_tool.cc b/src/cobalt/browser/memory_tracker/tool/print_csv_tool.cc
index 51ef41f..64db442 100644
--- a/src/cobalt/browser/memory_tracker/tool/print_csv_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/print_csv_tool.cc
@@ -87,14 +87,14 @@
       continue;
     }
 
-    const AllocationSamples& samples = it->second;
-    if (samples.allocated_bytes_.empty() ||
-        samples.number_allocations_.empty()) {
+    const AllocationSamples& samples_ref = it->second;
+    if (samples_ref.allocated_bytes_.empty() ||
+      samples_ref.number_allocations_.empty()) {
       SB_NOTREACHED() << "Should not be here";
       return "ERROR";
     }
-    const int64 n_allocs = samples.number_allocations_.back();
-    const int64 n_bytes = samples.allocated_bytes_.back();
+    const int64 n_allocs = samples_ref.number_allocations_.back();
+    const int64 n_bytes = samples_ref.allocated_bytes_.back();
     int64 bytes_per_alloc = 0;
     if (n_allocs > 0) {
       bytes_per_alloc = n_bytes / n_allocs;
@@ -135,14 +135,14 @@
       }
       const int64 alloc_bytes = it->second.allocated_bytes_[i];
       // Convert to float megabytes with decimals of precision.
-      double n = alloc_bytes / (1000 * 10);
+      double n = static_cast<double>(alloc_bytes / (1000 * 10));
       n = n / (100.);
       ss << n << kDelimiter;
     }
     if (total_cpu_memory_it != samples.end()) {
       const int64 alloc_bytes = total_cpu_memory_it->second.allocated_bytes_[i];
       // Convert to float megabytes with decimals of precision.
-      double n = alloc_bytes / (1000 * 10);
+      double n = static_cast<double>(alloc_bytes / (1000 * 10));
       n = n / (100.);
       ss << n << kDelimiter;
     }
diff --git a/src/cobalt/browser/memory_tracker/tool/tool_impl.cc b/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
index 01634e0..f2a92bb 100644
--- a/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
+++ b/src/cobalt/browser/memory_tracker/tool/tool_impl.cc
@@ -54,7 +54,7 @@
 
 size_t AllocationSizeBinner::GetBucketIndexForAllocationSize(size_t size) {
   for (int i = 0; i < 32; ++i) {
-    size_t val = 0x1 << i;
+    size_t val = static_cast<size_t>(0x1) << i;
     if (val > size) {
       return i;
     }
@@ -76,7 +76,7 @@
     *max_value = 0;
     return;
   }
-  *min_value = 0x1 << (idx - 1);
+  *min_value = static_cast<size_t>(0x1) << (idx - 1);
   *max_value = (*min_value << 1) - 1;
   return;
 }
@@ -86,7 +86,7 @@
   size_t largest_allocation_total_idx = 0;
 
   for (size_t i = 0; i < allocation_histogram_.size(); ++i) {
-    size_t alloc_size = 0x1 << i;
+    size_t alloc_size = static_cast<size_t>(0x1) << i;
     size_t count = allocation_histogram_[i];
     int64 allocation_total =
         static_cast<int64>(alloc_size) * static_cast<int64>(count);
diff --git a/src/cobalt/browser/resource_provider_array_buffer_allocator.cc b/src/cobalt/browser/resource_provider_array_buffer_allocator.cc
index 7f2ee02..ef9020e 100644
--- a/src/cobalt/browser/resource_provider_array_buffer_allocator.cc
+++ b/src/cobalt/browser/resource_provider_array_buffer_allocator.cc
@@ -27,8 +27,8 @@
   DCHECK(gpu_memory_buffer_space_->GetMemory());
 
   gpu_memory_pool_.set(starboard::make_scoped_ptr(
-      new nb::MemoryPool(gpu_memory_buffer_space_->GetMemory(),
-                         gpu_memory_buffer_space_->GetSizeInBytes())));
+      new nb::FirstFitMemoryPool(gpu_memory_buffer_space_->GetMemory(),
+                                 gpu_memory_buffer_space_->GetSizeInBytes())));
 }
 
 void* ResourceProviderArrayBufferAllocator::Allocate(size_t size) {
diff --git a/src/cobalt/browser/resource_provider_array_buffer_allocator.h b/src/cobalt/browser/resource_provider_array_buffer_allocator.h
index c9de006..98734cb 100644
--- a/src/cobalt/browser/resource_provider_array_buffer_allocator.h
+++ b/src/cobalt/browser/resource_provider_array_buffer_allocator.h
@@ -37,7 +37,7 @@
   void Free(void* p) OVERRIDE;
 
   scoped_ptr<render_tree::RawImageMemory> gpu_memory_buffer_space_;
-  starboard::LockedPtr<nb::MemoryPool> gpu_memory_pool_;
+  starboard::LockedPtr<nb::FirstFitMemoryPool> gpu_memory_pool_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc
index ae60aa5..d2b8e12 100644
--- a/src/cobalt/browser/splash_screen.cc
+++ b/src/cobalt/browser/splash_screen.cc
@@ -24,7 +24,8 @@
 const char SplashScreen::Options::kDefaultSplashScreenURL[] =
     "h5vcc-embedded://splash_screen.html";
 
-SplashScreen::SplashScreen(const WebModule::OnRenderTreeProducedCallback&
+SplashScreen::SplashScreen(base::ApplicationState initial_application_state,
+                           const WebModule::OnRenderTreeProducedCallback&
                                render_tree_produced_callback,
                            network::NetworkModule* network_module,
                            const math::Size& window_dimensions,
@@ -44,7 +45,7 @@
       base::kThreadPriority_High;
 
   web_module_.reset(new WebModule(
-      options.url,
+      options.url, initial_application_state,
       base::Bind(&SplashScreen::OnRenderTreeProduced, base::Unretained(this)),
       base::Bind(&SplashScreen::OnError, base::Unretained(this)),
       base::Bind(&SplashScreen::OnWindowClosed, base::Unretained(this)),
@@ -60,11 +61,6 @@
   web_module_.reset();
 }
 
-void SplashScreen::Suspend() { web_module_->Suspend(); }
-void SplashScreen::Resume(render_tree::ResourceProvider* resource_provider) {
-  web_module_->Resume(resource_provider);
-}
-
 void SplashScreen::WaitUntilReady() {
   is_ready_.Wait();
 }
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index a0d65f7..8ca9ba5 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -20,6 +20,7 @@
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/synchronization/waitable_event.h"
+#include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/media/media_module_stub.h"
 #include "googleurl/src/gurl.h"
@@ -29,7 +30,7 @@
 
 // SplashScreen uses a WebModule to present a splash screen.
 //
-class SplashScreen {
+class SplashScreen : public LifecycleObserver {
  public:
   struct Options {
     Options() : url(kDefaultSplashScreenURL) {}
@@ -37,7 +38,8 @@
     GURL url;
   };
 
-  SplashScreen(const WebModule::OnRenderTreeProducedCallback&
+  SplashScreen(base::ApplicationState initial_application_state,
+               const WebModule::OnRenderTreeProducedCallback&
                    render_tree_produced_callback,
                network::NetworkModule* network_module,
                const math::Size& window_dimensions,
@@ -45,8 +47,16 @@
                float layout_refresh_rate, const Options& options = Options());
   ~SplashScreen();
 
-  void Suspend();
-  void Resume(render_tree::ResourceProvider* resource_provider);
+  // LifecycleObserver implementation.
+  void Start(render_tree::ResourceProvider* resource_provider) OVERRIDE {
+    web_module_->Start(resource_provider);
+  }
+  void Pause() OVERRIDE { web_module_->Pause(); }
+  void Unpause() OVERRIDE { web_module_->Unpause(); }
+  void Suspend() OVERRIDE { web_module_->Suspend(); }
+  void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE {
+    web_module_->Resume(resource_provider);
+  }
 
   // Block the caller until the splash screen is ready to be rendered.
   void WaitUntilReady();
diff --git a/src/cobalt/browser/suspend_fuzzer.cc b/src/cobalt/browser/suspend_fuzzer.cc
new file mode 100644
index 0000000..b94158b
--- /dev/null
+++ b/src/cobalt/browser/suspend_fuzzer.cc
@@ -0,0 +1,70 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/suspend_fuzzer.h"
+
+namespace cobalt {
+namespace browser {
+
+namespace {
+
+// How long to wait before starting the suspend fuzzer, if it is enabled.  It
+// is important that this value is large enough to let the hosted application
+// get into its initialized state, since otherwise, we would likely not be
+// fuzzing the main state of the application.
+const base::TimeDelta kBeginTimeout = base::TimeDelta::FromSeconds(30);
+
+// How long to wait in between suspend fuzzer suspends and resumes, if it is
+// enabled.
+const base::TimeDelta kInterval = base::TimeDelta::FromSeconds(10);
+
+}  // namespace
+
+SuspendFuzzer::SuspendFuzzer()
+    : thread_("suspend_fuzzer"), step_type_(kShouldRequestSuspend) {
+  thread_.Start();
+  thread_.message_loop()->PostDelayedTask(
+      FROM_HERE, base::Bind(&SuspendFuzzer::DoStep, base::Unretained(this)),
+      kBeginTimeout);
+}
+
+SuspendFuzzer::~SuspendFuzzer() { thread_.Stop(); }
+
+void SuspendFuzzer::DoStep() {
+  DCHECK(MessageLoop::current() == thread_.message_loop());
+#if SB_API_VERSION < 4
+  NOTREACHED() << "Cannot run suspend_fuzzer on SB_API_VERSION < 4.";
+#endif
+  if (step_type_ == kShouldRequestSuspend) {
+    SB_DLOG(INFO) << "suspend_fuzzer: Requesting suspend.";
+#if SB_API_VERSION >= 4
+    SbSystemRequestSuspend();
+#endif
+    step_type_ = kShouldRequestUnpause;
+  } else if (step_type_ == kShouldRequestUnpause) {
+    SB_DLOG(INFO) << "suspend_fuzzer: Requesting unpause.";
+#if SB_API_VERSION >= 4
+    SbSystemRequestUnpause();
+#endif
+    step_type_ = kShouldRequestSuspend;
+  } else {
+    NOTREACHED();
+  }
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, base::Bind(&SuspendFuzzer::DoStep, base::Unretained(this)),
+      kInterval);
+}
+
+}  // namespace browser
+}  // namespace cobalt
diff --git a/src/cobalt/browser/suspend_fuzzer.h b/src/cobalt/browser/suspend_fuzzer.h
new file mode 100644
index 0000000..4aa97cc
--- /dev/null
+++ b/src/cobalt/browser/suspend_fuzzer.h
@@ -0,0 +1,48 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_SUSPEND_FUZZER_H_
+#define COBALT_BROWSER_SUSPEND_FUZZER_H_
+
+#include "base/bind.h"
+#include "base/threading/thread.h"
+
+namespace cobalt {
+namespace browser {
+
+// Repeatedly switch off between calling |SbSystemRequestSuspend| and
+// |SbSystemRequestUnpause|, or just no-op if on an SB_API_VERSION < 4.
+class SuspendFuzzer {
+ public:
+  SuspendFuzzer();
+  ~SuspendFuzzer();
+
+ private:
+  void DoStep();
+
+  // Suspending the application could possibly freeze our message loop or our
+  // web modules' message loops.  We thus create a separate thread to run the
+  // suspend fuzzer on.
+  base::Thread thread_;
+
+  enum StepType {
+    kShouldRequestSuspend,
+    kShouldRequestUnpause,
+  } step_type_;
+};
+
+}  // namespace browser
+}  // namespace cobalt
+
+#endif  // COBALT_BROWSER_SUSPEND_FUZZER_H_
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 9e11ce3..0232dff 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -82,6 +82,11 @@
 // Decode all images using StubImageDecoder.
 const char kStubImageDecoder[] = "stub_image_decoder";
 
+// If this flag is set, alternating calls to |SbSystemRequestSuspend| and
+// |SbSystemRequestUnpause| will be made periodically. Requires
+// SB_API_VERSION >= 4, and will otherwise just no-op.
+const char kSuspendFuzzer[] = "suspend_fuzzer";
+
 // If this is set, then a trace (see base/debug/trace_eventh.h) is started on
 // Cobalt startup.  A value must also be specified for this switch, which is
 // the duration in seconds of how long the trace will be done for before ending
@@ -112,6 +117,15 @@
 
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
+// If toggled, framerate statistics will be printed to stdout after each
+// animation completes, or after a maximum number of frames has been collected.
+const char kFPSPrint[] = "fps_stdout";
+
+// If toggled, framerate statistics will be displayed in an on-screen overlay
+// and updated after each animation completes, or after a maximum number of
+// frames has been collected.
+const char kFPSOverlay[] = "fps_overlay";
+
 // Disables the hard-coded navigation whitelist without disabling any other
 // security checks. This is enabled in Gold builds.
 const char kDisableNavigationWhitelist[] = "disable_navigation_whitelist";
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 7eba479..6f03782 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -39,15 +39,18 @@
 extern const char kRemoteDebuggingPort[];
 extern const char kShutdownAfter[];
 extern const char kStubImageDecoder[];
+extern const char kSuspendFuzzer[];
 extern const char kTimedTrace[];
 extern const char kUseTTS[];
 extern const char kVideoContainerSizeOverride[];
 extern const char kVideoDecoderStub[];
-extern const char kWebDriverPort[];
 extern const char kWebDriverListenIp[];
+extern const char kWebDriverPort[];
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 extern const char kDisableNavigationWhitelist[];
+extern const char kFPSPrint[];
+extern const char kFPSOverlay[];
 extern const char kImageCacheSizeInBytes[];
 extern const char kInitialURL[];
 extern const char kRemoteTypefaceCacheSizeInBytes[];
diff --git a/src/cobalt/browser/testdata/deviceorientation-demo/deviceorientation-demo.html b/src/cobalt/browser/testdata/deviceorientation-demo/deviceorientation-demo.html
new file mode 100644
index 0000000..2867e52
--- /dev/null
+++ b/src/cobalt/browser/testdata/deviceorientation-demo/deviceorientation-demo.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    body {
+      background-color: rgb(255, 255, 255);
+      font-size: 100px;
+    }
+</style>
+</head>
+<body>
+  <div id="alpha"></div>
+  <div id="beta"></div>
+  <div id="gamma"></div>
+  <div id="frequency"></div>
+  <div id="count"></div>
+
+  <script>
+    var lastShowAngles = NaN;
+    var lastShowHz = NaN;
+    var fireCount = 0;
+
+    window.addEventListener('deviceorientation', function(event) {
+      var now = Date.now();
+
+      if (isNaN(lastShowAngles) || now - lastShowAngles >= 200) {
+        ['alpha', 'beta', 'gamma'].forEach(function(axis) {
+          document.getElementById(axis).innerHTML =
+              axis + ' = ' + event[axis];
+        });
+        lastShowAngles = now;
+      }
+
+      var elapsedSinceShowHz = now - lastShowHz;
+      if (isNaN(lastShowHz) || elapsedSinceShowHz >= 1000) {
+        document.getElementById('frequency').innerHTML =
+          (fireCount * 1000 / elapsedSinceShowHz) + ' Hz';
+
+        fireCount = 0;
+        lastShowHz = now;
+      }
+
+      fireCount++;
+    }, true);
+  </script>
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/page-visibility-demo/page-visibility-demo.html b/src/cobalt/browser/testdata/page-visibility-demo/page-visibility-demo.html
new file mode 100644
index 0000000..88afa97
--- /dev/null
+++ b/src/cobalt/browser/testdata/page-visibility-demo/page-visibility-demo.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="text/javascript">
+    console.log("document.visibilityState: " + document.visibilityState);
+
+    window.onblur = function() {
+      console.log("window.onblur");
+    };
+
+    window.onfocus = function() {
+      console.log("window.onfocus");
+    };
+
+    document.onblur = function() {
+      console.log("document.onblur?!");
+    };
+
+    document.onfocus = function() {
+      console.log("document.onfocus?!");
+    };
+
+    document.onvisibilitychange = function() {
+      console.log("document.onvisibilitychange: " + document.visibilityState);
+    };
+  </script>
+</head>
+<body style="background-color:#ccc"></body>
+</html>
diff --git a/src/cobalt/browser/trace_manager.cc b/src/cobalt/browser/trace_manager.cc
index 6a31774..5a5769f 100644
--- a/src/cobalt/browser/trace_manager.cc
+++ b/src/cobalt/browser/trace_manager.cc
@@ -32,13 +32,14 @@
     "If a trace is currently running, stops it and saves the result; "
     "otherwise starts a new trace.";
 
-// Name of the command to start / stop tracing after key event.
-const char kKeyTraceCommand[] = "key_trace";
+// Name of the command to start / stop tracing after input event.
+const char kInputTraceCommand[] = "input_trace";
 
-// Help strings for the key trace command.
-const char kKeyTraceCommandShortHelp[] = "Starts/stops tracing after key event";
-const char kKeyTraceCommandLongHelp[] =
-    "Switches the flag of whether we start a new tracing after each key "
+// Help strings for the input trace command.
+const char kInputTraceCommandShortHelp[] =
+    "Starts/stops tracing after input event";
+const char kInputTraceCommandLongHelp[] =
+    "Switches the flag of whether we start a new tracing after each input "
     "event.";
 
 }  // namespace
@@ -53,17 +54,18 @@
           kTraceCommandChannel,
           base::Bind(&TraceManager::OnTraceMessage, base::Unretained(this)),
           kTraceCommandShortHelp, kTraceCommandLongHelp)),
-      ALLOW_THIS_IN_INITIALIZER_LIST(key_trace_command_handler_(
-          kKeyTraceCommand,
-          base::Bind(&TraceManager::OnKeyTraceMessage, base::Unretained(this)),
-          kKeyTraceCommandShortHelp, kKeyTraceCommandLongHelp)),
-      key_tracing_enabled_(false) {}
+      ALLOW_THIS_IN_INITIALIZER_LIST(input_trace_command_handler_(
+          kInputTraceCommand,
+          base::Bind(&TraceManager::OnInputTraceMessage,
+                     base::Unretained(this)),
+          kInputTraceCommandShortHelp, kInputTraceCommandLongHelp)),
+      input_tracing_enabled_(false) {}
 
-void TraceManager::OnKeyEventProduced() {
+void TraceManager::OnInputEventProduced() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (key_tracing_enabled_ && !IsTracing()) {
+  if (input_tracing_enabled_ && !IsTracing()) {
     static const int kTraceTimeInMilliSeconds = 500;
-    LOG(INFO) << "Key event produced, start tracing for "
+    LOG(INFO) << "Input event produced, start tracing for "
               << kTraceTimeInMilliSeconds << "ms...";
     start_time_to_event_map_.clear();
     trace_event::TraceWithEventParserForDuration(
@@ -102,19 +104,19 @@
   }
 }
 
-void TraceManager::OnKeyTraceMessage(const std::string& message) {
+void TraceManager::OnInputTraceMessage(const std::string& message) {
   if (MessageLoop::current() != self_message_loop_) {
     self_message_loop_->PostTask(FROM_HERE,
-                                 base::Bind(&TraceManager::OnKeyTraceMessage,
+                                 base::Bind(&TraceManager::OnInputTraceMessage,
                                             base::Unretained(this), message));
     return;
   }
 
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  key_tracing_enabled_ = !key_tracing_enabled_;
-  LOG(INFO) << "Key tracing is now "
-            << (key_tracing_enabled_ ? "enabled" : "disabled") << ".";
+  input_tracing_enabled_ = !input_tracing_enabled_;
+  LOG(INFO) << "Input tracing is now "
+            << (input_tracing_enabled_ ? "enabled" : "disabled") << ".";
 }
 
 void TraceManager::OnReceiveTraceEvent(
@@ -123,6 +125,8 @@
   // TODO: Generalize the following logic. Currently the criteria for
   // interesting events are hardcoded.
   if (event->name() == "WebModule::InjectKeyboardEvent()" ||
+      event->name() == "WebModule::InjectPointerEvent()" ||
+      event->name() == "WebModule::InjectWheelEvent()" ||
       event->name() == "Layout") {
     double event_duration = event->in_scope_duration()->InMillisecondsF();
 
@@ -148,7 +152,7 @@
               << event->in_scope_duration()->InMillisecondsF() << "ms";
   }
   start_time_to_event_map_.clear();
-  LOG(INFO) << "Key trace finished.";
+  LOG(INFO) << "Input trace finished.";
 }
 
 }  // namespace browser
diff --git a/src/cobalt/browser/trace_manager.h b/src/cobalt/browser/trace_manager.h
index ffa2c8f..180c2f5 100644
--- a/src/cobalt/browser/trace_manager.h
+++ b/src/cobalt/browser/trace_manager.h
@@ -34,14 +34,14 @@
 
   TraceManager();
 
-  // Called by browser module when a key event is produced.
-  void OnKeyEventProduced();
+  // Called by browser module when an input event is produced.
+  void OnInputEventProduced();
 
   // Message handler callback for trace message from debug console.
   void OnTraceMessage(const std::string& message);
 
-  // Message handler callback for key trace message from debug console.
-  void OnKeyTraceMessage(const std::string& message);
+  // Message handler callback for input trace message from debug console.
+  void OnInputTraceMessage(const std::string& message);
 
   // Called when receiving and finishing receiving parsed trace events.
   void OnReceiveTraceEvent(
@@ -58,10 +58,10 @@
 
   // Command handler object for trace command from the debug console.
   base::ConsoleCommandManager::CommandHandler trace_command_handler_;
-  // Command handler object for key trace command from the debug console.
-  base::ConsoleCommandManager::CommandHandler key_trace_command_handler_;
-  // Whether key tracing is enabled.
-  bool key_tracing_enabled_;
+  // Command handler object for input trace command from the debug console.
+  base::ConsoleCommandManager::CommandHandler input_trace_command_handler_;
+  // Whether input tracing is enabled.
+  bool input_tracing_enabled_;
 
   base::ThreadChecker thread_checker_;
   // This object can be set to start a trace if a hotkey (like F3) is pressed.
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 380b091..059c85c 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -23,8 +23,7 @@
 #include "base/message_loop_proxy.h"
 #include "base/optional.h"
 #include "base/stringprintf.h"
-#include "cobalt/base/c_val.h"
-#include "cobalt/base/poller.h"
+#include "cobalt/base/startup_timer.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/browser/stack_size_constants.h"
 #include "cobalt/browser/switches.h"
@@ -34,32 +33,33 @@
 #include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/element.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/dom/global_stats.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
+#include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/storage.h"
+#include "cobalt/dom/ui_event.h"
 #include "cobalt/dom/url.h"
+#include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/h5vcc/h5vcc.h"
+#include "cobalt/layout/topmost_event_target.h"
 #include "cobalt/loader/image/animated_image_tracker.h"
 #include "cobalt/media_session/media_session_client.h"
+#include "cobalt/page_visibility/visibility_state.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/storage/storage_manager.h"
 #include "cobalt/system_window/system_window.h"
 #include "starboard/accessibility.h"
 #include "starboard/log.h"
-#include "starboard/once.h"
 
 namespace cobalt {
 namespace browser {
 
 namespace {
 
-#if defined(COBALT_BUILD_TYPE_GOLD)
-const int kPollerPeriodMs = 2000;
-#else   // #if defined(COBALT_BUILD_TYPE_GOLD)
-const int kPollerPeriodMs = 20;
-#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.
 const int kDOMMaxElementDepth = 32;
@@ -85,45 +85,6 @@
     "To wipe the box tree and turn partial layout off.";
 #endif  // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
 
-class JSEngineStats {
- public:
-  JSEngineStats()
-      : js_reserved_memory_("Memory.JS", 0,
-                            "The total memory that is reserved by the engine, "
-                            "including the part that is actually occupied by "
-                            "JS objects, and the part that is not yet.") {}
-
-  static JSEngineStats* GetInstance() {
-    return Singleton<JSEngineStats,
-                     StaticMemorySingletonTraits<JSEngineStats> >::get();
-  }
-
-  void SetReservedMemory(size_t js_reserved_memory) {
-    js_reserved_memory_ = static_cast<uint64>(js_reserved_memory);
-  }
-
- private:
-  // The total memory that is reserved by the engine, including the part that is
-  // actually occupied by JS objects, and the part that is not yet.
-  base::CVal<base::cval::SizeInBytes, base::CValPublic> js_reserved_memory_;
-};
-
-// StartupTimer is designed to measure time since the startup of the app.
-// It is loader initialized to have the most accurate start time as possible.
-class StartupTimer {
- public:
-  static StartupTimer* Instance();
-  base::TimeDelta TimeSinceStartup() const {
-    return base::TimeTicks::Now() - start_time_;
-  }
-
- private:
-  StartupTimer() : start_time_(base::TimeTicks::Now()) {}
-  base::TimeTicks start_time_;
-};
-
-SB_ONCE_INITIALIZE_FUNCTION(StartupTimer, StartupTimer::Instance);
-StartupTimer* s_on_startup_init_dont_use = StartupTimer::Instance();
 }  // namespace
 
 // Private WebModule implementation. Each WebModule owns a single instance of
@@ -146,14 +107,29 @@
   // 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);
+                           base::Token type,
+                           const dom::KeyboardEventInit& event);
+
+  // Called to inject a pointer 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 InjectPointerEvent(scoped_refptr<dom::Element> element, base::Token type,
+                          const dom::PointerEventInit& event);
+
+  // Called to inject a wheel 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 InjectWheelEvent(scoped_refptr<dom::Element> element, base::Token type,
+                        const dom::WheelEventInit& event);
 
   // Called to execute JavaScript in this WebModule. Sets the |result|
   // output parameter and signals |got_result|.
   void ExecuteJavascript(const std::string& script_utf8,
                          const base::SourceLocation& script_location,
                          base::WaitableEvent* got_result, std::string* result,
-                         bool *out_succeeded);
+                         bool* out_succeeded);
 
   // Clears disables timer related objects
   // so that the message loop can easily exit
@@ -175,11 +151,21 @@
   void CreateDebugServerIfNull();
 #endif  // ENABLE_DEBUG_CONSOLE
 
+  // Sets the application state, asserts preconditions to transition to that
+  // state, and dispatches any precipitate web events.
+  void SetApplicationState(base::ApplicationState state);
+
   // 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();
+
+  // See LifecycleObserver. These functions do not implement the interface, but
+  // have the same basic function.
+  void Start(render_tree::ResourceProvider* resource_provider);
+  void Pause();
+  void Unpause();
   void Resume(render_tree::ResourceProvider* resource_provider);
 
   void ReportScriptError(const base::SourceLocation& source_location,
@@ -218,12 +204,12 @@
     error_callback_.Run(window_->location()->url(), error);
   }
 
-  void UpdateJavaScriptEngineStats() {
-    if (javascript_engine_) {
-      JSEngineStats::GetInstance()->SetReservedMemory(
-          javascript_engine_->UpdateMemoryStatsAndReturnReserved());
-    }
-  }
+  // Inject the DOM event object into the window or the element.
+  void InjectInputEvent(scoped_refptr<dom::Element> element,
+                        const scoped_refptr<dom::Event>& event);
+
+  // Handle queued pointer events. Called by LayoutManager on_layout callback.
+  void HandlePointerEvents();
 
   // Thread checker ensures all calls to the WebModule are made from the same
   // thread that it is created in.
@@ -281,9 +267,6 @@
   // JavaScript engine for the browser.
   scoped_ptr<script::JavaScriptEngine> javascript_engine_;
 
-  // Poller that updates javascript engine stats.
-  scoped_ptr<base::PollerWithThread> javascript_engine_poller_;
-
   // JavaScript Global Object for the browser. There should be one per window,
   // but since there is only one window, we can have one per browser.
   scoped_refptr<script::GlobalEnvironment> global_environment_;
@@ -342,6 +325,8 @@
 #endif  // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
 
   scoped_ptr<media_session::MediaSessionClient> media_session_client_;
+
+  layout::TopmostEventTarget topmost_event_target_;
 };
 
 class WebModule::Impl::DocumentLoadedObserver : public dom::DocumentObserver {
@@ -364,9 +349,9 @@
 };
 
 WebModule::Impl::Impl(const ConstructionData& data)
-    : name_(data.options.name), is_running_(false) {
-  resource_provider_ = data.resource_provider;
-
+    : name_(data.options.name),
+      is_running_(false),
+      resource_provider_(data.resource_provider) {
   // Currently we rely on a platform to explicitly specify that it supports
   // the map-to-mesh filter via the ENABLE_MAP_TO_MESH define (and the
   // 'enable_map_to_mesh' gyp variable).  When we have better support for
@@ -454,11 +439,6 @@
   javascript_engine_->RegisterErrorHandler(error_handler);
 #endif
 
-  javascript_engine_poller_.reset(new base::PollerWithThread(
-      base::Bind(&WebModule::Impl::UpdateJavaScriptEngineStats,
-                 base::Unretained(this)),
-      base::TimeDelta::FromMilliseconds(kPollerPeriodMs)));
-
   global_environment_ = javascript_engine_->CreateGlobalEnvironment();
   DCHECK(global_environment_);
 
@@ -476,8 +456,9 @@
 
   window_ = new dom::Window(
       data.window_dimensions.width(), data.window_dimensions.height(),
-      css_parser_.get(), dom_parser_.get(), fetcher_factory_.get(),
-      &resource_provider_, animated_image_tracker_.get(), image_cache_.get(),
+      data.initial_application_state, css_parser_.get(), dom_parser_.get(),
+      fetcher_factory_.get(), &resource_provider_,
+      animated_image_tracker_.get(), image_cache_.get(),
       reduced_image_cache_capacity_manager_.get(), remote_typeface_cache_.get(),
       mesh_cache_.get(), local_storage_database_.get(), data.media_module,
       data.media_module, execution_state_.get(), script_runner_.get(),
@@ -519,15 +500,15 @@
   DCHECK(!error_callback_.is_null());
 
   layout_manager_.reset(new layout::LayoutManager(
-      name_, window_.get(), base::Bind(&WebModule::Impl::OnRenderTreeProduced,
-                                       base::Unretained(this)),
+      name_, window_.get(),
+      base::Bind(&WebModule::Impl::OnRenderTreeProduced,
+                 base::Unretained(this)),
+      base::Bind(&WebModule::Impl::HandlePointerEvents, base::Unretained(this)),
       data.options.layout_trigger, data.dom_max_element_depth,
       data.layout_refresh_rate, data.network_module->preferred_language(),
       web_module_stat_tracker_->layout_stat_tracker()));
   DCHECK(layout_manager_);
 
-  resource_provider_ = data.resource_provider;
-
 #if defined(ENABLE_DEBUG_CONSOLE)
   debug_overlay_.reset(
       new debug::RenderOverlay(data.render_tree_produced_callback));
@@ -585,7 +566,6 @@
   script_runner_.reset();
   execution_state_.reset();
   global_environment_ = NULL;
-  javascript_engine_poller_.reset();
   javascript_engine_.reset();
   web_module_stat_tracker_.reset();
   local_storage_database_.reset();
@@ -598,24 +578,18 @@
   css_parser_.reset();
 }
 
-void WebModule::Impl::InjectKeyboardEvent(
-    scoped_refptr<dom::Element> element,
-    const dom::KeyboardEvent::Data& event) {
+void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
+                                       const scoped_refptr<dom::Event>& event) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(is_running_);
   DCHECK(window_);
 
-  // Construct the DOM object from the keyboard event builder and inject it
-  // into the window.
-  scoped_refptr<dom::KeyboardEvent> keyboard_event(
-      new dom::KeyboardEvent(event));
-
-  web_module_stat_tracker_->OnStartInjectEvent(keyboard_event);
+  web_module_stat_tracker_->OnStartInjectEvent(event);
 
   if (element) {
-    element->DispatchEvent(keyboard_event);
+    element->DispatchEvent(event);
   } else {
-    window_->InjectEvent(keyboard_event);
+    window_->InjectEvent(event);
   }
 
   web_module_stat_tracker_->OnEndInjectEvent(
@@ -623,14 +597,46 @@
       layout_manager_->IsNewRenderTreePending());
 }
 
+void WebModule::Impl::InjectKeyboardEvent(scoped_refptr<dom::Element> element,
+                                          base::Token type,
+                                          const dom::KeyboardEventInit& event) {
+  scoped_refptr<dom::KeyboardEvent> keyboard_event(
+      new dom::KeyboardEvent(type, window_, event));
+  InjectInputEvent(element, keyboard_event);
+}
+
+void WebModule::Impl::InjectPointerEvent(scoped_refptr<dom::Element> element,
+                                         base::Token type,
+                                         const dom::PointerEventInit& event) {
+  scoped_refptr<dom::PointerEvent> pointer_event(
+      new dom::PointerEvent(type, window_, event));
+  InjectInputEvent(element, pointer_event);
+}
+
+void WebModule::Impl::InjectWheelEvent(scoped_refptr<dom::Element> element,
+                                       base::Token type,
+                                       const dom::WheelEventInit& event) {
+  scoped_refptr<dom::WheelEvent> wheel_event(
+      new dom::WheelEvent(type, window_, event));
+  InjectInputEvent(element, wheel_event);
+}
+
 void WebModule::Impl::ExecuteJavascript(
     const std::string& script_utf8, const base::SourceLocation& script_location,
     base::WaitableEvent* got_result, std::string* result, bool* out_succeeded) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(is_running_);
   DCHECK(script_runner_);
+
+  // JavaScript is being run. Track it in the global stats.
+  dom::GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   *result = script_runner_->Execute(script_utf8, script_location,
       out_succeeded);
+
+  // JavaScript is done running. Stop tracking it in the global stats.
+  dom::GlobalStats::GetInstance()->StopJavaScriptEvent();
+
   got_result->Signal();
 }
 
@@ -706,6 +712,8 @@
       window_id, window_weak_,
       base::Bind(&WebModule::Impl::global_environment, base::Unretained(this)),
       base::Bind(&WebModule::Impl::InjectKeyboardEvent, base::Unretained(this)),
+      base::Bind(&WebModule::Impl::InjectPointerEvent, base::Unretained(this)),
+      base::Bind(&WebModule::Impl::InjectWheelEvent, base::Unretained(this)),
       base::MessageLoopProxy::current()));
 }
 #endif  // defined(ENABLE_WEBDRIVER)
@@ -742,9 +750,32 @@
   }
 }
 
+void WebModule::Impl::SetApplicationState(base::ApplicationState state) {
+  window_->SetApplicationState(state);
+}
+
+void WebModule::Impl::Start(render_tree::ResourceProvider* resource_provider) {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Start()");
+  SetApplicationState(base::kApplicationStateStarted);
+  // TODO: Initialize resource provider here rather than constructor.
+  DCHECK(resource_provider == resource_provider_);
+}
+
+void WebModule::Impl::Pause() {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Pause()");
+  SetApplicationState(base::kApplicationStatePaused);
+}
+
+void WebModule::Impl::Unpause() {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Unpause()");
+  SetApplicationState(base::kApplicationStateStarted);
+}
+
 void WebModule::Impl::SuspendLoaders() {
   TRACE_EVENT0("cobalt::browser", "WebModule::Impl::SuspendLoaders()");
 
+  SetApplicationState(base::kApplicationStateSuspended);
+
   // Purge the resource caches before running any suspend logic. This will force
   // any pending callbacks that the caches are batching to run.
   PurgeResourceCaches();
@@ -801,6 +832,8 @@
   // invalidated with the call to Suspend(), so the layout manager's first task
   // will be to perform a full re-layout.
   layout_manager_->Resume();
+
+  SetApplicationState(base::kApplicationStatePaused);
 }
 
 void WebModule::Impl::ReportScriptError(
@@ -810,7 +843,7 @@
       FilePath(source_location.file_path).BaseName().value();
 
   std::stringstream ss;
-  base::TimeDelta dt = StartupTimer::Instance()->TimeSinceStartup();
+  base::TimeDelta dt = base::StartupTimer::TimeElapsed();
 
   // Create the error output.
   // Example:
@@ -857,7 +890,7 @@
       video_playback_rate_multiplier(1.f) {}
 
 WebModule::WebModule(
-    const GURL& initial_url,
+    const GURL& initial_url, base::ApplicationState initial_application_state,
     const OnRenderTreeProducedCallback& render_tree_produced_callback,
     const OnErrorCallback& error_callback,
     const base::Closure& window_close_callback,
@@ -869,10 +902,10 @@
     const Options& options)
     : thread_(options.name.c_str()) {
   ConstructionData construction_data(
-      initial_url, render_tree_produced_callback, error_callback,
-      window_close_callback, window_minimize_callback, media_module,
-      network_module, window_dimensions, resource_provider, kDOMMaxElementDepth,
-      system_window, layout_refresh_rate, options);
+      initial_url, initial_application_state, render_tree_produced_callback,
+      error_callback, window_close_callback, window_minimize_callback,
+      media_module, network_module, window_dimensions, resource_provider,
+      kDOMMaxElementDepth, system_window, layout_refresh_rate, options);
 
   // Start the dedicated thread and create the internal implementation
   // object on that thread.
@@ -881,21 +914,15 @@
       options.thread_priority));
   DCHECK(message_loop());
 
-  message_loop()->PostTask(
-      FROM_HERE, base::Bind(&WebModule::Initialize, base::Unretained(this),
-                            construction_data));
-
   // Block this thread until the initialization is complete.
   // TODO: Figure out why this is necessary.
   // It would be preferable to return immediately and let the WebModule
   // continue in its own time, but without this wait there is a race condition
   // such that inline scripts may be executed before the document elements they
   // operate on are present.
-  base::WaitableEvent is_initialized(true, false);
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&base::WaitableEvent::Signal,
-                                      base::Unretained(&is_initialized)));
-  is_initialized.Wait();
+  message_loop()->PostBlockingTask(
+      FROM_HERE, base::Bind(&WebModule::Initialize, base::Unretained(this),
+                            construction_data));
 
 #if defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
   CommandLine* command_line = CommandLine::ForCurrentProcess();
@@ -926,16 +953,11 @@
   // destroy the internal components before the message loop is set to NULL.
   // No posted tasks will be executed once the thread is stopped.
   DestructionObserver destruction_observer(this);
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&MessageLoop::AddDestructionObserver,
-                                      base::Unretained(message_loop()),
-                                      base::Unretained(&destruction_observer)));
-
-  base::WaitableEvent did_register_shutdown_observer(true, false);
-  message_loop()->PostTask(
-      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
-                            base::Unretained(&did_register_shutdown_observer)));
-  did_register_shutdown_observer.Wait();
+  message_loop()->PostBlockingTask(
+      FROM_HERE,
+      base::Bind(&MessageLoop::AddDestructionObserver,
+                 base::Unretained(message_loop()),
+                 base::Unretained(&destruction_observer)));
 
   // This will cancel the timers for tasks, which help the thread exit
   ClearAllIntervalsAndTimeouts();
@@ -949,15 +971,40 @@
   impl_.reset(new Impl(data));
 }
 
-void WebModule::InjectKeyboardEvent(const dom::KeyboardEvent::Data& event) {
+void WebModule::InjectKeyboardEvent(base::Token type,
+                                    const dom::KeyboardEventInit& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type",
-               event.type);
+               type.c_str());
   DCHECK(message_loop());
   DCHECK(impl_);
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&WebModule::Impl::InjectKeyboardEvent,
-                                      base::Unretained(impl_.get()),
-                                      scoped_refptr<dom::Element>(), event));
+  message_loop()->PostTask(
+      FROM_HERE, base::Bind(&WebModule::Impl::InjectKeyboardEvent,
+                            base::Unretained(impl_.get()),
+                            scoped_refptr<dom::Element>(), type, event));
+}
+
+void WebModule::InjectPointerEvent(base::Token type,
+                                   const dom::PointerEventInit& event) {
+  TRACE_EVENT1("cobalt::browser", "WebModule::InjectPointerEvent()", "type",
+               type.c_str());
+  DCHECK(message_loop());
+  DCHECK(impl_);
+  message_loop()->PostTask(
+      FROM_HERE, base::Bind(&WebModule::Impl::InjectPointerEvent,
+                            base::Unretained(impl_.get()),
+                            scoped_refptr<dom::Element>(), type, event));
+}
+
+void WebModule::InjectWheelEvent(base::Token type,
+                                 const dom::WheelEventInit& event) {
+  TRACE_EVENT1("cobalt::browser", "WebModule::InjectWheelEvent()", "type",
+               type.c_str());
+  DCHECK(message_loop());
+  DCHECK(impl_);
+  message_loop()->PostTask(
+      FROM_HERE, base::Bind(&WebModule::Impl::InjectWheelEvent,
+                            base::Unretained(impl_.get()),
+                            scoped_refptr<dom::Element>(), type, event));
 }
 
 std::string WebModule::ExecuteJavascript(
@@ -1011,16 +1058,11 @@
   DCHECK(impl_);
 
   scoped_ptr<webdriver::WindowDriver> window_driver;
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&WebModule::Impl::CreateWindowDriver,
-                                      base::Unretained(impl_.get()), window_id,
-                                      base::Unretained(&window_driver)));
-
-  base::WaitableEvent window_driver_created(true, false);
-  message_loop()->PostTask(
-      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
-                            base::Unretained(&window_driver_created)));
-  window_driver_created.Wait();
+  message_loop()->PostBlockingTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::CreateWindowDriver,
+                 base::Unretained(impl_.get()), window_id,
+                 base::Unretained(&window_driver)));
 
   return window_driver.Pass();
 }
@@ -1032,65 +1074,93 @@
   DCHECK(message_loop());
   DCHECK(impl_);
 
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&WebModule::Impl::CreateDebugServerIfNull,
-                                      base::Unretained(impl_.get())));
+  message_loop()->PostBlockingTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::CreateDebugServerIfNull,
+                 base::Unretained(impl_.get())));
 
-  base::WaitableEvent debug_server_created(true, false);
-  message_loop()->PostTask(FROM_HERE,
-                           base::Bind(&base::WaitableEvent::Signal,
-                                      base::Unretained(&debug_server_created)));
-
-  debug_server_created.Wait();
   return impl_->debug_server();
 }
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
-void WebModule::Suspend() {
-  TRACE_EVENT0("cobalt::browser", "WebModule::Suspend()");
-
-  // Suspend() must only be called by a thread external from the WebModule
-  // thread.
+void WebModule::Start(render_tree::ResourceProvider* resource_provider) {
+  // Must only be called by a thread external from the WebModule thread.
   DCHECK_NE(MessageLoop::current(), message_loop());
 
-  base::WaitableEvent task_finished(false /* automatic reset */,
-                                    false /* initially unsignaled */);
+  // We must block here so that the call doesn't return until the web
+  // application has had a chance to process the whole event.
+  message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::Start, base::Unretained(impl_.get()),
+                 base::Unretained(resource_provider)));
+}
 
-  // 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())));
+void WebModule::Pause() {
+  // Must only be called by a thread external from the WebModule thread.
+  DCHECK_NE(MessageLoop::current(), message_loop());
 
-  // 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();
+  // We must block here so that the call doesn't return until the web
+  // application has had a chance to process the whole event.
+  message_loop()->PostBlockingTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::Pause, base::Unretained(impl_.get())));
+}
 
-  // 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())));
+void WebModule::Unpause() {
+  // Must only be called by a thread external from the WebModule thread.
+  DCHECK_NE(MessageLoop::current(), message_loop());
 
-  // 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();
+  // We must block here so that the call doesn't return until the web
+  // application has had a chance to process the whole event.
+  message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&WebModule::Impl::Unpause, base::Unretained(impl_.get())));
+}
+
+void WebModule::Suspend() {
+  // Must only be called by a thread external from the WebModule thread.
+  DCHECK_NE(MessageLoop::current(), message_loop());
+
+  // We must block here so that we don't queue the finish until after
+  // SuspendLoaders has run to completion, and therefore has already queued any
+  // precipitate tasks.
+  message_loop()->PostBlockingTask(FROM_HERE,
+                                   base::Bind(&WebModule::Impl::SuspendLoaders,
+                                              base::Unretained(impl_.get())));
+
+  // We must block here so that the call doesn't return until the web
+  // application has had a chance to process the whole event.
+  message_loop()->PostBlockingTask(FROM_HERE,
+                   base::Bind(&WebModule::Impl::FinishSuspend,
+                              base::Unretained(impl_.get())));
 }
 
 void WebModule::Resume(render_tree::ResourceProvider* resource_provider) {
+  // Must only be called by a thread external from the WebModule thread.
+  DCHECK_NE(MessageLoop::current(), message_loop());
+
+  // We must block here so that the call doesn't return until the web
+  // application has had a chance to process the whole event.
   message_loop()->PostTask(
       FROM_HERE, base::Bind(&WebModule::Impl::Resume,
                             base::Unretained(impl_.get()), resource_provider));
 }
 
+void WebModule::Impl::HandlePointerEvents() {
+  TRACE_EVENT0("cobalt::browser", "WebModule::Impl::HandlePointerEvents");
+  const scoped_refptr<dom::Document>& document = window_->document();
+  scoped_refptr<dom::Event> event;
+  do {
+    event = document->GetNextQueuedPointerEvent();
+    if (event) {
+      SB_DCHECK(
+          window_ ==
+          base::polymorphic_downcast<const dom::UIEvent* const>(event.get())
+              ->view());
+      topmost_event_target_.MaybeSendPointerEvents(event, window_);
+    }
+  } while (event && !layout_manager_->IsNewRenderTreePending());
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index d590e3e..0cc79ce 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -29,6 +29,7 @@
 #include "cobalt/base/address_sanitizer.h"
 #include "cobalt/base/console_commands.h"
 #include "cobalt/base/source_location.h"
+#include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/css_parser/parser.h"
 #if defined(ENABLE_DEBUG_CONSOLE)
 #include "cobalt/debug/debug_server.h"
@@ -37,9 +38,11 @@
 #include "cobalt/dom/blob.h"
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/dom_settings.h"
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/layout/layout_manager.h"
@@ -74,7 +77,7 @@
 // This necessarily implies that details contained within WebModule, such as the
 // DOM, are intentionally kept private, since these structures expect to be
 // accessed from only one thread.
-class WebModule {
+class WebModule : public LifecycleObserver {
  public:
   struct Options {
     typedef base::Callback<scoped_refptr<script::Wrappable>(
@@ -181,6 +184,7 @@
   typedef base::Callback<void(const GURL&, const std::string&)> OnErrorCallback;
 
   WebModule(const GURL& initial_url,
+            base::ApplicationState initial_application_state,
             const OnRenderTreeProducedCallback& render_tree_produced_callback,
             const OnErrorCallback& error_callback,
             const base::Closure& window_close_callback,
@@ -193,8 +197,19 @@
             float layout_refresh_rate, const Options& options);
   ~WebModule();
 
-  // Call this to inject a keyboard event into the web module.
-  void InjectKeyboardEvent(const dom::KeyboardEvent::Data& event);
+  // Call this to inject a keyboard event into the web module. The value for
+  // type represents the event name, for example 'keydown' or 'keyup'.
+  void InjectKeyboardEvent(base::Token type,
+                           const dom::KeyboardEventInit& event);
+
+  // Call this to inject a pointer event into the web module. The value for type
+  // represents the event name, for example 'pointerdown', 'pointerup', or
+  // 'pointermove'.
+  void InjectPointerEvent(base::Token type, const dom::PointerEventInit& event);
+
+  // Call this to inject a wheel event into the web module. The value for type
+  // represents the event name, for example 'wheel'.
+  void InjectWheelEvent(base::Token type, const dom::WheelEventInit& event);
 
   // Call this to execute Javascript code in this web module.  The calling
   // thread will block until the JavaScript has executed and the output results
@@ -217,13 +232,12 @@
   debug::DebugServer* GetDebugServer();
 #endif  // ENABLE_DEBUG_CONSOLE
 
-  // Suspends the WebModule from creating new render trees, and releases this
-  // web module's reference to the resource provider, clearing it out and
-  // releasing all references to any resources created from it.
-  void Suspend();
-  // Resumes the WebModule, possibly with a new resource provider.  This method
-  // can only be called if we have previously suspended the WebModule.
-  void Resume(render_tree::ResourceProvider* resource_provider);
+  // LifecycleObserver implementation
+  void Start(render_tree::ResourceProvider* resource_provider) OVERRIDE;
+  void Pause() OVERRIDE;
+  void Unpause() OVERRIDE;
+  void Suspend() OVERRIDE;
+  void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE;
 
  private:
   // Data required to construct a WebModule, initialized in the constructor and
@@ -231,6 +245,7 @@
   struct ConstructionData {
     ConstructionData(
         const GURL& initial_url,
+        base::ApplicationState initial_application_state,
         const OnRenderTreeProducedCallback& render_tree_produced_callback,
         const OnErrorCallback& error_callback,
         const base::Closure& window_close_callback,
@@ -242,6 +257,7 @@
         int dom_max_element_depth, system_window::SystemWindow* system_window,
         float layout_refresh_rate, const Options& options)
         : initial_url(initial_url),
+          initial_application_state(initial_application_state),
           render_tree_produced_callback(render_tree_produced_callback),
           error_callback(error_callback),
           window_close_callback(window_close_callback),
@@ -256,6 +272,7 @@
           options(options) {}
 
     GURL initial_url;
+    base::ApplicationState initial_application_state;
     OnRenderTreeProducedCallback render_tree_produced_callback;
     OnErrorCallback error_callback;
     const base::Closure& window_close_callback;
diff --git a/src/cobalt/browser/web_module_stat_tracker.cc b/src/cobalt/browser/web_module_stat_tracker.cc
index bb5f370..4151969 100644
--- a/src/cobalt/browser/web_module_stat_tracker.cc
+++ b/src/cobalt/browser/web_module_stat_tracker.cc
@@ -80,6 +80,7 @@
   // If this is a valid event type, then start tracking it.
   if (current_event_type_ != kEventTypeInvalid) {
     event_is_processing_ = true;
+    event_start_time_ = base::TimeTicks::Now();
 
     dom_stat_tracker_->OnStartEvent();
     layout_stat_tracker_->OnStartEvent();
@@ -128,6 +129,12 @@
           StringPrintf("Event.Count.%s.DOM.HtmlElement.Destroyed",
                        name.c_str()),
           0, "Number of HTML elements destroyed."),
+      count_dom_html_elements_added(
+          StringPrintf("Event.Count.%s.DOM.HtmlElement.Added", name.c_str()), 0,
+          "Number of HTML elements added to document."),
+      count_dom_html_elements_removed(
+          StringPrintf("Event.Count.%s.DOM.HtmlElement.Removed", name.c_str()),
+          0, "Number of HTML elements removed from document."),
       count_dom_update_matching_rules(
           StringPrintf("Event.Count.%s.DOM.HtmlElement.UpdateMatchingRules",
                        name.c_str()),
@@ -225,6 +232,8 @@
 
 void WebModuleStatTracker::EndCurrentEvent(bool was_render_tree_produced) {
   if (current_event_type_ == kEventTypeInvalid) {
+    dom_stat_tracker_->OnEndEvent();
+    layout_stat_tracker_->OnEndEvent();
     return;
   }
 
@@ -240,6 +249,10 @@
       dom_stat_tracker_->html_elements_created_count();
   event_stats->count_dom_html_elements_destroyed =
       dom_stat_tracker_->html_elements_destroyed_count();
+  event_stats->count_dom_html_elements_added =
+      dom_stat_tracker_->html_elements_added_to_document_count();
+  event_stats->count_dom_html_elements_removed =
+      dom_stat_tracker_->html_elements_removed_from_document_count();
   event_stats->count_dom_update_matching_rules =
       dom_stat_tracker_->update_matching_rules_count();
   event_stats->count_dom_update_computed_style =
@@ -283,17 +296,31 @@
           layout::LayoutStatTracker::kStopWatchTypeRenderAndAnimate);
 
 #if defined(ENABLE_WEBDRIVER)
+  // Include the event's numbers in the total counts.
+  int html_elements_count = dom_stat_tracker_->html_elements_count() +
+                            dom_stat_tracker_->html_elements_created_count() -
+                            dom_stat_tracker_->html_elements_destroyed_count();
+  int document_html_elements_count =
+      dom_stat_tracker_->document_html_elements_count() +
+      dom_stat_tracker_->html_elements_added_to_document_count() -
+      dom_stat_tracker_->html_elements_removed_from_document_count();
+  int layout_boxes_count = layout_stat_tracker_->total_boxes() +
+                           layout_stat_tracker_->boxes_created_count() -
+                           layout_stat_tracker_->boxes_destroyed_count();
+
   // When the Webdriver is enabled, all of the event's values are stored within
   // a single string representing a dictionary of key-value pairs. This allows
   // the Webdriver to query a single CVal to retrieve all of the event's values.
   std::ostringstream oss;
   oss << "{"
+      << "\"StartTime\":" << event_start_time_.ToInternalValue() << ", "
       << "\"ProducedRenderTree\":" << was_render_tree_produced << ", "
       << "\"CntDomEventListeners\":"
       << dom::GlobalStats::GetInstance()->GetNumEventListeners() << ", "
       << "\"CntDomNodes\":" << dom::GlobalStats::GetInstance()->GetNumNodes()
       << ", "
-      << "\"CntDomHtmlElements\":" << dom_stat_tracker_->total_html_elements()
+      << "\"CntDomHtmlElements\":" << html_elements_count << ", "
+      << "\"CntDomDocumentHtmlElements\":" << document_html_elements_count
       << ", "
       << "\"CntDomHtmlElementsCreated\":"
       << dom_stat_tracker_->html_elements_created_count() << ", "
@@ -306,7 +333,7 @@
       << "\"CntDomGeneratePseudoComputedStyle\":"
       << dom_stat_tracker_->generate_pseudo_element_computed_style_count()
       << ", "
-      << "\"CntLayoutBoxes\":" << layout_stat_tracker_->total_boxes() << ", "
+      << "\"CntLayoutBoxes\":" << layout_boxes_count << ", "
       << "\"CntLayoutBoxesCreated\":"
       << layout_stat_tracker_->boxes_created_count() << ", "
       << "\"CntLayoutUpdateSize\":" << layout_stat_tracker_->update_size_count()
diff --git a/src/cobalt/browser/web_module_stat_tracker.h b/src/cobalt/browser/web_module_stat_tracker.h
index 3956a52..08ae658 100644
--- a/src/cobalt/browser/web_module_stat_tracker.h
+++ b/src/cobalt/browser/web_module_stat_tracker.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "base/memory/scoped_vector.h"
+#include "base/time.h"
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/stop_watch.h"
 #include "cobalt/dom/dom_stat_tracker.h"
@@ -83,6 +84,8 @@
     // Count-related
     base::CVal<int, base::CValPublic> count_dom_html_elements_created;
     base::CVal<int, base::CValPublic> count_dom_html_elements_destroyed;
+    base::CVal<int, base::CValPublic> count_dom_html_elements_added;
+    base::CVal<int, base::CValPublic> count_dom_html_elements_removed;
     base::CVal<int, base::CValPublic> count_dom_update_matching_rules;
     base::CVal<int, base::CValPublic> count_dom_update_computed_style;
     base::CVal<int, base::CValPublic>
@@ -144,7 +147,8 @@
 
   std::string name_;
 
-  base::CVal<bool, base::CValPublic> event_is_processing_;
+  base::CVal<bool> event_is_processing_;
+  base::TimeTicks event_start_time_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index 663e4ba..74e0b49 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -56,6 +56,7 @@
         '<(DEPTH)/cobalt/media_session/media_session.gyp:*',
         '<(DEPTH)/cobalt/media_session/media_session_test.gyp:*',
         '<(DEPTH)/cobalt/network/network.gyp:*',
+        '<(DEPTH)/cobalt/page_visibility/page_visibility.gyp:*',
         '<(DEPTH)/cobalt/render_tree/render_tree.gyp:*',
         '<(DEPTH)/cobalt/renderer/renderer.gyp:*',
         '<(DEPTH)/cobalt/renderer/sandbox/sandbox.gyp:*',
@@ -72,6 +73,7 @@
         '<(DEPTH)/cobalt/websocket/websocket.gyp:*',
         '<(DEPTH)/cobalt/xhr/xhr.gyp:*',
         '<(DEPTH)/crypto/crypto.gyp:crypto_unittests',
+        '<(DEPTH)/net/net.gyp:net_unittests',
         '<(DEPTH)/sql/sql.gyp:sql_unittests',
       ],
       'conditions': [
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 8a3b29a..66e3496 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-62465
\ No newline at end of file
+71685
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index f3941f4..3133cd6 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -21,9 +21,9 @@
     # Whether Cobalt is being built.
     'cobalt': 1,
 
-    # Similarly to chromium_code, marks the projects that are created by the
-    # Cobalt team and thus are held to the highest standards of code health.
-    'cobalt_code%': 0,
+    # Enabling this variable enables pedantic levels of warnings for the current
+    # toolchain.
+    'sb_pedantic_warnings%': 0,
 
     # Contains the current build configuration.
     'cobalt_config%': 'gold',
@@ -206,14 +206,15 @@
     # Set to 1 to compile with SPDY support.
     'enable_spdy%': 0,
 
+    # Set to 1 to enable filtering of HTTP headers before sending.
+    'enable_xhr_header_filtering%': 0,
+
     # Halt execution on failure to allocate memory.
     'abort_on_allocation_failure%': 1,
 
     # Used by cobalt/media/media.gyp to pick a proper media platform.
     'sb_media_platform%': 'starboard',
 
-    'sb_has_deploy_step%': 0,
-
     # Needed for backwards compatibility with lbshell code.
     'lbshell_root%': '<(DEPTH)/lbshell',
 
@@ -407,10 +408,10 @@
     'platform_libraries%': [],
 
 
-    # The only currently-supported Javascript engine is 'mozjs'.
+    # The only currently-supported Javascript engine is 'mozjs-45'.
     # TODO: Figure out how to massage gyp the right way to make this work
     # as expected, rather than requiring it to be set for each platform.
-    #'javascript_engine%': 'mozjs',
+    #'javascript_engine%': 'mozjs-45',
 
     # Disable jit and run in interpreter-only mode by default. It can be set to
     # 1 to run in jit mode.  We have found that disabling jit often results in
@@ -489,16 +490,16 @@
 
   'target_defaults': {
     'variables': {
-      # The condition that operates on cobalt_code is in a target_conditions
-      # section, and will not have access to the default fallback value of
-      # cobalt_code at the top of this file, or to the cobalt_code
-      # variable placed at the root variables scope of .gyp files, because
-      # those variables are not set at target scope.  As a workaround,
-      # if cobalt_code is not set at target scope, define it in target scope
-      # to contain whatever value it has during early variable expansion.
-      # That's enough to make it available during target conditional
-      # processing.
-      'cobalt_code%': '<(cobalt_code)',
+      # The condition that operates on sb_pedantic_warnings is in a
+      # target_conditions section, and will not have access to the default
+      # fallback value of sb_pedantic_warnings at the top of this file,
+      # or to the sb_pedantic_warnings variable placed at the root
+      # variables scope of .gyp files, because those variables are not set at
+      # target scope.  As a workaround, if sb_pedantic_warnings is not
+      # set at target scope, define it in target scope to contain whatever
+      # value it has during early variable expansion. That's enough to make
+      # it available during target conditional processing.
+      'sb_pedantic_warnings%': '<(sb_pedantic_warnings)',
     },
     'defines': [
       'COBALT',
diff --git a/src/cobalt/build/config/base.py b/src/cobalt/build/config/base.py
index 622a61e..d7cd087 100644
--- a/src/cobalt/build/config/base.py
+++ b/src/cobalt/build/config/base.py
@@ -34,7 +34,7 @@
 VALID_BUILD_CONFIGS = [Configs.DEBUG, Configs.DEVEL, Configs.QA, Configs.GOLD]
 
 # Represents all supported platforms, uniquified and sorted.
-VALID_PLATFORMS = sorted(gyp_utils.GetThirdPartyPlatforms().keys())
+VALID_PLATFORMS = sorted(gyp_utils.GetAllPlatforms().keys())
 
 _CURRENT_PATH = os.path.abspath(os.path.dirname(__file__))
 
@@ -69,9 +69,11 @@
     Returns:
         A list containing paths to .gypi files.
     """
-    platforms = gyp_utils.GetThirdPartyPlatforms()
-    if self.platform in platforms:
-      return [os.path.join(platforms[self.platform], 'gyp_configuration.gypi')]
+    platforms = gyp_utils.GetAllPlatforms()
+    if self.platform in platforms.keys():
+      return [
+          os.path.join(platforms[self.platform].path, 'gyp_configuration.gypi')
+      ]
     return [os.path.join(self.config_path, self.platform + '.gypi')]
 
   def GetEnvironmentVariables(self):
@@ -111,9 +113,9 @@
   """
   try:
     logging.debug('Loading platform configuration for "%s".', platform)
-    platforms = gyp_utils.GetThirdPartyPlatforms()
-    if platform in platforms:
-      platform_path = platforms[platform]
+    platforms = gyp_utils.GetAllPlatforms()
+    if platform in platforms.keys():
+      platform_path = platforms[platform].path
       module_path = os.path.join(platform_path, 'gyp_configuration.py')
       platform_module = imp.load_source('platform_module', module_path)
     else:
diff --git a/src/cobalt/build/config/starboard.py b/src/cobalt/build/config/starboard.py
index 562c166..07ffc4b 100644
--- a/src/cobalt/build/config/starboard.py
+++ b/src/cobalt/build/config/starboard.py
@@ -30,6 +30,13 @@
   def IsStarboard(self):
     return True
 
+  def GetEnvironmentVariables(self):
+    raise NotImplementedError
+
+  def GetToolchain(self):
+    """Returns the instance of the toolchain implementation class."""
+    return None
+
   def GetVariables(self, config, use_clang=0):
     use_asan = 0
     use_tsan = 0
diff --git a/src/cobalt/build/gyp_cobalt b/src/cobalt/build/gyp_cobalt
index e83ef44..4444e7e 100755
--- a/src/cobalt/build/gyp_cobalt
+++ b/src/cobalt/build/gyp_cobalt
@@ -157,9 +157,11 @@
     # Make a copy of the common arguments.
     args = self.common_args[:]
 
-    # Add config specific variables.
     variables = {
+        # Add config specific variables.
         'cobalt_config': config_name,
+        # Default deploy script. Certain platforms may choose to change this.
+        'include_path_platform_deploy_gypi': 'starboard/build/deploy.gypi'
     }
     variables.update(self.platform_config.GetVariables(config_name))
     _AppendVariables(variables, args)
@@ -234,9 +236,9 @@
     if self.platform_config.IsStarboard():
       variables['OS'] = 'starboard'
       platform = self.platform_config.platform
-      platforms = gyp_utils.GetThirdPartyPlatforms()
-      if platform in platforms:
-        full_starboard_path = platforms[platform]
+      platforms = gyp_utils.GetAllPlatforms()
+      if platform in platforms.keys():
+        full_starboard_path = platforms[platform].path
         assert full_starboard_path[:len(source_tree_dir)] == source_tree_dir
         starboard_path = full_starboard_path[len(source_tree_dir) + 1:]
         starboard_path = starboard_path.replace(os.sep, '/')
diff --git a/src/cobalt/build/gyp_utils.py b/src/cobalt/build/gyp_utils.py
index 28248ef..bf38b8a 100644
--- a/src/cobalt/build/gyp_utils.py
+++ b/src/cobalt/build/gyp_utils.py
@@ -277,8 +277,8 @@
   return value
 
 
-def _FindThirdPartyPlatforms():
-  """Workhorse for GetThirdPartyPlatforms().
+def _FindAllPlatforms():
+  """Workhorse for GetAllPlatforms().
 
   Search through directories listed in _PORT_SEARCH_PATH to find valid
   ports, so that they can be added to the VALID_PLATFORMS list. This
@@ -286,7 +286,7 @@
   code in src/cobalt/.
 
   Returns:
-    A dictionary of name->absolute paths to the port directory.
+    A dictionary of name->PlatformInfo.
 
   """
 
@@ -306,19 +306,19 @@
         logging.error('Found duplicate port name "%s" at "%s" and "%s"',
                       platform_info.port_name, result[platform_info.port_name],
                       platform_info.path)
-      result[platform_info.port_name] = platform_info.path
+      result[platform_info.port_name] = platform_info
 
   return result
 
 
 # Global cache of TPP so the filesystem walk is only done once, and the values
 # are always consistent.
-_THIRD_PARTY_PLATFORMS = None
+_ALL_PLATFORMS = None
 
 
-def GetThirdPartyPlatforms():
+def GetAllPlatforms():
   """Return valid platform definitions found by scanning the source tree."""
-  global _THIRD_PARTY_PLATFORMS
-  if _THIRD_PARTY_PLATFORMS is None:
-    _THIRD_PARTY_PLATFORMS = _FindThirdPartyPlatforms()
-  return _THIRD_PARTY_PLATFORMS
+  global _ALL_PLATFORMS
+  if _ALL_PLATFORMS is None:
+    _ALL_PLATFORMS = _FindAllPlatforms()
+  return _ALL_PLATFORMS
diff --git a/src/cobalt/build/ninja/ninja-linux32.armv7l b/src/cobalt/build/ninja/ninja-linux32.armv7l
index e69de29..cb4c3ec 100644
--- a/src/cobalt/build/ninja/ninja-linux32.armv7l
+++ b/src/cobalt/build/ninja/ninja-linux32.armv7l
Binary files differ
diff --git a/src/cobalt/csp/csp.gyp b/src/cobalt/csp/csp.gyp
index a1e9144..e8e9ac7 100644
--- a/src/cobalt/csp/csp.gyp
+++ b/src/cobalt/csp/csp.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp
index 37ea756..24bf201 100644
--- a/src/cobalt/css_parser/css_parser.gyp
+++ b/src/cobalt/css_parser/css_parser.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
 
     # Define the platform specific Bison binary.
     'conditions': [
diff --git a/src/cobalt/css_parser/parser.cc b/src/cobalt/css_parser/parser.cc
index 7be1525..b8b9047 100644
--- a/src/cobalt/css_parser/parser.cc
+++ b/src/cobalt/css_parser/parser.cc
@@ -400,6 +400,11 @@
         LogError("unrecoverable syntax error");
       }
       return false;
+    case 2:
+      LogError(
+          "the html put too many queries on bison stack without "
+          "closing/reducing");
+      return false;
     default:
       NOTREACHED();
       return false;
diff --git a/src/cobalt/cssom/cssom.gyp b/src/cobalt/cssom/cssom.gyp
index 08156e5..8adc37f 100644
--- a/src/cobalt/cssom/cssom.gyp
+++ b/src/cobalt/cssom/cssom.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp
index 8478def..318282a 100644
--- a/src/cobalt/cssom/cssom_test.gyp
+++ b/src/cobalt/cssom/cssom_test.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/debug/content/cobalt-logo-180.png b/src/cobalt/debug/content/cobalt-logo-180.png
index 4488434..f50f70c 100644
--- a/src/cobalt/debug/content/cobalt-logo-180.png
+++ b/src/cobalt/debug/content/cobalt-logo-180.png
Binary files differ
diff --git a/src/cobalt/debug/content/favicon.ico b/src/cobalt/debug/content/favicon.ico
index eae7eed..2d01cfa 100644
--- a/src/cobalt/debug/content/favicon.ico
+++ b/src/cobalt/debug/content/favicon.ico
Binary files differ
diff --git a/src/cobalt/debug/debug.gyp b/src/cobalt/debug/debug.gyp
index 9fa32c3..3242c1d 100644
--- a/src/cobalt/debug/debug.gyp
+++ b/src/cobalt/debug/debug.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/doc/performance_tuning.md b/src/cobalt/doc/performance_tuning.md
index 44fc05f..a142028 100644
--- a/src/cobalt/doc/performance_tuning.md
+++ b/src/cobalt/doc/performance_tuning.md
@@ -295,16 +295,12 @@
 wraps it with a custom allocator, in order to avoid fragmentation of main
 memory.  However, depending on your platform and your system allocator,
 overall memory usage may improve if media buffer allocations were made
-normally via the system allocator instead.  TODO: Unfortunately, there is
-currently no flip to switch to toggle one method or the other, however
-we should look into this because switching to using the system allocator
-has been found to reduce memory usage by ~5MB.  In the meantime, it can
-be hacked by modifying
-[`cobalt/media/shell_media_platform_starboard.cc`](../media/shell_media_platform_starboard.cc).
-Note also that if you choose to pre-allocate memory, for 1080p video it has been
-found that 24MB is a good media buffer size.  The pre-allocated media buffer
-capacity size can be adjusted by modifying the value of
-`SB_MEDIA_MAIN_BUFFER_BUDGET` in your platform's `configuration_public.h` file.
+normally via the system allocator instead.  This can be achieved by setting
+`cobalt_media_buffer_initial_capacity` and `cobalt_media_buffer_allocation_unit`
+to 0 in gyp_configuration.gypi.  Note also that if you choose to pre-allocate
+memory, for 1080p video it has been found that 24MB is a good media buffer size.
+The pre-allocated media buffer capacity size can be adjusted by modifying the
+value of `cobalt_media_buffer_initial_capacity` mentioned above.
 
 **Tags:** *configuration_public.h, cpu memory.*
 
diff --git a/src/cobalt/dom/animation_frame_request_callback_list.cc b/src/cobalt/dom/animation_frame_request_callback_list.cc
index 6108afc..e20138e 100644
--- a/src/cobalt/dom/animation_frame_request_callback_list.cc
+++ b/src/cobalt/dom/animation_frame_request_callback_list.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/animation_frame_request_callback_list.h"
 
 #include "base/debug/trace_event.h"
+#include "cobalt/dom/global_stats.h"
 
 namespace cobalt {
 namespace dom {
@@ -44,12 +45,18 @@
   TRACE_EVENT1("cobalt::dom", "Window::RunAnimationFrameCallbacks()",
                "number_of_callbacks", frame_request_callbacks_.size());
 
+  // The callbacks are now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   for (InternalList::const_iterator iter = frame_request_callbacks_.begin();
        iter != frame_request_callbacks_.end(); ++iter) {
     if (!(*iter)->cancelled) {
       (*iter)->callback.value().Run(animation_time);
     }
   }
+
+  // The callbacks are done running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 }
 
 bool AnimationFrameRequestCallbackList::HasPendingCallbacks() const {
diff --git a/src/cobalt/dom/camera_3d.cc b/src/cobalt/dom/camera_3d.cc
index 297d1cd..9741f74 100644
--- a/src/cobalt/dom/camera_3d.cc
+++ b/src/cobalt/dom/camera_3d.cc
@@ -16,10 +16,19 @@
 
 #include "cobalt/dom/camera_3d.h"
 
+#include "third_party/glm/glm/gtx/euler_angles.hpp"
+
+#include "base/time.h"
+#include "cobalt/dom/device_orientation_event.h"
+
 namespace cobalt {
 namespace dom {
 
-Camera3D::Camera3D(const scoped_refptr<input::Camera3D>& impl) : impl_(impl) {}
+Camera3D::Camera3D(const scoped_refptr<input::Camera3D>& impl)
+    : impl_(impl),
+      last_event_alpha_(720.0),
+      last_event_beta_(720.0),
+      last_event_gamma_(720.0) {}
 
 void Camera3D::CreateKeyMapping(int keycode, uint32 camera_axis,
                                 float degrees_per_second) {
@@ -32,5 +41,88 @@
 
 void Camera3D::Reset() { impl_->Reset(); }
 
+void Camera3D::StartOrientationEvents(
+    const base::WeakPtr<EventTarget>& target) {
+  if (!impl()) {
+    return;
+  }
+  orientation_event_timer_.Start(
+      FROM_HERE,
+      base::TimeDelta::FromSecondsD(1.0 / ORIENTATION_POLL_FREQUENCY_HZ),
+      base::Bind(&Camera3D::FireOrientationEvent, base::Unretained(this),
+                 target));
+}
+
+void Camera3D::StopOrientationEvents() { orientation_event_timer_.Stop(); }
+
+namespace {
+
+// These produces angles within the ranges: alpha in [-180,180], beta in
+// [-90,90], gamma in [-180,180].
+static void QuaternionToIntrinsicZXY(const glm::dquat& q, double* intrinsic_z,
+                                     double* intrinsic_x, double* intrinsic_y) {
+  *intrinsic_z = atan2(2 * (q.x * q.y + q.w * q.z),
+                       q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z);
+  *intrinsic_x = asin(-2 * (q.y * q.z - q.w * q.x));
+  *intrinsic_y = atan2(2 * (q.x * q.z + q.w * q.y),
+                       q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z);
+}
+
+}  // namespace
+
+void Camera3D::FireOrientationEvent(base::WeakPtr<EventTarget> target) {
+  glm::dquat quaternion = glm::normalize(
+      glm::dquat(impl()->GetOrientation()) *
+      // The API assumes a different initial orientation (straight down instead
+      // of ahead), requiring a previous rotation of 90 degrees.
+      glm::angleAxis(M_PI / 2, glm::dvec3(1.0f, 0.0f, 0.0f)));
+
+  double intrinsic_z = 0.0f, intrinsic_x = 0.0f, intrinsic_y = 0.0f;
+  QuaternionToIntrinsicZXY(quaternion, &intrinsic_z, &intrinsic_x,
+                           &intrinsic_y);
+
+  // Adjust to the angle ranges specified by the Device Orientation API.
+  // They should be: alpha in [0,360], beta in [-180,180], gamma in [-90,90].
+  double alpha = input::Camera3D::RadiansToDegrees(intrinsic_z);
+  double beta = input::Camera3D::RadiansToDegrees(intrinsic_x);
+  double gamma = -input::Camera3D::RadiansToDegrees(intrinsic_y);
+
+  if (gamma > 90 || gamma < -90) {
+    gamma = copysign(180, gamma) - gamma;
+    // Represent excess gamma as rotations along other axes.
+    beta = 180 - beta;
+    alpha = 180 - alpha;
+  }
+
+  alpha = fmod(360 + alpha, 360);
+  beta = beta >= 180 ? beta - 360 : beta;
+
+  // Filter event based the time elapsed and the angle deltas since the last
+  // event.
+  base::TimeTicks now = base::TimeTicks::Now();
+  if (!last_event_time_ ||
+      (now - *last_event_time_).InSecondsF() >
+          1.0 / ORIENTATION_EVENT_MIN_FREQUENCY_HZ ||
+      fabs(alpha - last_event_alpha_) >
+          ORIENTATION_EVENT_DELTA_THRESHOLD_DEGREES ||
+      fabs(beta - last_event_beta_) >
+          ORIENTATION_EVENT_DELTA_THRESHOLD_DEGREES ||
+      fabs(gamma - last_event_gamma_) >
+          ORIENTATION_EVENT_DELTA_THRESHOLD_DEGREES) {
+    DeviceOrientationEventInit init;
+    init.set_absolute(false);
+    init.set_alpha(alpha);
+    init.set_beta(beta);
+    init.set_gamma(gamma);
+
+    target->DispatchEvent(
+        new DeviceOrientationEvent("deviceorientation", init));
+    last_event_time_ = now;
+    last_event_alpha_ = alpha;
+    last_event_beta_ = beta;
+    last_event_gamma_ = gamma;
+  }
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/camera_3d.h b/src/cobalt/dom/camera_3d.h
index 056683b..792ea1f 100644
--- a/src/cobalt/dom/camera_3d.h
+++ b/src/cobalt/dom/camera_3d.h
@@ -17,6 +17,8 @@
 #ifndef COBALT_DOM_CAMERA_3D_H_
 #define COBALT_DOM_CAMERA_3D_H_
 
+#include "base/timer.h"
+#include "cobalt/dom/event_target.h"
 #include "cobalt/input/camera_3d.h"
 #include "cobalt/script/wrappable.h"
 
@@ -35,6 +37,18 @@
     kDomCameraYaw = input::Camera3D::kCameraYaw,
   };
 
+  // The frequency at which Camera3D will poll the hardware implementation.
+  // Effectively the maximum frequency at which orientation events will be
+  // dispatched.
+  static constexpr double ORIENTATION_POLL_FREQUENCY_HZ = 30.0;
+  // The minimum frequency at which Camera3D will dispatch events. Its inverse
+  // is the most that will elapse between dispatching consecutive events (might
+  // not be true in practice due to other processing done on the web thread).
+  static constexpr double ORIENTATION_EVENT_MIN_FREQUENCY_HZ = 0.5;
+  // The minimum change in any of the angles that will trigger an event. The
+  // above frequencies still have precedence to decide the timing.
+  static constexpr double ORIENTATION_EVENT_DELTA_THRESHOLD_DEGREES = 0.5;
+
   explicit Camera3D(const scoped_refptr<input::Camera3D>& impl);
 
   // Creates a mapping between the specified keyCode and the specified camera
@@ -54,9 +68,14 @@
   // Custom, not in any spec.
   scoped_refptr<input::Camera3D> impl() { return impl_; }
 
+  void StartOrientationEvents(const base::WeakPtr<EventTarget>& target);
+  void StopOrientationEvents();
+
   DEFINE_WRAPPABLE_TYPE(Camera3D);
 
  private:
+  void FireOrientationEvent(const base::WeakPtr<EventTarget> target);
+
   // We delegate all calls to the implementation of Camera3D so that all camera
   // state is stored within an object that is *not* a script::Wrappable. This
   // is important because input::Camera3D will typically be attached to a render
@@ -65,6 +84,13 @@
   // for just this.
   scoped_refptr<input::Camera3D> impl_;
 
+  // State to control the polling and event firing rate.
+  base::RepeatingTimer<Camera3D> orientation_event_timer_;
+  base::optional<base::TimeTicks> last_event_time_;
+  double last_event_alpha_;
+  double last_event_beta_;
+  double last_event_gamma_;
+
   DISALLOW_COPY_AND_ASSIGN(Camera3D);
 };
 
diff --git a/src/cobalt/dom/camera_3d_impl.cc b/src/cobalt/dom/camera_3d_impl.cc
deleted file mode 100644
index 90a502e..0000000
--- a/src/cobalt/dom/camera_3d_impl.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2017 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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/camera_3d_impl.h"
-
-#include <algorithm>
-#include <cmath>
-
-#include "third_party/glm/glm/gtc/matrix_transform.hpp"
-#include "third_party/glm/glm/gtx/transform.hpp"
-
-namespace cobalt {
-namespace dom {
-
-Camera3DImpl::Camera3DImpl(
-    const scoped_refptr<input::InputPoller>& input_poller)
-    : input_poller_(input_poller) {}
-
-void Camera3DImpl::CreateKeyMapping(int keycode, uint32 camera_axis,
-                                    float degrees_per_second) {
-  base::AutoLock lock(mutex_);
-  keycode_map_[keycode] = KeycodeMappingInfo(camera_axis, degrees_per_second);
-}
-
-void Camera3DImpl::ClearKeyMapping(int keycode) {
-  base::AutoLock lock(mutex_);
-  keycode_map_.erase(keycode);
-}
-
-void Camera3DImpl::ClearAllKeyMappings() {
-  base::AutoLock lock(mutex_);
-  keycode_map_.clear();
-}
-
-void Camera3DImpl::Reset() {
-  base::AutoLock lock(mutex_);
-  orientation_ = Orientation();
-}
-
-namespace {
-
-const float kPiF = static_cast<float>(M_PI);
-
-float DegreesToRadians(float degrees) { return (degrees / 360.0f) * 2 * kPiF; }
-
-}  // namespace
-
-glm::mat4 Camera3DImpl::QueryViewPerspectiveMatrix(
-    float width_over_height_aspect_ratio, float max_horizontal_fov_rad,
-    float max_vertical_fov_rad) {
-  base::AutoLock lock(mutex_);
-  AccumulateOrientation();
-
-  // Setup a temporary demo camera animation to show that this functionality
-  // works.  This should eventually be replaced by camera adjustments driven
-  // by input.  Note that we invert the rotation angles since this matrix is
-  // applied to the objects in our scene, and if the camera moves right, the
-  // objects, relatively, would move right.
-  glm::mat4 camera_rotations =
-      glm::rotate(-DegreesToRadians(orientation_.roll), glm::vec3(0, 0, 1)) *
-      glm::rotate(-DegreesToRadians(orientation_.pitch), glm::vec3(1, 0, 0)) *
-      glm::rotate(-DegreesToRadians(orientation_.yaw), glm::vec3(0, 1, 0));
-
-  // Setup a (right-handed) perspective projection matrix.
-  float vertical_fov_rad =
-      std::min(max_vertical_fov_rad,
-               2 * static_cast<float>(atan(tan(max_horizontal_fov_rad * 0.5f) /
-                                           width_over_height_aspect_ratio)));
-
-  const float kNearZ = 0.01f;
-  const float kFarZ = 1000.0f;
-  glm::mat4 projection = glm::perspectiveRH(
-      vertical_fov_rad, width_over_height_aspect_ratio, kNearZ, kFarZ);
-  return projection * camera_rotations;
-}
-
-void Camera3DImpl::AccumulateOrientation() {
-  if (!input_poller_) {
-    // Nothing to do if no input poller was provided.
-    return;
-  }
-
-  base::TimeTicks now = base::TimeTicks::Now();
-  if (last_update_) {
-    base::TimeDelta delta = now - *last_update_;
-    // Cap out the maximum time delta that we will accumulate changes over, to
-    // avoid a random extra long time delta that completely changes the camera
-    // orientation.
-    const base::TimeDelta kMaxTimeDelta = base::TimeDelta::FromMilliseconds(40);
-    if (delta > kMaxTimeDelta) {
-      delta = kMaxTimeDelta;
-    }
-
-    for (KeycodeMap::const_iterator iter = keycode_map_.begin();
-         iter != keycode_map_.end(); ++iter) {
-      // If the key does not have analog output, the AnalogInput() method will
-      // always return 0.0f, so check this first, and if it is indeed 0,
-      // fallback to a check to see if the button is digital and pressed.
-      float value = input_poller_->AnalogInput(static_cast<SbKey>(iter->first));
-      if (value == 0.0f) {
-        value = input_poller_->IsPressed(static_cast<SbKey>(iter->first))
-                    ? 1.0f
-                    : 0.0f;
-      }
-
-      // Get a pointer to the camera axis angle that this key is bound to.
-      float* target_angle;
-      switch (iter->second.axis) {
-        case kCameraRoll:
-          target_angle = &orientation_.roll;
-          break;
-        case kCameraPitch:
-          target_angle = &orientation_.pitch;
-          break;
-        case kCameraYaw:
-          target_angle = &orientation_.yaw;
-          break;
-      }
-
-      // Apply the angle adjustment from the key.
-      *target_angle += value * iter->second.degrees_per_second *
-                       static_cast<float>(delta.InSecondsF());
-
-      // Apply any clamping or wrapping to the resulting camera angles.
-      if (iter->second.axis == kCameraPitch) {
-        *target_angle = std::min(90.0f, std::max(-90.0f, *target_angle));
-      } else {
-        *target_angle = static_cast<float>(fmod(*target_angle, 360));
-        if (*target_angle < 0) {
-          *target_angle += 360;
-        }
-      }
-    }
-  }
-  last_update_ = now;
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/src/cobalt/dom/camera_3d_impl.h b/src/cobalt/dom/camera_3d_impl.h
deleted file mode 100644
index 311d2ed..0000000
--- a/src/cobalt/dom/camera_3d_impl.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2017 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_CAMERA_3D_IMPL_H_
-#define COBALT_DOM_CAMERA_3D_IMPL_H_
-
-#include <map>
-#include <utility>
-
-#include "base/optional.h"
-#include "base/synchronization/lock.h"
-#include "cobalt/input/input_poller.h"
-#include "third_party/glm/glm/mat4x4.hpp"
-
-namespace cobalt {
-namespace dom {
-
-// 3D camera is used for setting the key mapping.
-class Camera3DImpl : public base::RefCountedThreadSafe<Camera3DImpl> {
- public:
-  enum CameraAxes {
-    // Restricted to [0deg, 360deg]
-    kCameraRoll = 0x00,
-    // Restricted to [-90deg, 90deg]
-    kCameraPitch = 0x01,
-    // Restricted to [0deg, 360deg]
-    kCameraYaw = 0x02,
-  };
-
-  explicit Camera3DImpl(const scoped_refptr<input::InputPoller>& input_poller);
-
-  void CreateKeyMapping(int keycode, uint32 camera_axis,
-                        float degrees_per_second);
-  void ClearKeyMapping(int keycode);
-  void ClearAllKeyMappings();
-
-  void Reset();
-
-  // Returns the camera's view-perspective matrix, setup according to the passed
-  // in width/height aspect ratio.  It is likely that this function will be
-  // called from another thread, like a renderer thread.
-  glm::mat4 QueryViewPerspectiveMatrix(float width_over_height_aspect_ratio,
-                                       float max_horizontal_fov_rad,
-                                       float max_vertical_fov_rad);
-
- private:
-  struct KeycodeMappingInfo {
-    KeycodeMappingInfo() : axis(0), degrees_per_second(0.0f) {}
-    KeycodeMappingInfo(uint32 axis, float degrees_per_second)
-        : axis(axis), degrees_per_second(degrees_per_second) {}
-
-    uint32 axis;
-    float degrees_per_second;
-  };
-
-  typedef std::map<int, KeycodeMappingInfo> KeycodeMap;
-
-  // Structure to keep track of the current orientation state.
-  struct Orientation {
-    Orientation() : roll(0.0f), pitch(0.0f), yaw(0.0f) {}
-
-    float roll;
-    float pitch;
-    float yaw;
-  };
-
-  void AccumulateOrientation();
-
-  // The Camera3D's WebAPI will be accessed from the WebModule thread, but
-  // the internal camera orientation will be accessed on the renderer thread
-  // via QueryViewPerspectiveMatrix().  So, we do need a mutex to guard against
-  // these potentially parallel accesses.
-  base::Lock mutex_;
-
-  // A map of keys bound to camera movements.
-  KeycodeMap keycode_map_;
-
-  // The input poller from which we can check the state of a given key.
-  scoped_refptr<input::InputPoller> input_poller_;
-
-  // The current accumulated camera orientation state.
-  Orientation orientation_;
-
-  // The time that the last update to the camera's state has occurred.
-  base::optional<base::TimeTicks> last_update_;
-
-  DISALLOW_COPY_AND_ASSIGN(Camera3DImpl);
-};
-
-}  // namespace dom
-}  // namespace cobalt
-
-#endif  // COBALT_DOM_CAMERA_3D_IMPL_H_
diff --git a/src/cobalt/dom/cdata_section.h b/src/cobalt/dom/cdata_section.h
index bdf1605..137e46d 100644
--- a/src/cobalt/dom/cdata_section.h
+++ b/src/cobalt/dom/cdata_section.h
@@ -42,7 +42,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<CDATASection> AsCDATASection() OVERRIDE { return this; }
+  CDATASection* AsCDATASection() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
diff --git a/src/cobalt/dom/comment.h b/src/cobalt/dom/comment.h
index 26d2310..014f1f9 100644
--- a/src/cobalt/dom/comment.h
+++ b/src/cobalt/dom/comment.h
@@ -41,7 +41,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<Comment> AsComment() OVERRIDE { return this; }
+  Comment* AsComment() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
diff --git a/src/cobalt/dom/device_orientation_event.cc b/src/cobalt/dom/device_orientation_event.cc
new file mode 100644
index 0000000..284c5f9
--- /dev/null
+++ b/src/cobalt/dom/device_orientation_event.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/device_orientation_event.h"
+
+#include "cobalt/base/token.h"
+#include "cobalt/base/tokens.h"
+#include "cobalt/dom/storage.h"
+
+namespace cobalt {
+namespace dom {
+
+DeviceOrientationEvent::DeviceOrientationEvent()
+    : Event(base::Tokens::deviceorientation()) {}
+
+DeviceOrientationEvent::DeviceOrientationEvent(const std::string& type)
+    : Event(type) {}
+
+DeviceOrientationEvent::DeviceOrientationEvent(
+    const std::string& type, const DeviceOrientationEventInit& init)
+    : Event(type),
+      alpha_(init.alpha()),
+      beta_(init.beta()),
+      gamma_(init.gamma()),
+      absolute_(init.absolute()) {}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/device_orientation_event.h b/src/cobalt/dom/device_orientation_event.h
new file mode 100644
index 0000000..5c9c8a7
--- /dev/null
+++ b/src/cobalt/dom/device_orientation_event.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_DEVICE_ORIENTATION_EVENT_H_
+#define COBALT_DOM_DEVICE_ORIENTATION_EVENT_H_
+
+#include <string>
+
+#include "base/optional.h"
+#include "cobalt/dom/device_orientation_event_init.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace dom {
+
+// Represents a device orientation event, where the new orientation of the
+// device is represented as intrinsic rotations on the Z (by |alpha|
+// degrees), X' (by |beta| degrees), Y'' (by |gamma| degrees) axes; intrinsic
+// meaning each of the rotations is done on the axis of the rotated system
+// resulting of the previous rotation.
+//   https://www.w3.org/TR/2016/CR-orientation-event-20160818/
+class DeviceOrientationEvent : public Event {
+ public:
+  DeviceOrientationEvent();
+  explicit DeviceOrientationEvent(const std::string& type);
+
+  DeviceOrientationEvent(const std::string& type,
+                         const DeviceOrientationEventInit& init);
+
+  base::optional<double> alpha() const { return alpha_; }
+  base::optional<double> beta() const { return beta_; }
+  base::optional<double> gamma() const { return gamma_; }
+  bool absolute() const { return absolute_; }
+
+  DEFINE_WRAPPABLE_TYPE(DeviceOrientationEvent);
+
+ private:
+  base::optional<double> alpha_;
+  base::optional<double> beta_;
+  base::optional<double> gamma_;
+  bool absolute_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceOrientationEvent);
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_DEVICE_ORIENTATION_EVENT_H_
diff --git a/src/cobalt/dom/device_orientation_event.idl b/src/cobalt/dom/device_orientation_event.idl
new file mode 100644
index 0000000..0ecd3e9
--- /dev/null
+++ b/src/cobalt/dom/device_orientation_event.idl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.w3.org/TR/2016/CR-orientation-event-20160818/
+
+[Constructor(DOMString type, optional DeviceOrientationEventInit eventInitDict)]
+interface DeviceOrientationEvent : Event {
+  readonly attribute double? alpha;
+  readonly attribute double? beta;
+  readonly attribute double? gamma;
+  readonly attribute boolean absolute;
+};
diff --git a/src/cobalt/dom/device_orientation_event_init.idl b/src/cobalt/dom/device_orientation_event_init.idl
new file mode 100644
index 0000000..42fda86
--- /dev/null
+++ b/src/cobalt/dom/device_orientation_event_init.idl
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2016/CR-orientation-event-20160818/
+dictionary DeviceOrientationEventInit : EventInit {
+  double? alpha = null;
+  double? beta = null;
+  double? gamma = null;
+  boolean absolute = false;
+};
\ No newline at end of file
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index 85e8006..295eb3b 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -22,6 +22,7 @@
 #include "base/debug/trace_event.h"
 #include "base/message_loop.h"
 #include "base/string_util.h"
+#include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/css_media_rule.h"
 #include "cobalt/cssom/css_rule.h"
@@ -45,12 +46,17 @@
 #include "cobalt/dom/html_html_element.h"
 #include "cobalt/dom/html_script_element.h"
 #include "cobalt/dom/initial_computed_style.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/keyframes_map_updater.h"
 #include "cobalt/dom/location.h"
+#include "cobalt/dom/message_event.h"
+#include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/named_node_map.h"
 #include "cobalt/dom/node_descendants_iterator.h"
+#include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/ui_event.h"
+#include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/global_environment.h"
 #include "nb/memory_scope.h"
@@ -86,6 +92,7 @@
       dom_max_element_depth_(options.dom_max_element_depth) {
   DCHECK(html_element_context_);
   DCHECK(options.url.is_empty() || options.url.is_valid());
+  html_element_context_->page_visibility_state()->AddObserver(this);
 
   if (options.viewport_size) {
     SetViewport(*options.viewport_size);
@@ -215,11 +222,20 @@
     const std::string& interface_name,
     script::ExceptionState* exception_state) {
   TRACK_MEMORY_SCOPE("DOM");
-  // https://www.w3.org/TR/2015/WD-dom-20150428/#dom-document-createevent
+  // https://www.w3.org/TR/dom/#dom-document-createevent
   // The match of interface name is case-insensitive.
   if (base::strcasecmp(interface_name.c_str(), "event") == 0 ||
-      base::strcasecmp(interface_name.c_str(), "events") == 0) {
+      base::strcasecmp(interface_name.c_str(), "events") == 0 ||
+      base::strcasecmp(interface_name.c_str(), "htmlevents") == 0) {
     return new Event(Event::Uninitialized);
+  } else if (base::strcasecmp(interface_name.c_str(), "keyboardevent") == 0 ||
+             base::strcasecmp(interface_name.c_str(), "keyevents") == 0) {
+    return new KeyboardEvent(Event::Uninitialized);
+  } else if (base::strcasecmp(interface_name.c_str(), "messageevent") == 0) {
+    return new MessageEvent(Event::Uninitialized);
+  } else if (base::strcasecmp(interface_name.c_str(), "mouseevent") == 0 ||
+             base::strcasecmp(interface_name.c_str(), "mouseevents") == 0) {
+    return new MouseEvent(Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "uievent") == 0 ||
              base::strcasecmp(interface_name.c_str(), "uievents") == 0) {
     return new UIEvent(Event::Uninitialized);
@@ -343,6 +359,11 @@
   }
 }
 
+// https://www.w3.org/TR/2016/REC-html51-20161101/matching-html-elements-using-selectors.html#selectordef-hover
+scoped_refptr<HTMLElement> Document::indicated_element() const {
+  return indicated_element_.get();
+}
+
 void Document::set_cookie(const std::string& cookie) {
 #if defined(COBALT_BUILD_TYPE_GOLD)
   UNREFERENCED_PARAMETER(cookie);
@@ -398,6 +419,23 @@
   }
 }
 
+void Document::SetIndicatedElement(HTMLElement* indicated_element) {
+  if (indicated_element != indicated_element_) {
+    is_selector_tree_dirty_ = true;
+    if (indicated_element_) {
+      indicated_element_->OnCSSMutation();
+    }
+    if (indicated_element) {
+      indicated_element_ = base::AsWeakPtr(indicated_element);
+      indicated_element_->OnCSSMutation();
+    } else {
+      indicated_element_.reset();
+    }
+  }
+}
+
+const scoped_refptr<Window> Document::window() { return window_; }
+
 void Document::IncreaseLoadingCounter() { ++loading_counter_; }
 
 void Document::DecreaseLoadingCounter() { --loading_counter_; }
@@ -707,6 +745,7 @@
 }
 
 Document::~Document() {
+  html_element_context_->page_visibility_state()->RemoveObserver(this);
   // Ensure that all outstanding weak ptrs become invalid.
   // Some objects that will be released while this destructor runs may
   // have weak ptrs to |this|.
@@ -770,6 +809,18 @@
       ->DisableJit();
 }
 
+void Document::OnWindowFocusChanged(bool has_focus) {
+  UNREFERENCED_PARAMETER(has_focus);
+  // Ignored by this class.
+}
+
+void Document::OnVisibilityStateChanged(
+    page_visibility::VisibilityState visibility_state) {
+  UNREFERENCED_PARAMETER(visibility_state);
+  DispatchEvent(new Event(base::Tokens::visibilitychange(), Event::kBubbles,
+                          Event::kNotCancelable));
+}
+
 void Document::TraceMembers(script::Tracer* tracer) {
   Node::TraceMembers(tracer);
 
@@ -789,6 +840,43 @@
   tracer->Trace(initial_computed_style_declaration_);
 }
 
+void Document::QueuePointerEvent(const scoped_refptr<Event>& event) {
+  // Only accept this for event types that are MouseEvents or known derivatives.
+  SB_DCHECK(event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
+            event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
+            event->GetWrappableType() == base::GetTypeId<WheelEvent>());
+
+  // Queue the event to be handled on the next layout.
+  pointer_events_.push(event);
+}
+
+scoped_refptr<Event> Document::GetNextQueuedPointerEvent() {
+  scoped_refptr<Event> event;
+  if (pointer_events_.empty()) {
+    return event;
+  }
+
+  // Ignore pointer move events when they are succeeded by additional pointer
+  // move events.
+  bool next_event_is_move_event =
+      pointer_events_.front()->type() == base::Tokens::pointermove() ||
+      pointer_events_.front()->type() == base::Tokens::mousemove();
+  bool current_event_is_move_event;
+  do {
+    current_event_is_move_event = next_event_is_move_event;
+    event = pointer_events_.front();
+    pointer_events_.pop();
+    if (!current_event_is_move_event) {
+      break;
+    }
+    next_event_is_move_event =
+        !pointer_events_.empty() &&
+        (pointer_events_.front()->type() == base::Tokens::pointermove() ||
+         pointer_events_.front()->type() == base::Tokens::mousemove());
+  } while (next_event_is_move_event);
+  return event;
+}
+
 void Document::DispatchOnLoadEvent() {
   TRACE_EVENT0("cobalt::dom", "Document::DispatchOnLoadEvent()");
 
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index 04ef5c5..da896bf 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -17,6 +17,7 @@
 
 #include <deque>
 #include <map>
+#include <queue>
 #include <string>
 
 #include "base/callback.h"
@@ -40,6 +41,8 @@
 #include "cobalt/math/size.h"
 #include "cobalt/network_bridge/cookie_jar.h"
 #include "cobalt/network_bridge/net_poster.h"
+#include "cobalt/page_visibility/page_visibility_state.h"
+#include "cobalt/page_visibility/visibility_state.h"
 #include "cobalt/script/exception_state.h"
 #include "googleurl/src/gurl.h"
 
@@ -81,7 +84,9 @@
 // (the DOM tree, including elements such as <head> and <body>) and provides
 // functionality which is global to the document.
 //   https://www.w3.org/TR/dom/#document
-class Document : public Node, public cssom::MutationObserver {
+class Document : public Node,
+                 public cssom::MutationObserver,
+                 public page_visibility::PageVisibilityState::Observer {
  public:
   struct Options {
     Options()
@@ -186,6 +191,7 @@
   scoped_refptr<HTMLHeadElement> head() const;
 
   scoped_refptr<Element> active_element() const;
+  scoped_refptr<HTMLElement> indicated_element() const;
 
   const EventListenerScriptValue* onreadystatechange() const {
     return GetAttributeEventListener(base::Tokens::readystatechange());
@@ -212,7 +218,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<Document> AsDocument() OVERRIDE { return this; }
+  Document* AsDocument() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
@@ -252,10 +258,14 @@
   bool HasBrowsingContext() { return !!window_; }
 
   void set_window(Window* window) { window_ = window; }
+  const scoped_refptr<Window> window();
 
   // Sets the active element of the document.
   void SetActiveElement(Element* active_element);
 
+  // Sets the indicated element of the document.
+  void SetIndicatedElement(HTMLElement* indicated_element);
+
   // Count all ongoing loadings, including document itself and its dependent
   // resources, and dispatch OnLoad() if necessary.
   void IncreaseLoadingCounter();
@@ -347,13 +357,46 @@
   // Disable just-in-time compilation of JavaScript code.
   void DisableJit();
 
+  // Page Visibility fields.
+  bool hidden() const {
+    return visibility_state() == page_visibility::kVisibilityStateHidden;
+  }
+  page_visibility::VisibilityState visibility_state() const {
+    return page_visibility_state()->GetVisibilityState();
+  }
+  const EventListenerScriptValue* onvisibilitychange() const {
+    return GetAttributeEventListener(base::Tokens::visibilitychange());
+  }
+  void set_onvisibilitychange(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::visibilitychange(), event_listener);
+  }
+
+  // page_visibility::PageVisibilityState::Observer implementation.
+  void OnWindowFocusChanged(bool has_focus) OVERRIDE;
+  void OnVisibilityStateChanged(
+      page_visibility::VisibilityState visibility_state) OVERRIDE;
+
   void TraceMembers(script::Tracer* tracer) OVERRIDE;
 
+  // Queue up pointer related events.
+  void QueuePointerEvent(const scoped_refptr<Event>& event);
+
+  // Get the next queued pointer event.
+  scoped_refptr<Event> GetNextQueuedPointerEvent();
+
   DEFINE_WRAPPABLE_TYPE(Document);
 
  protected:
   ~Document() OVERRIDE;
 
+  page_visibility::PageVisibilityState* page_visibility_state() {
+    return html_element_context_->page_visibility_state();
+  }
+
+  const page_visibility::PageVisibilityState* page_visibility_state() const {
+    return html_element_context_->page_visibility_state();
+  }
+
  private:
   void DispatchOnLoadEvent();
 
@@ -405,6 +448,8 @@
 
   // Weak reference to the active element.
   base::WeakPtr<Element> active_element_;
+  // Weak reference to the indicated element.
+  base::WeakPtr<HTMLElement> indicated_element_;
   // List of document observers.
   ObserverList<DocumentObserver> observers_;
   // Selector Tree.
@@ -426,6 +471,8 @@
 
   // The max depth of elements that are guaranteed to be rendered.
   int dom_max_element_depth_;
+
+  std::queue<scoped_refptr<Event> > pointer_events_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/document.idl b/src/cobalt/dom/document.idl
index 45d27e1..a2e81ee 100644
--- a/src/cobalt/dom/document.idl
+++ b/src/cobalt/dom/document.idl
@@ -20,6 +20,7 @@
   readonly attribute DOMString documentURI;
   readonly attribute Element? documentElement;
 
+  // user interaction
   // Non-standard return type, should be WindowProxy.
   // https://www.w3.org/TR/html5/single-page.html#dom-document-defaultview
   readonly attribute Window? defaultView;
diff --git a/src/cobalt/dom/document_test.cc b/src/cobalt/dom/document_test.cc
index 8544240..b9e82b2 100644
--- a/src/cobalt/dom/document_test.cc
+++ b/src/cobalt/dom/document_test.cc
@@ -29,11 +29,15 @@
 #include "cobalt/dom/html_head_element.h"
 #include "cobalt/dom/html_html_element.h"
 #include "cobalt/dom/html_style_element.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/location.h"
+#include "cobalt/dom/message_event.h"
+#include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/node_list.h"
 #include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/testing/html_collection_testing.h"
 #include "cobalt/dom/text.h"
+#include "cobalt/dom/ui_event.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -65,7 +69,8 @@
       dom_stat_tracker_(new DomStatTracker("DocumentTest")),
       html_element_context_(NULL, css_parser_.get(), NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            dom_stat_tracker_.get(), "") {
+                            dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
 }
 
@@ -144,19 +149,105 @@
   EXPECT_EQ(document, comment->node_document());
 }
 
-TEST_F(DocumentTest, CreateEvent) {
+TEST_F(DocumentTest, CreateEventEvent) {
   StrictMock<MockExceptionState> exception_state;
   scoped_refptr<script::ScriptException> exception;
   scoped_refptr<Document> document = new Document(&html_element_context_);
+
   // Create an Event, the name is case insensitive.
   scoped_refptr<Event> event = document->CreateEvent("EvEnT", &exception_state);
-
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
 
+  event = document->CreateEvent("EvEnTs", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_FALSE(dynamic_cast<UIEvent*>(event.get()));
+  EXPECT_FALSE(dynamic_cast<KeyboardEvent*>(event.get()));
+  EXPECT_FALSE(dynamic_cast<MessageEvent*>(event.get()));
+  EXPECT_FALSE(dynamic_cast<MouseEvent*>(event.get()));
+
+  event = document->CreateEvent("HtMlEvEnTs", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+}
+
+TEST_F(DocumentTest, CreateEventUIEvent) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<script::ScriptException> exception;
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
+  // Create an Event, the name is case insensitive.
+  scoped_refptr<Event> event =
+      document->CreateEvent("UiEvEnT", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<UIEvent*>(event.get()));
+
+  event = document->CreateEvent("UiEvEnTs", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<UIEvent*>(event.get()));
+}
+
+TEST_F(DocumentTest, CreateEventKeyboardEvent) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<script::ScriptException> exception;
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
+  // Create an Event, the name is case insensitive.
+  scoped_refptr<Event> event =
+      document->CreateEvent("KeYbOaRdEvEnT", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<KeyboardEvent*>(event.get()));
+
+  event = document->CreateEvent("KeYeVeNtS", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<KeyboardEvent*>(event.get()));
+}
+
+TEST_F(DocumentTest, CreateEventMessageEvent) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<script::ScriptException> exception;
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
+  // Create an Event, the name is case insensitive.
+  scoped_refptr<Event> event =
+      document->CreateEvent("MeSsAgEeVeNt", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<MessageEvent*>(event.get()));
+}
+
+TEST_F(DocumentTest, CreateEventMouseEvent) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<script::ScriptException> exception;
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
+  // Create an Event, the name is case insensitive.
+  scoped_refptr<Event> event =
+      document->CreateEvent("MoUsEeVeNt", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<MouseEvent*>(event.get()));
+
+  event = document->CreateEvent("MoUsEeVeNtS", &exception_state);
+  EXPECT_TRUE(event);
+  EXPECT_FALSE(event->initialized_flag());
+  EXPECT_TRUE(base::polymorphic_downcast<MouseEvent*>(event.get()));
+}
+
+TEST_F(DocumentTest, CreateEventEventNotSupported) {
+  StrictMock<MockExceptionState> exception_state;
+  scoped_refptr<script::ScriptException> exception;
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
   EXPECT_CALL(exception_state, SetException(_))
       .WillOnce(SaveArg<0>(&exception));
-  event = document->CreateEvent("Event Not Supported", &exception_state);
+  scoped_refptr<Event> event =
+      document->CreateEvent("Event Not Supported", &exception_state);
 
   EXPECT_FALSE(event);
   ASSERT_TRUE(exception);
diff --git a/src/cobalt/dom/document_type.h b/src/cobalt/dom/document_type.h
index a9eebd3..f5d857f 100644
--- a/src/cobalt/dom/document_type.h
+++ b/src/cobalt/dom/document_type.h
@@ -47,7 +47,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<DocumentType> AsDocumentType() OVERRIDE { return this; }
+  DocumentType* AsDocumentType() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index a1d6fbb..e516263 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
@@ -40,8 +40,6 @@
         'buffer_source.h',
         'camera_3d.cc',
         'camera_3d.h',
-        'camera_3d_impl.cc',
-        'camera_3d_impl.h',
         'cdata_section.cc',
         'cdata_section.h',
         'character_data.cc',
@@ -65,6 +63,9 @@
         'css_transitions_adapter.h',
         'data_view.cc',
         'data_view.h',
+        'device_orientation_event.cc',
+        'device_orientation_event.h',
+        'device_orientation_event_init.h',
         'document.cc',
         'document.h',
         'document_timeline.cc',
@@ -178,6 +179,8 @@
         'message_event.h',
         'mime_type_array.cc',
         'mime_type_array.h',
+        'mouse_event.cc',
+        'mouse_event.h',
         'mutation_observer.cc',
         'mutation_observer.h',
         'mutation_observer_init.h',
@@ -206,6 +209,8 @@
         'performance_timing.h',
         'plugin_array.cc',
         'plugin_array.h',
+        'pointer_event.cc',
+        'pointer_event.h',
         'progress_event.cc',
         'progress_event.h',
         'pseudo_element.cc',
@@ -250,6 +255,8 @@
         'url_utils.h',
         'video_track.h',
         'video_track_list.h',
+        'wheel_event.cc',
+        'wheel_event.h',
         'window.cc',
         'window.h',
         'window_timers.cc',
@@ -268,6 +275,7 @@
         '<(DEPTH)/cobalt/media_session/media_session.gyp:media_session',
         # Interface layer to avoid directly depending on network.
         '<(DEPTH)/cobalt/network_bridge/network_bridge.gyp:network_bridge',
+        '<(DEPTH)/cobalt/page_visibility/page_visibility.gyp:page_visibility',
         '<(DEPTH)/cobalt/script/script.gyp:script',
         '<(DEPTH)/cobalt/storage/storage.gyp:storage',
         '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
@@ -333,10 +341,10 @@
       'hard_dependency': 1,
       'export_dependent_settings': [
         # Additionally, ensure that the include directories for generated
-        # headers are put on the include directories for targets that depend
-        # on this one.
+        # headers are put on the include directories for targets that depend on
+        # this one.
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
-      ]
+      ],
     },
 
     {
diff --git a/src/cobalt/dom/dom_exception.gyp b/src/cobalt/dom/dom_exception.gyp
index ba6dcf6..cb2d09d 100644
--- a/src/cobalt/dom/dom_exception.gyp
+++ b/src/cobalt/dom/dom_exception.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/dom/dom_parser_test.cc b/src/cobalt/dom/dom_parser_test.cc
index 21d7302..273fdd6 100644
--- a/src/cobalt/dom/dom_parser_test.cc
+++ b/src/cobalt/dom/dom_parser_test.cc
@@ -50,7 +50,8 @@
           NULL /* animated_image_tracker */, NULL /* image_cache */,
           NULL /* reduced_image_cache_capacity_manager */,
           NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
-          NULL /* dom_stat_tracker */, "" /* language */),
+          NULL /* dom_stat_tracker */, "" /* language */,
+          base::kApplicationStateStarted),
       dom_parser_(new DOMParser(&html_element_context_)) {}
 
 TEST_F(DOMParserTest, ParsesXML) {
diff --git a/src/cobalt/dom/dom_stat_tracker.cc b/src/cobalt/dom/dom_stat_tracker.cc
index cadf09e..e2e1219 100644
--- a/src/cobalt/dom/dom_stat_tracker.cc
+++ b/src/cobalt/dom/dom_stat_tracker.cc
@@ -20,17 +20,28 @@
 namespace dom {
 
 DomStatTracker::DomStatTracker(const std::string& name)
-    : total_html_elements_(
-          StringPrintf("Count.%s.DOM.HtmlElement", name.c_str()), 0,
+    : html_elements_count_(
+          StringPrintf("Count.%s.DOM.HtmlElement.Total", name.c_str()), 0,
           "Total number of HTML elements."),
+      document_html_elements_count_(
+          StringPrintf("Count.%s.DOM.HtmlElement.Document", name.c_str()), 0,
+          "Number of HTML elements in the document."),
       is_event_active_(false),
       event_video_start_delay_stop_watch_(kStopWatchTypeEventVideoStartDelay,
                                           base::StopWatch::kAutoStartOff, this),
       event_video_start_delay_(
           StringPrintf("Event.Duration.%s.DOM.VideoStartDelay", name.c_str()),
           base::TimeDelta(), "Total delay between event and video starting."),
+      script_element_execute_count_(
+          StringPrintf("Count.%s.DOM.HtmlScriptElement.Execute", name.c_str()),
+          0, "Count of HTML script element execute calls."),
+      script_element_execute_time_(
+          StringPrintf("Time.%s.DOM.HtmlScriptElement.Execute", name.c_str()),
+          0, "Time of the last HTML script element execute."),
       html_elements_created_count_(0),
       html_elements_destroyed_count_(0),
+      html_elements_inserted_into_document_count_(0),
+      html_elements_removed_from_document_count_(0),
       update_matching_rules_count_(0),
       update_computed_style_count_(0),
       generate_html_element_computed_style_count_(0),
@@ -41,8 +52,10 @@
 DomStatTracker::~DomStatTracker() {
   FlushPeriodicTracking();
 
-  // Verify that all of the elements were destroyed.
-  DCHECK_EQ(total_html_elements_, 0);
+  // Verify that all of the elements were removed from the document and
+  // destroyed.
+  DCHECK_EQ(html_elements_count_, 0);
+  DCHECK_EQ(document_html_elements_count_, 0);
 
   event_video_start_delay_stop_watch_.Stop();
 }
@@ -84,12 +97,25 @@
   }
 }
 
+void DomStatTracker::OnHtmlScriptElementExecuted() {
+  ++script_element_execute_count_;
+  script_element_execute_time_ = base::TimeTicks::Now().ToInternalValue();
+}
+
 void DomStatTracker::OnHtmlElementCreated() { ++html_elements_created_count_; }
 
 void DomStatTracker::OnHtmlElementDestroyed() {
   ++html_elements_destroyed_count_;
 }
 
+void DomStatTracker::OnHtmlElementInsertedIntoDocument() {
+  ++html_elements_inserted_into_document_count_;
+}
+
+void DomStatTracker::OnHtmlElementRemovedFromDocument() {
+  ++html_elements_removed_from_document_count_;
+}
+
 void DomStatTracker::OnUpdateMatchingRules() { ++update_matching_rules_count_; }
 
 void DomStatTracker::OnUpdateComputedStyle() { ++update_computed_style_count_; }
@@ -117,12 +143,16 @@
 
 void DomStatTracker::FlushPeriodicTracking() {
   // Update the CVals before clearing the periodic values.
-  total_html_elements_ +=
+  html_elements_count_ +=
       html_elements_created_count_ - html_elements_destroyed_count_;
+  document_html_elements_count_ += html_elements_inserted_into_document_count_ -
+                                   html_elements_removed_from_document_count_;
 
   // Now clear the values.
   html_elements_created_count_ = 0;
   html_elements_destroyed_count_ = 0;
+  html_elements_inserted_into_document_count_ = 0;
+  html_elements_removed_from_document_count_ = 0;
   update_matching_rules_count_ = 0;
   update_computed_style_count_ = 0;
   generate_html_element_computed_style_count_ = 0;
diff --git a/src/cobalt/dom/dom_stat_tracker.h b/src/cobalt/dom/dom_stat_tracker.h
index 6e34ab2..35cedb7 100644
--- a/src/cobalt/dom/dom_stat_tracker.h
+++ b/src/cobalt/dom/dom_stat_tracker.h
@@ -42,16 +42,22 @@
   void OnEndEvent();
 
   void OnHtmlVideoElementPlaying();
+  void OnHtmlScriptElementExecuted();
 
   // Periodic count-related
   void OnHtmlElementCreated();
   void OnHtmlElementDestroyed();
+  void OnHtmlElementInsertedIntoDocument();
+  void OnHtmlElementRemovedFromDocument();
   void OnUpdateMatchingRules();
   void OnUpdateComputedStyle();
   void OnGenerateHtmlElementComputedStyle();
   void OnGeneratePseudoElementComputedStyle();
 
-  int total_html_elements() const { return total_html_elements_; }
+  int html_elements_count() const { return html_elements_count_; }
+  int document_html_elements_count() const {
+    return document_html_elements_count_;
+  }
 
   int html_elements_created_count() const {
     return html_elements_created_count_;
@@ -59,6 +65,12 @@
   int html_elements_destroyed_count() const {
     return html_elements_destroyed_count_;
   }
+  int html_elements_added_to_document_count() const {
+    return html_elements_inserted_into_document_count_;
+  }
+  int html_elements_removed_from_document_count() const {
+    return html_elements_removed_from_document_count_;
+  }
   int update_matching_rules_count() const {
     return update_matching_rules_count_;
   }
@@ -84,7 +96,8 @@
   void FlushPeriodicTracking();
 
   // Count cvals that are updated when the periodic tracking is flushed.
-  base::CVal<int, base::CValPublic> total_html_elements_;
+  base::CVal<int, base::CValPublic> html_elements_count_;
+  base::CVal<int, base::CValPublic> document_html_elements_count_;
 
   // Event-related
   bool is_event_active_;
@@ -93,10 +106,16 @@
   base::StopWatch event_video_start_delay_stop_watch_;
   base::CVal<base::TimeDelta> event_video_start_delay_;
 
+  // Count of HtmlScriptElement::Execute() calls and time of last call.
+  base::CVal<int> script_element_execute_count_;
+  base::CVal<int64> script_element_execute_time_;
+
   // Periodic counts. The counts are cleared after the CVals are updated in
   // |FlushPeriodicTracking|.
   int html_elements_created_count_;
   int html_elements_destroyed_count_;
+  int html_elements_inserted_into_document_count_;
+  int html_elements_removed_from_document_count_;
   int update_matching_rules_count_;
   int update_computed_style_count_;
   int generate_html_element_computed_style_count_;
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 9954088..2c41b83 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/dom/element.cc b/src/cobalt/dom/element.cc
index 521069e..d961ebc 100644
--- a/src/cobalt/dom/element.cc
+++ b/src/cobalt/dom/element.cc
@@ -512,6 +512,7 @@
 void Element::set_outer_html(const std::string& outer_html,
                              script::ExceptionState* exception_state) {
   TRACK_MEMORY_SCOPE("DOM");
+
   // 1. Let parent be the context object's parent.
   scoped_refptr<Node> parent = parent_node();
 
@@ -537,6 +538,9 @@
   // parent.
   // Remove this node from its parent.
   scoped_refptr<Node> reference = next_sibling();
+
+  // Make sure that this does not get cleaned up while it is being removed.
+  scoped_refptr<Node> keep_this_alive = this;
   parent->RemoveChild(this);
 
   // Use the DOM parser to parse the HTML input and generate children nodes.
diff --git a/src/cobalt/dom/element.h b/src/cobalt/dom/element.h
index 01901c9..337d467 100644
--- a/src/cobalt/dom/element.h
+++ b/src/cobalt/dom/element.h
@@ -117,7 +117,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<Element> AsElement() OVERRIDE { return this; }
+  Element* AsElement() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
diff --git a/src/cobalt/dom/element_test.cc b/src/cobalt/dom/element_test.cc
index 5d1f52f..6f3941f 100644
--- a/src/cobalt/dom/element_test.cc
+++ b/src/cobalt/dom/element_test.cc
@@ -62,7 +62,8 @@
       dom_stat_tracker_(new DomStatTracker("ElementTest")),
       html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                            NULL, NULL, dom_stat_tracker_.get(), "") {
+                            NULL, NULL, dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
   document_ = new Document(&html_element_context_);
   xml_document_ = new XMLDocument(&html_element_context_);
diff --git a/src/cobalt/dom/event.cc b/src/cobalt/dom/event.cc
index a3623e9..64c1d77 100644
--- a/src/cobalt/dom/event.cc
+++ b/src/cobalt/dom/event.cc
@@ -21,40 +21,31 @@
 namespace cobalt {
 namespace dom {
 
-Event::Event(UninitializedFlag uninitialized_flag)
+Event::Event(UninitializedFlag /* uninitialized_flag */)
     : event_phase_(kNone),
       time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
-  UNREFERENCED_PARAMETER(uninitialized_flag);
   InitEventInternal(base::Token(), false, false);
 }
 
-Event::Event(base::Token type)
-    : event_phase_(kNone),
-      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
-  InitEventInternal(type, false, false);
-}
-
 Event::Event(const std::string& type)
     : event_phase_(kNone),
       time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
   InitEventInternal(base::Token(type), false, false);
 }
 
-Event::Event(base::Token type, const EventInit& eventInitDict)
+Event::Event(const std::string& type, const EventInit& init_dict)
     : event_phase_(kNone),
       time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
-  SB_DCHECK(eventInitDict.has_bubbles());
-  SB_DCHECK(eventInitDict.has_cancelable());
-  InitEventInternal(type, eventInitDict.bubbles(), eventInitDict.cancelable());
+  SB_DCHECK(init_dict.has_bubbles());
+  SB_DCHECK(init_dict.has_cancelable());
+  InitEventInternal(base::Token(type), init_dict.bubbles(),
+                    init_dict.cancelable());
 }
 
-Event::Event(const std::string& type, const EventInit& eventInitDict)
+Event::Event(base::Token type)
     : event_phase_(kNone),
       time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
-  SB_DCHECK(eventInitDict.has_bubbles());
-  SB_DCHECK(eventInitDict.has_cancelable());
-  InitEventInternal(base::Token(type), eventInitDict.bubbles(),
-                    eventInitDict.cancelable());
+  InitEventInternal(type, false, false);
 }
 
 Event::Event(base::Token type, Bubbles bubbles, Cancelable cancelable)
@@ -63,6 +54,14 @@
   InitEventInternal(type, bubbles == kBubbles, cancelable == kCancelable);
 }
 
+Event::Event(base::Token type, const EventInit& init_dict)
+    : event_phase_(kNone),
+      time_stamp_(static_cast<uint64>(base::Time::Now().ToJsTime())) {
+  SB_DCHECK(init_dict.has_bubbles());
+  SB_DCHECK(init_dict.has_cancelable());
+  InitEventInternal(type, init_dict.bubbles(), init_dict.cancelable());
+}
+
 Event::~Event() {
   // This needs to be in the .cc file because EventTarget is only forward
   // declared in the .h file, and the implicit destructor for target_ cannot
diff --git a/src/cobalt/dom/event.h b/src/cobalt/dom/event.h
index 090066d..62c00ac 100644
--- a/src/cobalt/dom/event.h
+++ b/src/cobalt/dom/event.h
@@ -54,11 +54,11 @@
   explicit Event(UninitializedFlag uninitialized_flag);
 
   // Creates an event that cannot be bubbled and cancelled.
-  explicit Event(base::Token type);
   explicit Event(const std::string& type);
-  Event(base::Token type, const EventInit& eventInitDict);
-  Event(const std::string& type, const EventInit& eventInitDict);
+  explicit Event(base::Token type);
+  Event(const std::string& type, const EventInit& init_dict);
   Event(base::Token type, Bubbles bubbles, Cancelable cancelable);
+  Event(base::Token type, const EventInit& init_dict);
 
   ~Event() OVERRIDE;
 
diff --git a/src/cobalt/dom/event.idl b/src/cobalt/dom/event.idl
index cd863e1..35e6829 100644
--- a/src/cobalt/dom/event.idl
+++ b/src/cobalt/dom/event.idl
@@ -36,7 +36,9 @@
 
   readonly attribute DOMTimeStamp timeStamp;
 
-  void initEvent(DOMString type, boolean bubbles, boolean cancelable);
+  void initEvent(DOMString type,
+                 optional boolean bubbles = false,
+                 optional boolean cancelable = false);
 };
 
 typedef unsigned long long DOMTimeStamp;
diff --git a/src/cobalt/dom/event_modifier_init.idl b/src/cobalt/dom/event_modifier_init.idl
new file mode 100644
index 0000000..08e0461
--- /dev/null
+++ b/src/cobalt/dom/event_modifier_init.idl
@@ -0,0 +1,34 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2016/WD-uievents-20160804/#dictdef-eventmodifierinit
+// https://www.w3.org/TR/uievents/#event-modifier-initializers
+
+dictionary EventModifierInit : UIEventInit {
+  boolean ctrlKey = false;
+  boolean shiftKey = false;
+  boolean altKey = false;
+  boolean metaKey = false;
+
+  boolean modifierAltGraph = false;
+  boolean modifierCapsLock = false;
+  boolean modifierFn = false;
+  boolean modifierFnLock = false;
+  boolean modifierHyper = false;
+  boolean modifierNumLock = false;
+  boolean modifierScrollLock = false;
+  boolean modifierSuper = false;
+  boolean modifierSymbol = false;
+  boolean modifierSymbolLock = false;
+};
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc
index b923be1..ca6593c 100644
--- a/src/cobalt/dom/event_target.cc
+++ b/src/cobalt/dom/event_target.cc
@@ -83,10 +83,18 @@
     return false;
   }
 
+  // The event is now being dispatched. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   event->set_target(this);
   event->set_event_phase(Event::kAtTarget);
   FireEventOnListeners(event);
   event->set_event_phase(Event::kNone);
+
+  // The event has completed being dispatched. Stop tracking it in the global
+  // stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
+
   return !event->default_prevented();
 }
 
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index a31a414..9af3d72 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -90,6 +90,13 @@
     SetAttributeEventListener(base::Tokens::blur(), event_listener);
   }
 
+  const EventListenerScriptValue* onclick() {
+    return GetAttributeEventListener(base::Tokens::click());
+  }
+  void set_onclick(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::click(), event_listener);
+  }
+
   const EventListenerScriptValue* onerror() {
     return GetAttributeEventListener(base::Tokens::error());
   }
@@ -104,6 +111,27 @@
     SetAttributeEventListener(base::Tokens::focus(), event_listener);
   }
 
+  const EventListenerScriptValue* onkeydown() {
+    return GetAttributeEventListener(base::Tokens::keydown());
+  }
+  void set_onkeydown(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::keydown(), event_listener);
+  }
+
+  const EventListenerScriptValue* onkeypress() {
+    return GetAttributeEventListener(base::Tokens::keypress());
+  }
+  void set_onkeypress(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::keypress(), event_listener);
+  }
+
+  const EventListenerScriptValue* onkeyup() {
+    return GetAttributeEventListener(base::Tokens::keyup());
+  }
+  void set_onkeyup(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::keyup(), event_listener);
+  }
+
   const EventListenerScriptValue* onload() {
     return GetAttributeEventListener(base::Tokens::load());
   }
@@ -111,6 +139,181 @@
     SetAttributeEventListener(base::Tokens::load(), event_listener);
   }
 
+  const EventListenerScriptValue* onloadeddata() {
+    return GetAttributeEventListener(base::Tokens::loadeddata());
+  }
+  void set_onloadeddata(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::loadeddata(), event_listener);
+  }
+
+  const EventListenerScriptValue* onloadedmetadata() {
+    return GetAttributeEventListener(base::Tokens::loadedmetadata());
+  }
+  void set_onloadedmetadata(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::loadedmetadata(), event_listener);
+  }
+
+  const EventListenerScriptValue* onloadstart() {
+    return GetAttributeEventListener(base::Tokens::loadstart());
+  }
+  void set_onloadstart(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::loadstart(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmousedown() {
+    return GetAttributeEventListener(base::Tokens::mousedown());
+  }
+  void set_onmousedown(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mousedown(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmouseenter() {
+    return GetAttributeEventListener(base::Tokens::mouseenter());
+  }
+  void set_onmouseenter(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mouseenter(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmouseleave() {
+    return GetAttributeEventListener(base::Tokens::mouseleave());
+  }
+  void set_onmouseleave(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mouseleave(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmousemove() {
+    return GetAttributeEventListener(base::Tokens::mousemove());
+  }
+  void set_onmousemove(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mousemove(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmouseout() {
+    return GetAttributeEventListener(base::Tokens::mouseout());
+  }
+  void set_onmouseout(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mouseout(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmouseover() {
+    return GetAttributeEventListener(base::Tokens::mouseover());
+  }
+  void set_onmouseover(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mouseover(), event_listener);
+  }
+
+  const EventListenerScriptValue* onmouseup() {
+    return GetAttributeEventListener(base::Tokens::mouseup());
+  }
+  void set_onmouseup(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::mouseup(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpause() {
+    return GetAttributeEventListener(base::Tokens::pause());
+  }
+  void set_onpause(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pause(), event_listener);
+  }
+
+  const EventListenerScriptValue* onplay() {
+    return GetAttributeEventListener(base::Tokens::play());
+  }
+  void set_onplay(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::play(), event_listener);
+  }
+
+  const EventListenerScriptValue* onplaying() {
+    return GetAttributeEventListener(base::Tokens::playing());
+  }
+  void set_onplaying(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::playing(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerdown() {
+    return GetAttributeEventListener(base::Tokens::pointerdown());
+  }
+  void set_onpointerdown(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerdown(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerenter() {
+    return GetAttributeEventListener(base::Tokens::pointerenter());
+  }
+  void set_onpointerenter(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerenter(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerleave() {
+    return GetAttributeEventListener(base::Tokens::pointerleave());
+  }
+  void set_onpointerleave(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerleave(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointermove() {
+    return GetAttributeEventListener(base::Tokens::pointermove());
+  }
+  void set_onpointermove(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointermove(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerout() {
+    return GetAttributeEventListener(base::Tokens::pointerout());
+  }
+  void set_onpointerout(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerout(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerover() {
+    return GetAttributeEventListener(base::Tokens::pointerover());
+  }
+  void set_onpointerover(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerover(), event_listener);
+  }
+
+  const EventListenerScriptValue* onpointerup() {
+    return GetAttributeEventListener(base::Tokens::pointerup());
+  }
+  void set_onpointerup(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::pointerup(), event_listener);
+  }
+
+  const EventListenerScriptValue* onprogress() {
+    return GetAttributeEventListener(base::Tokens::progress());
+  }
+  void set_onprogress(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::progress(), event_listener);
+  }
+
+  const EventListenerScriptValue* onratechange() {
+    return GetAttributeEventListener(base::Tokens::ratechange());
+  }
+  void set_onratechange(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::ratechange(), event_listener);
+  }
+
+  const EventListenerScriptValue* onseeked() {
+    return GetAttributeEventListener(base::Tokens::seeked());
+  }
+  void set_onseeked(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::seeked(), event_listener);
+  }
+
+  const EventListenerScriptValue* onseeking() {
+    return GetAttributeEventListener(base::Tokens::seeking());
+  }
+  void set_onseeking(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::seeking(), event_listener);
+  }
+
+  const EventListenerScriptValue* ontimeupdate() {
+    return GetAttributeEventListener(base::Tokens::timeupdate());
+  }
+  void set_ontimeupdate(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::timeupdate(), event_listener);
+  }
+
   const EventListenerScriptValue* onunload() {
     return GetAttributeEventListener(base::Tokens::unload());
   }
@@ -118,6 +321,27 @@
     SetAttributeEventListener(base::Tokens::unload(), event_listener);
   }
 
+  const EventListenerScriptValue* onvolumechange() {
+    return GetAttributeEventListener(base::Tokens::volumechange());
+  }
+  void set_onvolumechange(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::volumechange(), event_listener);
+  }
+
+  const EventListenerScriptValue* onwaiting() {
+    return GetAttributeEventListener(base::Tokens::waiting());
+  }
+  void set_onwaiting(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::waiting(), event_listener);
+  }
+
+  const EventListenerScriptValue* onwheel() {
+    return GetAttributeEventListener(base::Tokens::wheel());
+  }
+  void set_onwheel(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::wheel(), event_listener);
+  }
+
   // Set an event listener assigned as an attribute. Overwrite the existing one
   // if there is any.
   void SetAttributeEventListener(base::Token type,
@@ -137,14 +361,13 @@
 
   void TraceMembers(script::Tracer* tracer) OVERRIDE;
 
-  DEFINE_WRAPPABLE_TYPE(EventTarget);
-
- protected:
   // This function sends the event to the event listeners attached to the
   // current event target. It takes stop immediate propagation flag into
   // account. The caller should set the phase and target.
   void FireEventOnListeners(const scoped_refptr<Event>& event);
 
+  DEFINE_WRAPPABLE_TYPE(EventTarget);
+
  private:
   struct EventListenerInfo {
     EventListenerInfo(base::Token type, EventTarget* const event_target,
diff --git a/src/cobalt/dom/focus_event.cc b/src/cobalt/dom/focus_event.cc
index cacd086..b876182 100644
--- a/src/cobalt/dom/focus_event.cc
+++ b/src/cobalt/dom/focus_event.cc
@@ -19,14 +19,11 @@
 
 FocusEvent::FocusEvent(const std::string& type)
     : UIEvent(type), related_target_(NULL) {}
-
-FocusEvent::FocusEvent(base::Token type,
-                       const scoped_refptr<EventTarget>& related_target)
-    : UIEvent(type), related_target_(related_target) {}
-
 FocusEvent::FocusEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                       const scoped_refptr<Window>& view,
                        const scoped_refptr<EventTarget>& related_target)
-    : UIEvent(type, bubbles, cancelable), related_target_(related_target) {}
+    : UIEvent(type, bubbles, cancelable, view),
+      related_target_(related_target) {}
 
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/focus_event.h b/src/cobalt/dom/focus_event.h
index 57ba8a3..78cf3be 100644
--- a/src/cobalt/dom/focus_event.h
+++ b/src/cobalt/dom/focus_event.h
@@ -31,10 +31,8 @@
  public:
   explicit FocusEvent(const std::string& type);
 
-  FocusEvent(base::Token type,
-             const scoped_refptr<EventTarget>& related_target);
-
   FocusEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+             const scoped_refptr<Window>& view,
              const scoped_refptr<EventTarget>& related_target);
 
   // Web API: FocusEvent
diff --git a/src/cobalt/dom/focus_event_init.idl b/src/cobalt/dom/focus_event_init.idl
new file mode 100644
index 0000000..e38c47f
--- /dev/null
+++ b/src/cobalt/dom/focus_event_init.idl
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2016/WD-uievents-20160804/#dictdef-focuseventinit
+
+dictionary FocusEventInit : UIEventInit {
+  EventTarget? relatedTarget = null;
+};
diff --git a/src/cobalt/dom/global_event_handlers.idl b/src/cobalt/dom/global_event_handlers.idl
index 73f4b95..cdacb6b 100644
--- a/src/cobalt/dom/global_event_handlers.idl
+++ b/src/cobalt/dom/global_event_handlers.idl
@@ -17,9 +17,49 @@
 [NoInterfaceObject]
 interface GlobalEventHandlers {
   attribute EventHandler onblur;
+  attribute EventHandler onclick;
   attribute EventHandler onerror;
   attribute EventHandler onfocus;
+
+  attribute EventHandler onkeydown;
+  attribute EventHandler onkeypress;
+  attribute EventHandler onkeyup;
+
   attribute EventHandler onload;
+  attribute EventHandler onloadeddata;
+  attribute EventHandler onloadedmetadata;
+  attribute EventHandler onloadstart;
+
+  attribute EventHandler onmousedown;
+  attribute EventHandler onmouseenter;
+  attribute EventHandler onmouseleave;
+  attribute EventHandler onmousemove;
+  attribute EventHandler onmouseout;
+  attribute EventHandler onmouseover;
+  attribute EventHandler onmouseup;
+
+  attribute EventHandler onpause;
+  attribute EventHandler onplay;
+  attribute EventHandler onplaying;
+
+  // Extensions for the Pointer Events recommendation.
+  //  https://www.w3.org/TR/2015/REC-pointerevents-20150224/#extensions-to-the-globaleventhandlers-interface
+  attribute EventHandler onpointerdown;
+  attribute EventHandler onpointerenter;
+  attribute EventHandler onpointerleave;
+  attribute EventHandler onpointermove;
+  attribute EventHandler onpointerout;
+  attribute EventHandler onpointerover;
+  attribute EventHandler onpointerup;
+
+  attribute EventHandler onprogress;
+  attribute EventHandler onratechange;
+  attribute EventHandler onseeked;
+  attribute EventHandler onseeking;
+  attribute EventHandler ontimeupdate;
+  attribute EventHandler onvolumechange;
+  attribute EventHandler onwaiting;
+  attribute EventHandler onwheel;
 };
 
 // TODO: The IDL defines EventHandler as a nullable callback
diff --git a/src/cobalt/dom/global_stats.cc b/src/cobalt/dom/global_stats.cc
index 8dc8f94..b2de28e 100644
--- a/src/cobalt/dom/global_stats.cc
+++ b/src/cobalt/dom/global_stats.cc
@@ -42,9 +42,9 @@
                  "Total number of currently active nodes."),
       num_node_lists_("Count.DOM.NodeLists", 0,
                       "Total number of currently active node lists."),
-      num_active_dispatch_events_(
-          "Count.DOM.ActiveDispatchEvents", 0,
-          "Total number of currently active dispatch events."),
+      num_active_java_script_events_(
+          "Count.DOM.ActiveJavaScriptEvents", 0,
+          "Total number of currently active JavaScript events."),
       num_xhrs_("Count.XHR", 0, "Total number of currently active XHRs."),
       xhr_memory_("Memory.XHR", 0, "Memory allocated by XHRs in bytes.") {}
 
@@ -55,7 +55,8 @@
          num_dom_token_lists_ == 0 && num_event_listeners_ == 0 &&
          num_html_collections_ == 0 && num_named_node_maps_ == 0 &&
          num_nodes_ == 0 && num_node_lists_ == 0 &&
-         num_active_dispatch_events_ == 0 && num_xhrs_ == 0 && xhr_memory_ == 0;
+         num_active_java_script_events_ == 0 && num_xhrs_ == 0 &&
+         xhr_memory_ == 0;
 }
 
 void GlobalStats::Add(Attr* /*object*/) { ++num_attrs_; }
@@ -92,9 +93,9 @@
 
 void GlobalStats::RemoveEventListener() { --num_event_listeners_; }
 
-void GlobalStats::StartDispatchEvent() { ++num_active_dispatch_events_; }
+void GlobalStats::StartJavaScriptEvent() { ++num_active_java_script_events_; }
 
-void GlobalStats::StopDispatchEvent() { --num_active_dispatch_events_; }
+void GlobalStats::StopJavaScriptEvent() { --num_active_java_script_events_; }
 
 void GlobalStats::Add(xhr::XMLHttpRequest* /* object */) { ++num_xhrs_; }
 
diff --git a/src/cobalt/dom/global_stats.h b/src/cobalt/dom/global_stats.h
index 75d4feb..413fc90 100644
--- a/src/cobalt/dom/global_stats.h
+++ b/src/cobalt/dom/global_stats.h
@@ -62,8 +62,8 @@
   int GetNumEventListeners() const { return num_event_listeners_; }
   int GetNumNodes() const { return num_nodes_; }
 
-  void StartDispatchEvent();
-  void StopDispatchEvent();
+  void StartJavaScriptEvent();
+  void StopJavaScriptEvent();
 
   void Add(xhr::XMLHttpRequest* object);
   void Remove(xhr::XMLHttpRequest* object);
@@ -84,7 +84,7 @@
   base::CVal<int, base::CValPublic> num_nodes_;
   base::CVal<int> num_node_lists_;
 
-  base::CVal<int> num_active_dispatch_events_;
+  base::CVal<int> num_active_java_script_events_;
 
   // XHR-related tracking
   base::CVal<int> num_xhrs_;
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 93a6f9f..215a8be 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -328,7 +328,7 @@
 
 // Algorithm for offsetParent:
 //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-htmlelement-offsetparent
-scoped_refptr<Element> HTMLElement::offset_parent() {
+Element* HTMLElement::offset_parent() {
   DCHECK(node_document());
   node_document()->DoSynchronousLayout();
 
@@ -341,7 +341,7 @@
   if (!layout_boxes_ || IsRootElement() || AsHTMLBodyElement() ||
       !computed_style() ||
       computed_style()->position() == cssom::KeywordValue::GetFixed()) {
-    return scoped_refptr<Element>();
+    return NULL;
   }
 
   // 2. Return the nearest ancestor element of the element for which at least
@@ -349,14 +349,13 @@
   //    ancestor is found:
   //    . The computed value of the 'position' property is not 'static'.
   //    . It is the HTML body element.
-  for (scoped_refptr<Node> ancestor_node = parent_node(); ancestor_node;
+  for (Node* ancestor_node = parent_node(); ancestor_node;
        ancestor_node = ancestor_node->parent_node()) {
-    scoped_refptr<Element> ancestor_element = ancestor_node->AsElement();
+    Element* ancestor_element = ancestor_node->AsElement();
     if (!ancestor_element) {
       continue;
     }
-    scoped_refptr<HTMLElement> ancestor_html_element =
-        ancestor_element->AsHTMLElement();
+    HTMLElement* ancestor_html_element = ancestor_element->AsHTMLElement();
     if (!ancestor_html_element) {
       continue;
     }
@@ -369,7 +368,7 @@
   }
 
   // 3. Return null.
-  return scoped_refptr<Element>();
+  return NULL;
 }
 
 // Algorithm for offset_top:
@@ -600,7 +599,7 @@
   // are updated.
   old_matching_rules_.swap(matching_rules_);
 
-  matching_rules_->clear();
+  matching_rules_.clear();
   rule_matching_state_.matching_nodes.clear();
   rule_matching_state_.descendant_potential_nodes.clear();
   rule_matching_state_.following_sibling_potential_nodes.clear();
@@ -739,8 +738,6 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(
           animations_adapter_(new DOMAnimatable(this))),
       css_animations_(&animations_adapter_),
-      old_matching_rules_(new cssom::RulesWithCascadePrecedence()),
-      matching_rules_(new cssom::RulesWithCascadePrecedence()),
       matching_rules_valid_(false) {
   css_computed_style_declaration_->set_animations(animations());
   style_->set_mutation_observer(this);
@@ -750,7 +747,11 @@
 
 HTMLElement::~HTMLElement() {
   --(non_trivial_static_fields.Get().html_element_count_log.count);
+  if (IsInDocument()) {
+    dom_stat_tracker_->OnHtmlElementRemovedFromDocument();
+  }
   dom_stat_tracker_->OnHtmlElementDestroyed();
+
   style_->set_mutation_observer(NULL);
 }
 
@@ -758,10 +759,14 @@
   directionality_ = other.directionality_;
 }
 
-void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
+void HTMLElement::OnInsertedIntoDocument() {
+  Node::OnInsertedIntoDocument();
+  dom_stat_tracker_->OnHtmlElementInsertedIntoDocument();
+}
 
 void HTMLElement::OnRemovedFromDocument() {
   Node::OnRemovedFromDocument();
+  dom_stat_tracker_->OnHtmlElementRemovedFromDocument();
 
   // When an element that is focused stops being a focusable element, or stops
   // being focused without another element being explicitly focused in its
@@ -779,6 +784,8 @@
   }
 }
 
+void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
+
 void HTMLElement::OnSetAttribute(const std::string& name,
                                  const std::string& value) {
   if (name == "class" || name == "id") {
@@ -856,7 +863,8 @@
   // before focus is shifted, and does bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focusin
   DispatchEvent(new FocusEvent(base::Tokens::focusin(), Event::kBubbles,
-                               Event::kNotCancelable, this));
+                               Event::kNotCancelable, document->window(),
+                               this));
 
   // 3. Make the element the currently focused element in its top-level browsing
   // context.
@@ -871,7 +879,8 @@
   // focus is shifted, and does not bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focus
   DispatchEvent(new FocusEvent(base::Tokens::focus(), Event::kNotBubbles,
-                               Event::kNotCancelable, this));
+                               Event::kNotCancelable, document->window(),
+                               this));
 
   // Custom, not in any sepc.
   InvalidateMatchingRulesRecursively();
@@ -888,11 +897,12 @@
   // focus. This event type is similar to blur, but is dispatched before focus
   // is shifted, and does bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focusout
+  Document* document = node_document();
+  scoped_refptr<Window> window(document ? document->window() : NULL);
   DispatchEvent(new FocusEvent(base::Tokens::focusout(), Event::kBubbles,
-                               Event::kNotCancelable, this));
+                               Event::kNotCancelable, window, this));
 
   // 2. Unfocus the element.
-  Document* document = node_document();
   if (document && document->active_element() == this->AsElement()) {
     document->SetActiveElement(NULL);
   }
@@ -904,7 +914,8 @@
   // focus is shifted, and does not bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-blur
   DispatchEvent(new FocusEvent(base::Tokens::blur(), Event::kNotBubbles,
-                               Event::kNotCancelable, this));
+                               Event::kNotCancelable, document->window(),
+                               this));
 
   // Custom, not in any sepc.
   InvalidateMatchingRulesRecursively();
@@ -1113,7 +1124,7 @@
 
     // Check for whether the matching rules have changed. If they have, then a
     // new computed style must be generated from them.
-    if (!generate_computed_style && *old_matching_rules_ != *matching_rules_) {
+    if (!generate_computed_style && old_matching_rules_ != matching_rules_) {
       generate_computed_style = true;
     }
   }
@@ -1222,6 +1233,22 @@
   computed_style_valid_ = true;
 }
 
+bool HTMLElement::IsDesignated() {
+  Document* document = node_document();
+  if (document) {
+    scoped_refptr<Element> element = document->indicated_element();
+    while (element) {
+      if (element == this) {
+        return true;
+      }
+      // The parent of an element that is :hover is also in that state.
+      //  https://www.w3.org/TR/selectors4/#hover-pseudo
+      element = element->parent_element();
+    }
+  }
+  return false;
+}
+
 void HTMLElement::ClearActiveBackgroundImages() {
   if (html_element_context() &&
       html_element_context()->animated_image_tracker()) {
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index 42c0146..4698fed 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -131,7 +131,7 @@
   // Web API: CSSOM View Module: Extensions to the HTMLElement Interface
   // (partial interface)
   //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#extensions-to-the-htmlelement-interface
-  scoped_refptr<Element> offset_parent();
+  Element* offset_parent();
   float offset_top();
   float offset_left();
   float offset_width();
@@ -181,7 +181,7 @@
   //
   // Returns the cached matching rules of this element.
   cssom::RulesWithCascadePrecedence* matching_rules() {
-    return matching_rules_.get();
+    return &matching_rules_;
   }
   // Returns the rule matching state of this element.
   RuleMatchingState* rule_matching_state() { return &rule_matching_state_; }
@@ -263,12 +263,19 @@
 
   bool matching_rules_valid() const { return matching_rules_valid_; }
 
+  // Returns whether the element has been designated.
+  //   https://www.w3.org/TR/selectors4/#hover-pseudo
+  bool IsDesignated();
+
   DEFINE_WRAPPABLE_TYPE(HTMLElement);
 
  protected:
   HTMLElement(Document* document, base::Token local_name);
   ~HTMLElement() OVERRIDE;
 
+  void OnInsertedIntoDocument() OVERRIDE;
+  void OnRemovedFromDocument() OVERRIDE;
+
   void CopyDirectionality(const HTMLElement& other);
 
   // HTMLElement keeps a pointer to the dom stat tracker to ensure that it can
@@ -279,7 +286,6 @@
  private:
   // From Node.
   void OnMutation() OVERRIDE;
-  void OnRemovedFromDocument() OVERRIDE;
 
   // From Element.
   void OnSetAttribute(const std::string& name,
@@ -349,8 +355,8 @@
   cssom::AnimationSet css_animations_;
 
   // The following fields are used in rule matching.
-  scoped_ptr<cssom::RulesWithCascadePrecedence> old_matching_rules_;
-  scoped_ptr<cssom::RulesWithCascadePrecedence> matching_rules_;
+  cssom::RulesWithCascadePrecedence old_matching_rules_;
+  cssom::RulesWithCascadePrecedence matching_rules_;
   RuleMatchingState rule_matching_state_;
 
   // This contains information about the boxes generated from the element.
diff --git a/src/cobalt/dom/html_element_context.cc b/src/cobalt/dom/html_element_context.cc
index 064403a..d9c502d 100644
--- a/src/cobalt/dom/html_element_context.cc
+++ b/src/cobalt/dom/html_element_context.cc
@@ -55,7 +55,9 @@
         reduced_image_cache_capacity_manager,
     loader::font::RemoteTypefaceCache* remote_typeface_cache,
     loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
-    const std::string& language, float video_playback_rate_multiplier)
+    const std::string& language,
+    base::ApplicationState initial_application_state,
+    float video_playback_rate_multiplier)
     : fetcher_factory_(fetcher_factory),
       css_parser_(css_parser),
       dom_parser_(dom_parser),
@@ -73,6 +75,7 @@
       mesh_cache_(mesh_cache),
       dom_stat_tracker_(dom_stat_tracker),
       language_(language),
+      page_visibility_state_(initial_application_state),
       video_playback_rate_multiplier_(video_playback_rate_multiplier),
       sync_load_thread_("Synchronous Load"),
       html_element_factory_(new HTMLElementFactory()) {
diff --git a/src/cobalt/dom/html_element_context.h b/src/cobalt/dom/html_element_context.h
index 2eb952f..970bc32 100644
--- a/src/cobalt/dom/html_element_context.h
+++ b/src/cobalt/dom/html_element_context.h
@@ -19,6 +19,7 @@
 
 #include "base/memory/scoped_ptr.h"
 #include "base/threading/thread.h"
+#include "cobalt/base/application_state.h"
 #include "cobalt/cssom/css_parser.h"
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/parser.h"
@@ -30,6 +31,7 @@
 #include "cobalt/loader/mesh/mesh_cache.h"
 #include "cobalt/media/can_play_type_handler.h"
 #include "cobalt/media/web_media_player_factory.h"
+#include "cobalt/page_visibility/page_visibility_state.h"
 #include "cobalt/script/script_runner.h"
 #include "cobalt/script/script_value_factory.h"
 
@@ -61,7 +63,9 @@
           reduced_image_cache_capacity_manager,
       loader::font::RemoteTypefaceCache* remote_typeface_cache,
       loader::mesh::MeshCache* mesh_cache, DomStatTracker* dom_stat_tracker,
-      const std::string& language, float video_playback_rate_multiplier = 1.0);
+      const std::string& language,
+      base::ApplicationState initial_application_state,
+      float video_playback_rate_multiplier = 1.0);
   ~HTMLElementContext();
 
   loader::FetcherFactory* fetcher_factory() { return fetcher_factory_; }
@@ -122,6 +126,10 @@
     return reduced_image_cache_capacity_manager_;
   }
 
+  page_visibility::PageVisibilityState* page_visibility_state() {
+    return &page_visibility_state_;
+  }
+
  private:
   loader::FetcherFactory* const fetcher_factory_;
   cssom::CSSParser* const css_parser_;
@@ -140,6 +148,7 @@
   loader::mesh::MeshCache* const mesh_cache_;
   DomStatTracker* const dom_stat_tracker_;
   const std::string language_;
+  page_visibility::PageVisibilityState page_visibility_state_;
   const float video_playback_rate_multiplier_;
 
   base::Thread sync_load_thread_;
diff --git a/src/cobalt/dom/html_element_factory_test.cc b/src/cobalt/dom/html_element_factory_test.cc
index 0b27f6f..c8047e8 100644
--- a/src/cobalt/dom/html_element_factory_test.cc
+++ b/src/cobalt/dom/html_element_factory_test.cc
@@ -60,7 +60,8 @@
             NULL /* image_cache */,
             NULL /* reduced_image_cache_capacity_manager */,
             NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
-            dom_stat_tracker_.get(), "" /* language */),
+            dom_stat_tracker_.get(), "" /* language */,
+            base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)) {}
   ~HTMLElementFactoryTest() OVERRIDE {}
 
diff --git a/src/cobalt/dom/html_element_test.cc b/src/cobalt/dom/html_element_test.cc
index 239d247..6bbf621 100644
--- a/src/cobalt/dom/html_element_test.cc
+++ b/src/cobalt/dom/html_element_test.cc
@@ -114,7 +114,8 @@
       : dom_stat_tracker_(new DomStatTracker("HTMLElementTest")),
         html_element_context_(NULL, &css_parser_, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              dom_stat_tracker_.get(), ""),
+                              dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)) {}
   ~HTMLElementTest() OVERRIDE {}
 
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 7f7cc7e..dd2e52b 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -134,13 +134,13 @@
       pending_load_(false),
       sent_stalled_event_(false),
       sent_end_event_(false) {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::HTMLMediaElement");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::HTMLMediaElement()");
   MLOG();
   html_media_element_count_log.Get().count++;
 }
 
 HTMLMediaElement::~HTMLMediaElement() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::~HTMLMediaElement");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::~HTMLMediaElement()");
   MLOG();
   ClearMediaSource();
   html_media_element_count_log.Get().count--;
@@ -157,7 +157,7 @@
 }
 
 void HTMLMediaElement::set_src(const std::string& src) {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::set_src");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::set_src()");
   MLOG() << src;
   SetAttribute("src", src);
   ClearMediaPlayer();
@@ -192,7 +192,7 @@
 }
 
 void HTMLMediaElement::Load() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Load");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Load()");
   // LoadInternal may result in a 'beforeload' event, which can make arbitrary
   // DOM mutations.
   scoped_refptr<HTMLMediaElement> protect(this);
@@ -243,6 +243,8 @@
 // See https://www.w3.org/TR/encrypted-media/#dom-htmlmediaelement-setmediakeys.
 scoped_ptr<HTMLMediaElement::VoidPromiseValue> HTMLMediaElement::SetMediaKeys(
     const scoped_refptr<eme::MediaKeys>& media_keys) {
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::SetMediaKeys()");
+
   scoped_ptr<VoidPromiseValue> promise = node_document()
                                              ->html_element_context()
                                              ->script_value_factory()
@@ -575,7 +577,7 @@
 }
 
 void HTMLMediaElement::Play() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Play");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Play()");
   MLOG();
   // 4.8.10.9. Playing the media resource
   if (!player_ || network_state_ == kNetworkEmpty) {
@@ -602,7 +604,7 @@
 }
 
 void HTMLMediaElement::Pause() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Pause");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::Pause()");
   MLOG();
   // 4.8.10.9. Playing the media resource
   if (!player_ || network_state_ == kNetworkEmpty) {
@@ -699,13 +701,13 @@
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
 void HTMLMediaElement::ScheduleEvent(const scoped_refptr<Event>& event) {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ScheduleEvent");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ScheduleEvent()");
   MLOG() << event->type();
   event_queue_.Enqueue(event);
 }
 
 void HTMLMediaElement::CreateMediaPlayer() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::CreateMediaPlayer");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::CreateMediaPlayer()");
   MLOG();
   if (src().empty()) {
     reduced_image_cache_capacity_request_ = base::nullopt;
@@ -747,7 +749,7 @@
 }
 
 void HTMLMediaElement::ScheduleLoad() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ScheduleLoad");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ScheduleLoad()");
   if (!pending_load_) {
     PrepareForLoad();
     pending_load_ = true;
@@ -760,7 +762,7 @@
 }
 
 void HTMLMediaElement::PrepareForLoad() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::PrepareForLoad");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::PrepareForLoad()");
   // Perform the cleanup required for the resource load algorithm to run.
   StopPeriodicTimers();
   load_timer_.Stop();
@@ -825,7 +827,7 @@
 }
 
 void HTMLMediaElement::LoadInternal() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::LoadInternal");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::LoadInternal()");
   DCHECK(node_document());
 
   // Select media resource.
@@ -956,7 +958,7 @@
 }
 
 void HTMLMediaElement::ClearMediaPlayer() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ClearMediaPlayer");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ClearMediaPlayer()");
   MLOG();
 
   ClearMediaSource();
@@ -1023,7 +1025,7 @@
 // This is kept as a one shot timer to be in sync with the original code. It
 // should be replaced by PostTask if this is any future rewrite.
 void HTMLMediaElement::OnLoadTimer() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::OnLoadTimer");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::OnLoadTimer()");
   scoped_refptr<HTMLMediaElement> protect(this);
 
   if (pending_load_) {
@@ -1125,6 +1127,8 @@
 }
 
 void HTMLMediaElement::SetReadyState(WebMediaPlayer::ReadyState state) {
+  TRACE_EVENT1("cobalt::dom", "HTMLMediaElement::SetReadyState()", "state",
+               state);
   // Set "was_potentially_playing" BEFORE updating ready_state_,
   // PotentiallyPlaying() uses it
   bool was_potentially_playing = PotentiallyPlaying();
@@ -1168,7 +1172,6 @@
 
   if (ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata &&
       old_state < WebMediaPlayer::kReadyStateHaveMetadata) {
-    PlayerOutputModeUpdated();
     duration_ = player_->GetDuration();
     ScheduleOwnEvent(base::Tokens::durationchange());
     ScheduleOwnEvent(base::Tokens::loadedmetadata());
@@ -1587,6 +1590,15 @@
   EndProcessingMediaPlayerCallback();
 }
 
+void HTMLMediaElement::OutputModeChanged() {
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::OutputModeChanged()");
+  // If the player mode is updated, trigger a re-layout so that we can setup
+  // the video render tree differently depending on whether we are in punch-out
+  // or decode-to-texture.
+  node_document()->OnDOMMutation();
+  InvalidateLayoutBoxesOfNodeAndAncestors();
+}
+
 void HTMLMediaElement::PlaybackStateChanged() {
   if (!player_) {
     return;
@@ -1607,7 +1619,7 @@
 
 #if defined(COBALT_MEDIA_SOURCE_2016)
 void HTMLMediaElement::SourceOpened(media::ChunkDemuxer* chunk_demuxer) {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::SourceOpened");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::SourceOpened()");
   BeginProcessingMediaPlayerCallback();
   DCHECK(media_source_);
   media_source_->SetChunkDemuxerAndOpen(chunk_demuxer);
@@ -1626,7 +1638,7 @@
 }
 
 bool HTMLMediaElement::PreferDecodeToTexture() {
-  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::PreferDecodeToTexture");
+  TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::PreferDecodeToTexture()");
 
 #if defined(ENABLE_MAP_TO_MESH)
   if (!node_document()->UpdateComputedStyleOnElementAndAncestor(this)) {
@@ -1788,13 +1800,5 @@
 }
 #endif  // !defined(COBALT_MEDIA_SOURCE_2016)
 
-void HTMLMediaElement::PlayerOutputModeUpdated() {
-  // If the player mode is updated, trigger a re-layout so that we can setup
-  // the video render tree differently depending on whether we are in punch-out
-  // or decode-to-texture.
-  node_document()->OnDOMMutation();
-  InvalidateLayoutBoxesOfNodeAndAncestors();
-}
-
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h
index a45eb4e..b3d1f0d 100644
--- a/src/cobalt/dom/html_media_element.h
+++ b/src/cobalt/dom/html_media_element.h
@@ -233,6 +233,7 @@
   void ReadyStateChanged() OVERRIDE;
   void TimeChanged() OVERRIDE;
   void DurationChanged() OVERRIDE;
+  void OutputModeChanged() OVERRIDE;
   void PlaybackStateChanged() OVERRIDE;
   void SawUnsupportedTracks() OVERRIDE;
   float Volume() const OVERRIDE;
@@ -264,10 +265,6 @@
   void SetSourceState(MediaSourceReadyState ready_state);
 #endif  // !defined(COBALT_MEDIA_SOURCE_2016)
 
-  // Called whenever the player's output mode (e.g. punch-out,
-  // decode-to-texture) is updated.
-  void PlayerOutputModeUpdated();
-
   scoped_ptr<WebMediaPlayer> player_;
 
   std::string current_src_;
diff --git a/src/cobalt/dom/html_meta_element.cc b/src/cobalt/dom/html_meta_element.cc
index 9e2ea63..f55b4e4 100644
--- a/src/cobalt/dom/html_meta_element.cc
+++ b/src/cobalt/dom/html_meta_element.cc
@@ -55,8 +55,7 @@
 }
 
 bool HTMLMetaElement::IsDescendantOfHeadElement() const {
-  for (scoped_refptr<Node> node = parent_node(); node;
-       node = node->parent_node()) {
+  for (Node* node = parent_node(); node; node = node->parent_node()) {
     if (node->AsElement() && node->AsElement()->AsHTMLElement() &&
         node->AsElement()->AsHTMLElement()->AsHTMLHeadElement())
       return true;
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc
index c7a3362..e140ed6 100644
--- a/src/cobalt/dom/html_script_element.cc
+++ b/src/cobalt/dom/html_script_element.cc
@@ -23,6 +23,7 @@
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
+#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/sync_loader.h"
@@ -506,6 +507,9 @@
     return;
   }
 
+  // The script is now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   TRACE_EVENT2("cobalt::dom", "HTMLScriptElement::Execute()", "file_path",
                script_location.file_path, "line_number",
                script_location.line_number);
@@ -537,6 +541,12 @@
     PreventGarbageCollectionAndPostToDispatchEvent(
         FROM_HERE, base::Tokens::readystatechange());
   }
+
+  // The script is done running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
+
+  // Notify the DomStatTracker of the execution.
+  dom_stat_tracker_->OnHtmlScriptElementExecuted();
 }
 
 void HTMLScriptElement::PreventGarbageCollectionAndPostToDispatchEvent(
diff --git a/src/cobalt/dom/keyboard_event.cc b/src/cobalt/dom/keyboard_event.cc
index 4af5b2b..8b3356e 100644
--- a/src/cobalt/dom/keyboard_event.cc
+++ b/src/cobalt/dom/keyboard_event.cc
@@ -24,65 +24,115 @@
 namespace cobalt {
 namespace dom {
 
-namespace {
-base::Token TypeEnumToToken(KeyboardEvent::Type type) {
-  switch (type) {
-    case KeyboardEvent::kTypeKeyDown:
-      return base::Tokens::keydown();
-    case KeyboardEvent::kTypeKeyUp:
-      return base::Tokens::keyup();
-    case KeyboardEvent::kTypeKeyPress:
-      return base::Tokens::keypress();
-    default:
-      NOTREACHED() << "Invalid KeyboardEvent::Type";
-      return base::Tokens::keydown();
+KeyboardEvent::KeyboardEvent(const std::string& type)
+    : UIEventWithKeyState(base::Token(type), kBubbles, kCancelable, NULL),
+      key_location_(kDomKeyLocationStandard),
+      key_code_(0),
+      char_code_(0),
+      repeat_(false) {}
+
+// TODO: Initialize from init_dict.key() or init_dict.code() when not empty.
+KeyboardEvent::KeyboardEvent(const std::string& type,
+                             const KeyboardEventInit& init_dict)
+    : UIEventWithKeyState(base::Token(type), kBubbles, kCancelable,
+                          init_dict.view(), init_dict),
+      key_location_(static_cast<KeyLocationCode>(init_dict.location())),
+      key_code_(init_dict.key_code()),
+      char_code_(init_dict.char_code()),
+      repeat_(init_dict.repeat()) {}
+
+KeyboardEvent::KeyboardEvent(base::Token type,
+                             const scoped_refptr<Window>& view,
+                             const KeyboardEventInit& init_dict)
+    : UIEventWithKeyState(type, kBubbles, kCancelable, view, init_dict),
+      key_location_(static_cast<KeyLocationCode>(init_dict.location())),
+      key_code_(init_dict.key_code()),
+      char_code_(init_dict.char_code()),
+      repeat_(init_dict.repeat()) {}
+
+KeyboardEvent::KeyboardEvent(UninitializedFlag uninitialized_flag)
+    : UIEventWithKeyState(uninitialized_flag),
+      key_code_(0),
+      char_code_(0),
+      repeat_(false) {}
+
+void KeyboardEvent::InitKeyboardEvent(const std::string& type, bool bubbles,
+                                      bool cancelable,
+                                      const scoped_refptr<Window>& view,
+                                      const std::string& key, uint32 location,
+                                      const std::string& modifierslist,
+                                      bool repeat) {
+  InitUIEventWithKeyState(type, bubbles, cancelable, view, 0, modifierslist);
+  key_location_ = static_cast<KeyLocationCode>(location);
+  repeat_ = repeat;
+
+  // TODO: Implement full keyCode determination according to 'Legacy key models'
+  // from: https://www.w3.org/TR/2016/WD-uievents-20160804/#legacy-key-models
+
+  // Parse keys from the "Fixed virtual key codes" table from:
+  // https://www.w3.org/TR/2016/WD-uievents-20160804/#fixed-virtual-key-codes
+  if (key == "Backspace") {
+    key_code_ = keycode::kBack;
+  } else if (key == "Tab") {
+    key_code_ = keycode::kTab;
+  } else if (key == "Enter") {
+    key_code_ = keycode::kReturn;
+  } else if (key == "Shift") {
+    key_code_ = keycode::kShift;
+  } else if (key == "Control") {
+    key_code_ = keycode::kControl;
+  } else if (key == "Alt") {
+    key_code_ = keycode::kMenu;
+  } else if (key == "CapsLock") {
+    key_code_ = keycode::kCapital;
+  } else if (key == "Escape") {
+    key_code_ = keycode::kEscape;
+  } else if (key == "Space") {
+    key_code_ = keycode::kSpace;
+  } else if (key == "PageUp") {
+    key_code_ = keycode::kPrior;
+  } else if (key == "PageDown") {
+    key_code_ = keycode::kNext;
+  } else if (key == "End") {
+    key_code_ = keycode::kEnd;
+  } else if (key == "Home") {
+    key_code_ = keycode::kHome;
+  } else if (key == "ArrowLeft") {
+    key_code_ = keycode::kLeft;
+  } else if (key == "ArrowUp") {
+    key_code_ = keycode::kUp;
+  } else if (key == "ArrowRight") {
+    key_code_ = keycode::kRight;
+  } else if (key == "ArrowDown") {
+    key_code_ = keycode::kDown;
+  } else if (key == "Delete") {
+    key_code_ = keycode::kDelete;
   }
 }
-}  // namespace
-
-KeyboardEvent::KeyboardEvent(const std::string& type)
-    : UIEventWithKeyState(base::Token(type), kBubbles, kCancelable, 0) {}
-
-KeyboardEvent::KeyboardEvent(const Data& data)
-    : UIEventWithKeyState(TypeEnumToToken(data.type), kBubbles, kCancelable,
-                          data.modifiers),
-      data_(data) {}
-
-KeyboardEvent::KeyboardEvent(Type type, KeyLocationCode location,
-                             unsigned int modifiers, int key_code,
-                             int char_code, bool is_repeat)
-    : UIEventWithKeyState(TypeEnumToToken(type), kBubbles, kCancelable,
-                          modifiers),
-      data_(type, location, modifiers, key_code, char_code, is_repeat) {}
 
 // How to determine keycode:
-//   https://www.w3.org/TR/DOM-Level-3-Events/#determine-keydown-keyup-keyCode
+//   https://www.w3.org/TR/2016/WD-uievents-20160804/#determine-keydown-keyup-keyCode
 // Virtual key code for keyup/keydown, 0 for keypress (split model)
-int KeyboardEvent::key_code() const {
+uint32 KeyboardEvent::key_code() const {
   if (type() == base::Tokens::keydown() || type() == base::Tokens::keyup()) {
-    return data_.key_code;
+    return key_code_;
   }
 
   return 0;
 }
 
-int KeyboardEvent::char_code() const {
-  return type() == base::Tokens::keypress() ? data_.char_code : 0;
+uint32 KeyboardEvent::char_code() const {
+  return type() == base::Tokens::keypress() ? char_code_ : 0;
 }
 
-std::string KeyboardEvent::key() const {
-  // First check if the event corresponds to a printable character.
-  // If so, just return a string containing that single character.
-  int char_code = ComputeCharCode(data_.key_code, modifiers());
-  if (char_code > 0 && char_code <= 127) {
-    return std::string(1, static_cast<char>(char_code));
-  }
+uint32 KeyboardEvent::which() const { return key_code(); }
 
+std::string KeyboardEvent::NonPrintableKey(int32_t key_code) const {
   // Otherwise, we have one of the non-printable characters.
   // Definitions taken from:
   //   https://www.w3.org/TR/DOM-Level-3-Events-key/
   //   https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key.
-  switch (data_.key_code) {
+  switch (key_code) {
     case keycode::kBack:
       return "Backspace";
     case keycode::kTab:
@@ -305,9 +355,111 @@
   }
 }
 
+std::string KeyboardEvent::key() const {
+  // First check if the event corresponds to a printable character.
+  // If so, just return a string containing that single character.
+  int char_code = ComputeCharCode(key_code_, shift_key());
+  if (char_code > 0 && char_code <= 127) {
+    return std::string(1, static_cast<char>(char_code));
+  }
+
+  return NonPrintableKey(key_code_);
+}
+
+std::string KeyboardEvent::code() const {
+  // First check if the event corresponds to a named key in the tables here:
+  // https://www.w3.org/TR/uievents-code/#code-value-tables
+
+  // First check if the event corresponds to a alphanumeric key.
+  if (key_code_ >= keycode::kA && key_code_ <= keycode::kZ) {
+    std::string key_code = "Key";
+    key_code.append(1, static_cast<char>(key_code_));
+    return key_code;
+  }
+
+  // Check if the event corresponds to a digit key.
+  if (key_code_ >= keycode::k0 && key_code_ <= keycode::k9) {
+    std::string key_code = "Digit";
+    key_code.append(1, static_cast<char>(key_code_));
+    return key_code;
+  }
+
+  // Check if the event corresponds to a numpad digit key.
+  //  https://www.w3.org/TR/uievents-code/#key-numpad-section
+  if (key_code_ >= keycode::kNumpad0 && key_code_ <= keycode::kDivide) {
+    std::string key_code = "Numpad";
+    char numpad_digit = '0' + static_cast<char>(key_code_ - keycode::kNumpad0);
+    key_code.append(1, numpad_digit);
+    return key_code;
+  }
+
+  // Check if the event corresponds to a remaining named key
+  switch (key_code_) {
+    case keycode::kSpace:
+      return "Space";
+    case keycode::kMultiply:
+      return "NumpadMultiply";
+    case keycode::kAdd:
+      return "NumpadAdd";
+    case keycode::kSeparator:
+      return "NumpadComma";
+    case keycode::kSubtract:
+      return "NumpadSubtract";
+    case keycode::kDecimal:
+      return "NumpadDecimal";
+    case keycode::kDivide:
+      return "NumpadDivide";
+    case keycode::kOem1:
+      return "Semicolon";
+    case keycode::kOemPlus:
+      return "Equal";
+    case keycode::kOemComma:
+      return "Comma";
+    case keycode::kOemMinus:
+      return "Minus";
+    case keycode::kOemPeriod:
+      return "Period";
+    case keycode::kOem2:
+      return "Slash";
+    case keycode::kOem3:
+      return "Backquote";
+    case keycode::kOem4:
+      return "BracketLeft";
+    case keycode::kOem5:
+      return "Backslash";
+    case keycode::kOem6:
+      return "BracketRight";
+    case keycode::kOem7:
+      return "Quote";
+    case keycode::kLwin:
+      return "MetaLeft";
+    case keycode::kRwin:
+      return "MetaRight";
+    case keycode::kLshift:
+      return "ShiftLeft";
+    case keycode::kRshift:
+      return "ShiftRight";
+    case keycode::kLcontrol:
+      return "ControlLeft";
+    case keycode::kRcontrol:
+      return "ControlRight";
+    case keycode::kLmenu:
+      return "AltLeft";
+    case keycode::kRmenu:
+      return "AltRight";
+    default:
+      // Nothing.
+      break;
+  }
+
+  // The event corresponds to a nonprintable key with a name that matches the
+  // named value for the key.
+  return NonPrintableKey(key_code_);
+}
+
 // Static.
-int32 KeyboardEvent::ComputeCharCode(int32 key_code, uint32 modifiers) {
-  if (modifiers & UIEventWithKeyState::kShiftKey) {
+int32 KeyboardEvent::ComputeCharCode(int32 key_code, bool shift_key) {
+  if (shift_key) {
     return KeyCodeToCharCodeWithShift(key_code);
   } else {
     return KeyCodeToCharCodeNoShift(key_code);
@@ -426,10 +578,12 @@
     case keycode::kLshift:
     case keycode::kLcontrol:
     case keycode::kLmenu:
+    case keycode::kLwin:
       return kDomKeyLocationLeft;
     case keycode::kRshift:
     case keycode::kRcontrol:
     case keycode::kRmenu:
+    case keycode::kRwin:
       return kDomKeyLocationRight;
     default:
       return kDomKeyLocationStandard;
diff --git a/src/cobalt/dom/keyboard_event.h b/src/cobalt/dom/keyboard_event.h
index 0b8b232..a0c5433 100644
--- a/src/cobalt/dom/keyboard_event.h
+++ b/src/cobalt/dom/keyboard_event.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "base/memory/scoped_ptr.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/ui_event_with_key_state.h"
 
 namespace cobalt {
@@ -26,16 +27,9 @@
 // The KeyboardEvent provides specific contextual information associated with
 // keyboard devices. Each keyboard event references a key using a value.
 // Keyboard events are commonly directed at the element that has the focus.
-//   https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents
+//   https://www.w3.org/TR/2016/WD-uievents-20160804/#events-keyboardevents
 class KeyboardEvent : public UIEventWithKeyState {
  public:
-  // Non-standard, used to make creating KeyboardEvents via C++ easier.
-  enum Type {
-    kTypeKeyDown,
-    kTypeKeyUp,
-    kTypeKeyPress,
-  };
-
   // Web API: KeyboardEvent
   //
   enum KeyLocationCode {
@@ -44,51 +38,40 @@
     kDomKeyLocationRight = 0x02,
     kDomKeyLocationNumpad = 0x03,
   };
-
-  struct Data {
-    Data()
-        : type(kTypeKeyDown),
-          key_location(kDomKeyLocationStandard),
-          modifiers(0),
-          key_code(0),
-          char_code(0),
-          repeat(false) {}
-
-    Data(Type type, KeyLocationCode key_location, uint32_t modifiers,
-         int32_t key_code, int32_t char_code, bool repeat)
-        : type(type),
-          key_location(key_location),
-          modifiers(modifiers),
-          key_code(key_code),
-          char_code(char_code),
-          repeat(repeat) {}
-
-    Type type;
-    KeyLocationCode key_location;
-    uint32_t modifiers;
-    int32_t key_code;
-    int32_t char_code;
-    bool repeat;
-  };
-
   explicit KeyboardEvent(const std::string& type);
-  explicit KeyboardEvent(const Data& data);
-  KeyboardEvent(Type type, KeyLocationCode location, unsigned int modifiers,
-                int key_code, int char_code, bool is_repeat);
+  KeyboardEvent(const std::string& type, const KeyboardEventInit& init_dict);
+  KeyboardEvent(base::Token type, const scoped_refptr<Window>& view,
+                const KeyboardEventInit& init_dict);
+
+  // Creates an event with its "initialized flag" unset.
+  explicit KeyboardEvent(UninitializedFlag uninitialized_flag);
+
+  void InitKeyboardEvent(const std::string& type, bool bubbles, bool cancelable,
+                         const scoped_refptr<Window>& view,
+                         const std::string& key, uint32 location,
+                         const std::string& modifierslist, bool repeat);
 
   // Returns a string describing the key event, as defined here:
   //   https://www.w3.org/TR/DOM-Level-3-Events-key/
   std::string key() const;
 
-  KeyLocationCode location() const { return data_.key_location; }
-  bool repeat() const { return data_.repeat; }
+  // Return a string describing the physical key pressed, not affected by
+  // current keyboard layout or modifier state, as defined here:
+  //   https://www.w3.org/TR/uievents-code/
+  std::string code() const;
+
+  KeyLocationCode location() const { return key_location_; }
+  bool repeat() const { return repeat_; }
+  bool is_composing() const { return false; }
 
   // Non-standard and deprecated.
   // key code for keydown and keyup, character for keypress
-  //   https://www.w3.org/TR/DOM-Level-3-Events/#legacy-key-models
-  int key_code() const;
-  int char_code() const;
-  KeyLocationCode key_location() const { return data_.key_location; }
+  //   https://www.w3.org/TR/2016/WD-uievents-20160804/#legacy-key-models
+  uint32 key_code() const;
+  uint32 char_code() const;
+  uint32 which() const;
+
+  KeyLocationCode key_location() const { return key_location_; }
 
   // keyIdentifier is deprecated and non-standard.
   // Here, we just map it to the standardized key() method, which matches some,
@@ -97,7 +80,7 @@
 
   // Custom, not in any spec.
   // Utility functions for keycode/charcode conversion.
-  static int32 ComputeCharCode(int32 key_code, uint32 modifiers);
+  static int32 ComputeCharCode(int32 key_code, bool shift_key);
   static int KeyCodeToCharCodeWithShift(int key_code);
   static int KeyCodeToCharCodeNoShift(int key_code);
   static KeyLocationCode KeyCodeToKeyLocation(int key_code);
@@ -106,8 +89,12 @@
 
  private:
   ~KeyboardEvent() OVERRIDE {}
+  std::string NonPrintableKey(int32_t key_code) const;
 
-  const Data data_;
+  KeyLocationCode key_location_;
+  uint32_t key_code_;
+  uint32_t char_code_;
+  bool repeat_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/keyboard_event.idl b/src/cobalt/dom/keyboard_event.idl
index 73607f6..e0b6220 100644
--- a/src/cobalt/dom/keyboard_event.idl
+++ b/src/cobalt/dom/keyboard_event.idl
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/2015/WD-uievents-20151215/#interface-KeyboardEvent
+// https://www.w3.org/TR/2016/WD-uievents-20160804/#interface-keyboardevent
 
-[Constructor(DOMString type)]
+[Constructor(DOMString type, optional KeyboardEventInit eventInitDict)]
 interface KeyboardEvent : UIEvent {
   // enum KeyLocationCode
   const unsigned long DOM_KEY_LOCATION_STANDARD = 0x00;
@@ -23,19 +23,33 @@
   const unsigned long DOM_KEY_LOCATION_NUMPAD = 0x03;
 
   readonly attribute DOMString key;
+  readonly attribute DOMString code;
   readonly attribute unsigned long location;
+
   readonly attribute boolean ctrlKey;
   readonly attribute boolean shiftKey;
   readonly attribute boolean altKey;
   readonly attribute boolean metaKey;
+
   readonly attribute boolean repeat;
+  readonly attribute boolean isComposing;
 
   boolean getModifierState(DOMString text);
 
   // Deprecated. These features have been removed from the Web standards.
-  //   https://www.w3.org/TR/DOM-Level-3-Events/#legacy-key-models
-  readonly attribute long keyCode;
-  readonly attribute long charCode;
+  //   https://www.w3.org/TR/2016/WD-uievents-20160804/#legacy-key-models
+  readonly attribute unsigned long keyCode;
+  readonly attribute unsigned long charCode;
+  readonly attribute unsigned long which;
   readonly attribute unsigned long keyLocation;
   readonly attribute DOMString keyIdentifier;
+
+  void initKeyboardEvent(DOMString type,
+                         optional boolean bubbles = false,
+                         optional boolean cancelable = false,
+                         optional Window? view = null,
+                         optional DOMString key = "",
+                         optional unsigned long location = 0,
+                         optional DOMString modifiersList = "",
+                         optional boolean repeat = false);
 };
diff --git a/src/cobalt/dom/keyboard_event_init.idl b/src/cobalt/dom/keyboard_event_init.idl
new file mode 100644
index 0000000..1d6252f
--- /dev/null
+++ b/src/cobalt/dom/keyboard_event_init.idl
@@ -0,0 +1,28 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/uievents/#dictdef-keyboardeventinit
+// https://www.w3.org/TR/2016/WD-uievents-20160804/#legacy-dictionary-KeyboardEventInit
+
+dictionary KeyboardEventInit : EventModifierInit {
+  DOMString key = "";
+  DOMString code = "";
+  unsigned long location = 0;
+  boolean repeat = false;
+  boolean isComposing = false;
+
+  unsigned long charCode = 0;
+  unsigned long keyCode = 0;
+  unsigned long which = 0;
+};
diff --git a/src/cobalt/dom/keyboard_event_test.cc b/src/cobalt/dom/keyboard_event_test.cc
index a9794b8..cece5e1 100644
--- a/src/cobalt/dom/keyboard_event_test.cc
+++ b/src/cobalt/dom/keyboard_event_test.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/keyboard_event.h"
 
 #include "cobalt/base/tokens.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/keycode.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -22,132 +23,171 @@
 namespace dom {
 
 TEST(KeyboardEventTest, ShouldHaveBubblesAndCancelableSet) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent("keydown");
   EXPECT_TRUE(keyboard_event->bubbles());
   EXPECT_TRUE(keyboard_event->cancelable());
 }
 
 TEST(KeyboardEventTest, CanGetKeyLocation) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event->key_location(),
             KeyboardEvent::kDomKeyLocationStandard);
 
-  scoped_refptr<KeyboardEvent> keyboard_event_l = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationLeft,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  init.set_location(KeyboardEvent::kDomKeyLocationLeft);
+  scoped_refptr<KeyboardEvent> keyboard_event_l =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event_l->key_location(),
             KeyboardEvent::kDomKeyLocationLeft);
 
-  scoped_refptr<KeyboardEvent> keyboard_event_r = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationRight,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  init.set_location(KeyboardEvent::kDomKeyLocationRight);
+  scoped_refptr<KeyboardEvent> keyboard_event_r =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event_r->key_location(),
             KeyboardEvent::kDomKeyLocationRight);
 }
 
-TEST(KeyboardEventTest, CanGetKeyIdentifier) {
-  scoped_refptr<KeyboardEvent> keyboard_event_a = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, keycode::kA, 0, false);
+TEST(KeyboardEventTest, CanGetKeyIdentifierAndKeyAndCode) {
+  KeyboardEventInit init;
+  init.set_key_code(keycode::kA);
+  scoped_refptr<KeyboardEvent> keyboard_event_a =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event_a->key_identifier(), "a");
+  EXPECT_EQ(keyboard_event_a->key(), "a");
+  EXPECT_EQ(keyboard_event_a->code(), "KeyA");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_ca = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kCtrlKey, keycode::kA, 0, false);
+  init.set_key_code(keycode::kA);
+  init.set_ctrl_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_ca =
+      new KeyboardEvent("keydown", init);
+  init.set_ctrl_key(false);
   EXPECT_EQ(keyboard_event_ca->key_identifier(), "a");
+  EXPECT_EQ(keyboard_event_ca->key(), "a");
+  EXPECT_EQ(keyboard_event_ca->code(), "KeyA");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_sa = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kShiftKey, keycode::kA, 0, false);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_sa =
+      new KeyboardEvent("keydown", init);
+  init.set_shift_key(false);
   EXPECT_EQ(keyboard_event_sa->key_identifier(), "A");
+  EXPECT_EQ(keyboard_event_sa->key(), "A");
+  EXPECT_EQ(keyboard_event_sa->code(), "KeyA");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_1 = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, keycode::k1, 0, false);
+  init.set_key_code(keycode::k1);
+  scoped_refptr<KeyboardEvent> keyboard_event_1 =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event_1->key_identifier(), "1");
+  EXPECT_EQ(keyboard_event_1->key(), "1");
+  EXPECT_EQ(keyboard_event_1->code(), "Digit1");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_s1 = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kShiftKey, keycode::k1, 0, false);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_s1 =
+      new KeyboardEvent("keydown", init);
+  init.set_shift_key(false);
   EXPECT_EQ(keyboard_event_s1->key_identifier(), "!");
+  EXPECT_EQ(keyboard_event_s1->key(), "!");
+  EXPECT_EQ(keyboard_event_1->code(), "Digit1");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_left = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, keycode::kLeft, 0, false);
+  init.set_key_code(keycode::kLeft);
+  scoped_refptr<KeyboardEvent> keyboard_event_left =
+      new KeyboardEvent("keydown", init);
   EXPECT_EQ(keyboard_event_left->key_identifier(), "ArrowLeft");
+  EXPECT_EQ(keyboard_event_left->key(), "ArrowLeft");
+  EXPECT_EQ(keyboard_event_left->code(), "ArrowLeft");
 
-  scoped_refptr<KeyboardEvent> keyboard_event_sleft = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kShiftKey, keycode::kLeft, 0, false);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_sleft =
+      new KeyboardEvent("keydown", init);
+  init.set_shift_key(false);
   EXPECT_EQ(keyboard_event_sleft->key_identifier(), "ArrowLeft");
+  EXPECT_EQ(keyboard_event_sleft->key(), "ArrowLeft");
+  EXPECT_EQ(keyboard_event_sleft->code(), "ArrowLeft");
+
+  init.set_key_code(keycode::kNumpad5);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_num5 =
+      new KeyboardEvent("keydown", init);
+  init.set_shift_key(false);
+  EXPECT_EQ(keyboard_event_num5->key_identifier(), "5");
+  EXPECT_EQ(keyboard_event_num5->key(), "5");
+  EXPECT_EQ(keyboard_event_num5->code(), "Numpad5");
+
+  init.set_key_code(keycode::kSpace);
+  scoped_refptr<KeyboardEvent> keyboard_event_space =
+      new KeyboardEvent("keydown", init);
+  EXPECT_EQ(keyboard_event_space->key_identifier(), " ");
+  EXPECT_EQ(keyboard_event_space->key(), " ");
+  EXPECT_EQ(keyboard_event_space->code(), "Space");
 }
 
 TEST(KeyboardEventTest, CanGetAltKey) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->alt_key());
 
-  scoped_refptr<KeyboardEvent> keyboard_event_a = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kAltKey, 0, 0, false);
+  init.set_alt_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_a =
+      new KeyboardEvent("keydown", init);
+  init.set_alt_key(false);
   EXPECT_TRUE(keyboard_event_a->alt_key());
 }
 
 TEST(KeyboardEventTest, CanGetCtrlKey) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->ctrl_key());
 
-  scoped_refptr<KeyboardEvent> keyboard_event_c = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kCtrlKey, 0, 0, false);
+  init.set_ctrl_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_c =
+      new KeyboardEvent("keydown", init);
+  init.set_ctrl_key(false);
   EXPECT_TRUE(keyboard_event_c->ctrl_key());
 }
 
 TEST(KeyboardEventTest, CanGetMetaKey) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->meta_key());
 
-  scoped_refptr<KeyboardEvent> keyboard_event_m = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kMetaKey, 0, 0, false);
+  init.set_meta_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_m =
+      new KeyboardEvent("keydown", init);
+  init.set_meta_key(false);
   EXPECT_TRUE(keyboard_event_m->meta_key());
 }
 
 TEST(KeyboardEventTest, CanGetShiftKey) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->shift_key());
 
-  scoped_refptr<KeyboardEvent> keyboard_event_s = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kShiftKey, 0, 0, false);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_s =
+      new KeyboardEvent("keydown", init);
+  init.set_shift_key(false);
   EXPECT_TRUE(keyboard_event_s->shift_key());
 }
 
 TEST(KeyboardEventTest, CanGetModifierState) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->GetModifierState("Alt"));
   EXPECT_FALSE(keyboard_event->GetModifierState("Control"));
   EXPECT_FALSE(keyboard_event->GetModifierState("Meta"));
   EXPECT_FALSE(keyboard_event->GetModifierState("Shift"));
 
-  scoped_refptr<KeyboardEvent> keyboard_event_m = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kAltKey | KeyboardEvent::kCtrlKey |
-          KeyboardEvent::kMetaKey | KeyboardEvent::kShiftKey,
-      0, 0, false);
+  init.set_alt_key(true);
+  init.set_ctrl_key(true);
+  init.set_meta_key(true);
+  init.set_shift_key(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_m =
+      new KeyboardEvent("keydown", init);
   EXPECT_TRUE(keyboard_event_m->GetModifierState("Alt"));
   EXPECT_TRUE(keyboard_event_m->GetModifierState("Control"));
   EXPECT_TRUE(keyboard_event_m->GetModifierState("Meta"));
@@ -155,14 +195,14 @@
 }
 
 TEST(KeyboardEventTest, CanGetRepeat) {
-  scoped_refptr<KeyboardEvent> keyboard_event = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, 0, 0, false);
+  KeyboardEventInit init;
+  scoped_refptr<KeyboardEvent> keyboard_event =
+      new KeyboardEvent("keydown", init);
   EXPECT_FALSE(keyboard_event->repeat());
 
-  scoped_refptr<KeyboardEvent> keyboard_event_r = new KeyboardEvent(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kShiftKey, 0, 0, true);
+  init.set_repeat(true);
+  scoped_refptr<KeyboardEvent> keyboard_event_r =
+      new KeyboardEvent("keydown", init);
   EXPECT_TRUE(keyboard_event_r->repeat());
 }
 }  // namespace dom
diff --git a/src/cobalt/dom/memory_info.cc b/src/cobalt/dom/memory_info.cc
index 8758ab9..ceb427b 100644
--- a/src/cobalt/dom/memory_info.cc
+++ b/src/cobalt/dom/memory_info.cc
@@ -21,22 +21,14 @@
 namespace cobalt {
 namespace dom {
 
-uint32 MemoryInfo::total_js_heap_size(
-    script::EnvironmentSettings* settings) const {
-  DOMSettings* dom_settings =
-      base::polymorphic_downcast<dom::DOMSettings*>(settings);
-  size_t total_memory =
-      dom_settings->javascript_engine()->UpdateMemoryStatsAndReturnReserved();
-  return static_cast<uint32>(total_memory);
+uint32 MemoryInfo::total_js_heap_size() const {
+  return static_cast<uint32>(
+      script::JavaScriptEngine::UpdateMemoryStatsAndReturnReserved());
 }
 
-uint32 MemoryInfo::used_js_heap_size(
-    script::EnvironmentSettings* settings) const {
-  DOMSettings* dom_settings =
-      base::polymorphic_downcast<dom::DOMSettings*>(settings);
-  size_t total_memory =
-      dom_settings->javascript_engine()->UpdateMemoryStatsAndReturnReserved();
-  return static_cast<uint32>(total_memory);
+uint32 MemoryInfo::used_js_heap_size() const {
+  return static_cast<uint32>(
+      script::JavaScriptEngine::UpdateMemoryStatsAndReturnReserved());
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom/memory_info.h b/src/cobalt/dom/memory_info.h
index d3de6a6..7bd61d2 100644
--- a/src/cobalt/dom/memory_info.h
+++ b/src/cobalt/dom/memory_info.h
@@ -28,9 +28,9 @@
  public:
   MemoryInfo() {}
 
-  uint32 total_js_heap_size(script::EnvironmentSettings* settings) const;
+  uint32 total_js_heap_size() const;
 
-  uint32 used_js_heap_size(script::EnvironmentSettings* settings) const;
+  uint32 used_js_heap_size() const;
 
   DEFINE_WRAPPABLE_TYPE(MemoryInfo);
 
diff --git a/src/cobalt/dom/memory_info.idl b/src/cobalt/dom/memory_info.idl
index 0f8b713..6dbfd88 100644
--- a/src/cobalt/dom/memory_info.idl
+++ b/src/cobalt/dom/memory_info.idl
@@ -18,6 +18,6 @@
 [
   NoInterfaceObject,
 ] interface MemoryInfo {
-  [CallWith=EnvironmentSettings] readonly attribute unsigned long totalJSHeapSize;
-  [CallWith=EnvironmentSettings] readonly attribute unsigned long usedJSHeapSize;
+  readonly attribute unsigned long totalJSHeapSize;
+  readonly attribute unsigned long usedJSHeapSize;
 };
diff --git a/src/cobalt/dom/message_event.h b/src/cobalt/dom/message_event.h
index 6c23bad..075afb1 100644
--- a/src/cobalt/dom/message_event.h
+++ b/src/cobalt/dom/message_event.h
@@ -47,6 +47,10 @@
         response_type_(response_type),
         data_(data) {}
 
+  // Creates an event with its "initialized flag" unset.
+  explicit MessageEvent(UninitializedFlag uninitialized_flag)
+      : Event(uninitialized_flag) {}
+
   ResponseType data() const;
 
   // These helper functions are custom, and not in any spec.
diff --git a/src/cobalt/dom/mouse_event.cc b/src/cobalt/dom/mouse_event.cc
new file mode 100644
index 0000000..ea882f2
--- /dev/null
+++ b/src/cobalt/dom/mouse_event.cc
@@ -0,0 +1,108 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/mouse_event.h"
+
+#include <string>
+
+#include "cobalt/base/token.h"
+
+namespace cobalt {
+namespace dom {
+
+MouseEvent::MouseEvent(const std::string& type)
+    : UIEventWithKeyState(base::Token(type), kBubbles, kCancelable, NULL),
+      screen_x_(0),
+      screen_y_(0),
+      client_x_(0),
+      client_y_(0),
+      button_(0),
+      buttons_(0) {}
+
+MouseEvent::MouseEvent(const std::string& type, const MouseEventInit& init_dict)
+    : UIEventWithKeyState(base::Token(type), kBubbles, kCancelable,
+                          init_dict.view(), init_dict),
+      screen_x_(init_dict.screen_x()),
+      screen_y_(init_dict.screen_y()),
+      client_x_(init_dict.client_x()),
+      client_y_(init_dict.client_y()),
+      button_(init_dict.button()),
+      buttons_(init_dict.buttons()) {}
+
+MouseEvent::MouseEvent(base::Token type, const scoped_refptr<Window>& view,
+                       const MouseEventInit& init_dict)
+    : UIEventWithKeyState(type, kBubbles, kCancelable, view, init_dict),
+      screen_x_(init_dict.screen_x()),
+      screen_y_(init_dict.screen_y()),
+      client_x_(init_dict.client_x()),
+      client_y_(init_dict.client_y()),
+      button_(init_dict.button()),
+      buttons_(init_dict.buttons()) {}
+
+MouseEvent::MouseEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                       const scoped_refptr<Window>& view,
+                       const MouseEventInit& init_dict)
+    : UIEventWithKeyState(type, bubbles, cancelable, view, init_dict),
+      screen_x_(init_dict.screen_x()),
+      screen_y_(init_dict.screen_y()),
+      client_x_(init_dict.client_x()),
+      client_y_(init_dict.client_y()),
+      button_(init_dict.button()),
+      buttons_(init_dict.buttons()) {}
+
+MouseEvent::MouseEvent(UninitializedFlag uninitialized_flag)
+    : UIEventWithKeyState(uninitialized_flag),
+      screen_x_(0),
+      screen_y_(0),
+      client_x_(0),
+      client_y_(0),
+      button_(0),
+      buttons_(0) {}
+
+void MouseEvent::InitMouseEvent(
+    const std::string& type, bool bubbles, bool cancelable,
+    const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
+    int32 screen_y, int32 client_x, int32 client_y, bool ctrl_key, bool alt_key,
+    bool shift_key, bool meta_key, uint16 button,
+    const scoped_refptr<EventTarget>& related_target) {
+  InitUIEventWithKeyState(type, bubbles, cancelable, view, detail, ctrl_key,
+                          alt_key, shift_key, meta_key);
+  screen_x_ = screen_x;
+  screen_y_ = screen_y;
+  client_x_ = client_x;
+  client_y_ = client_y;
+  button_ = button;
+  buttons_ = 0;
+  related_target_ = related_target;
+}
+
+void MouseEvent::InitMouseEvent(
+    const std::string& type, bool bubbles, bool cancelable,
+    const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
+    int32 screen_y, int32 client_x, int32 client_y,
+    const std::string& modifierslist, uint16 button,
+    const scoped_refptr<EventTarget>& related_target) {
+  InitUIEventWithKeyState(type, bubbles, cancelable, view, detail,
+                          modifierslist);
+  screen_x_ = screen_x;
+  screen_y_ = screen_y;
+  client_x_ = client_x;
+  client_y_ = client_y;
+  button_ = button;
+  buttons_ = 0;
+  related_target_ = related_target;
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/mouse_event.h b/src/cobalt/dom/mouse_event.h
new file mode 100644
index 0000000..da09013
--- /dev/null
+++ b/src/cobalt/dom/mouse_event.h
@@ -0,0 +1,93 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MOUSE_EVENT_H_
+#define COBALT_DOM_MOUSE_EVENT_H_
+
+#include <string>
+
+#include "cobalt/base/token.h"
+#include "cobalt/dom/event_target.h"
+#include "cobalt/dom/mouse_event_init.h"
+#include "cobalt/dom/ui_event_with_key_state.h"
+
+namespace cobalt {
+namespace dom {
+
+// The MouseEvent provides specific contextual information associated with
+// mouse devices.
+//   https://www.w3.org/TR/2016/WD-uievents-20160804/#events-mouseevents
+class MouseEvent : public UIEventWithKeyState {
+ public:
+  explicit MouseEvent(const std::string& type);
+  MouseEvent(const std::string& type, const MouseEventInit& init_dict);
+  MouseEvent(base::Token type, const scoped_refptr<Window>& view,
+             const MouseEventInit& init_dict);
+  MouseEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+             const scoped_refptr<Window>& view,
+             const MouseEventInit& init_dict);
+
+  // Creates an event with its "initialized flag" unset.
+  explicit MouseEvent(UninitializedFlag uninitialized_flag);
+
+  void InitMouseEvent(const std::string& type, bool bubbles, bool cancelable,
+                      const scoped_refptr<Window>& view, int32 detail,
+                      int32 screen_x, int32 screen_y, int32 client_x,
+                      int32 client_y, bool ctrl_key, bool alt_key,
+                      bool shift_key, bool meta_key, uint16 button,
+                      const scoped_refptr<EventTarget>& related_target);
+
+  void InitMouseEvent(const std::string& type, bool bubbles, bool cancelable,
+                      const scoped_refptr<Window>& view, int32 detail,
+                      int32 screen_x, int32 screen_y, int32 client_x,
+                      int32 client_y, const std::string& modifierslist,
+                      uint16 button,
+                      const scoped_refptr<EventTarget>& related_target);
+
+  int32_t screen_x() const { return screen_x_; }
+  int32_t screen_y() const { return screen_y_; }
+  int32_t client_x() const { return client_x_; }
+  int32_t client_y() const { return client_y_; }
+
+  int16_t button() const { return button_; }
+  uint16_t buttons() const { return buttons_; }
+
+  void set_related_target(const scoped_refptr<EventTarget>& target) {
+    related_target_ = target;
+  }
+
+  const scoped_refptr<EventTarget>& related_target() const {
+    return related_target_;
+  }
+
+  DEFINE_WRAPPABLE_TYPE(MouseEvent);
+
+ protected:
+  ~MouseEvent() OVERRIDE {}
+
+ private:
+  int32_t screen_x_;
+  int32_t screen_y_;
+  int32_t client_x_;
+  int32_t client_y_;
+  int16_t button_;
+  uint16_t buttons_;
+
+  scoped_refptr<EventTarget> related_target_;
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_MOUSE_EVENT_H_
diff --git a/src/cobalt/dom/mouse_event.idl b/src/cobalt/dom/mouse_event.idl
new file mode 100644
index 0000000..759e615
--- /dev/null
+++ b/src/cobalt/dom/mouse_event.idl
@@ -0,0 +1,52 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2016/WD-uievents-20160804/#interface-mouseevent
+// https://www.w3.org/TR/2016/WD-uievents-20160804/#idl-interface-MouseEvent-initializers
+
+[Constructor(DOMString type, optional MouseEventInit eventInitDict)]
+interface MouseEvent : UIEvent {
+  readonly attribute long screenX;
+  readonly attribute long screenY;
+  readonly attribute long clientX;
+  readonly attribute long clientY;
+
+  readonly attribute boolean ctrlKey;
+  readonly attribute boolean shiftKey;
+  readonly attribute boolean altKey;
+  readonly attribute boolean metaKey;
+
+  readonly attribute short button;
+  readonly attribute unsigned short buttons;
+
+  readonly attribute EventTarget? relatedTarget;
+
+  boolean getModifierState(DOMString keyArg);
+
+  void initMouseEvent(DOMString type,
+                   optional boolean bubbles = false,
+                   optional boolean cancelable = false,
+                   optional Window? view = null,
+                   optional long detail = 0,
+                   optional long screenX = 0,
+                   optional long screenY = 0,
+                   optional long clientX = 0,
+                   optional long clientY = 0,
+                   optional boolean ctrlKey = false,
+                   optional boolean altKey = false,
+                   optional boolean shiftKey = false,
+                   optional boolean metaKey = false,
+                   optional unsigned short button = 0,
+                   optional EventTarget? relatedTarget = null);
+};
diff --git a/src/cobalt/dom/mouse_event_init.idl b/src/cobalt/dom/mouse_event_init.idl
new file mode 100644
index 0000000..9b752d8
--- /dev/null
+++ b/src/cobalt/dom/mouse_event_init.idl
@@ -0,0 +1,27 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/uievents/#ref-for-dictdef-eventmodifierinit-3
+// https://www.w3.org/TR/2013/WD-cssom-view-20131217/#extensions-to-the-mouseevent-interface
+
+dictionary MouseEventInit : EventModifierInit {
+  long screenX = 0;
+  long screenY = 0;
+  long clientX = 0;
+  long clientY = 0;
+
+  short button = 0;
+  unsigned short buttons = 0;
+  EventTarget? relatedTarget = null;
+};
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index b1f0569..4b46103 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -36,6 +36,7 @@
 #include "cobalt/dom/node_list_live.h"
 #include "cobalt/dom/rule_matching.h"
 #include "cobalt/dom/text.h"
+#include "cobalt/dom/window.h"
 #if defined(OS_STARBOARD)
 #include "starboard/configuration.h"
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
@@ -85,8 +86,8 @@
 
 }  // namespace
 
-// Algorithm for DispatchEvent:
-//   https://www.w3.org/TR/dom/#dispatching-events
+// Diagram for DispatchEvent:
+//  https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
 bool Node::DispatchEvent(const scoped_refptr<Event>& event) {
   DCHECK(event);
   DCHECK(!event->IsBeingDispatched());
@@ -100,38 +101,59 @@
   }
 
   // The event is now being dispatched. Track it in the global stats.
-  GlobalStats::GetInstance()->StartDispatchEvent();
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
+  scoped_refptr<Window> window;
+  if (IsInDocument()) {
+    DCHECK(node_document());
+    window = node_document()->default_view();
+  }
 
   typedef std::vector<scoped_refptr<Node> > Ancestors;
   Ancestors ancestors;
-  for (scoped_refptr<Node> current = this->parent_node(); current != NULL;
+  for (Node* current = this->parent_node(); current != NULL;
        current = current->parent_node()) {
     ancestors.push_back(current);
   }
 
   event->set_target(this);
 
-  // The capture phase
-  if (!ancestors.empty()) {
-    event->set_event_phase(Event::kCapturingPhase);
+  // The capture phase: The event object propagates through the target's
+  // ancestors from the Window to the target's parent. This phase is also known
+  // as the capturing phase.
+  event->set_event_phase(Event::kCapturingPhase);
+  if (window) {
+    window->FireEventOnListeners(event);
+  }
+  if (!event->propagation_stopped() && !ancestors.empty()) {
     for (Ancestors::reverse_iterator iter = ancestors.rbegin();
          iter != ancestors.rend() && !event->propagation_stopped(); ++iter) {
       (*iter)->FireEventOnListeners(event);
     }
   }
 
-  // The at target phase
   if (!event->propagation_stopped()) {
+    // The target phase: The event object arrives at the event object's event
+    // target. This phase is also known as the at-target phase.
     event->set_event_phase(Event::kAtTarget);
     FireEventOnListeners(event);
   }
 
-  // The bubbling phase
-  if (!event->propagation_stopped() && event->bubbles() && !ancestors.empty()) {
-    event->set_event_phase(Event::kBubblingPhase);
-    for (Ancestors::iterator iter = ancestors.begin();
-         iter != ancestors.end() && !event->propagation_stopped(); ++iter) {
-      (*iter)->FireEventOnListeners(event);
+  // If the event type indicates that the event doesn't bubble, then the event
+  // object will halt after completion of this phase.
+  if (!event->propagation_stopped() && event->bubbles()) {
+    if (!ancestors.empty()) {
+      // The bubble phase: The event object propagates through the target's
+      // ancestors in reverse order, starting with the target's parent and
+      // ending with the Window. This phase is also known as the bubbling phase.
+      event->set_event_phase(Event::kBubblingPhase);
+      for (Ancestors::iterator iter = ancestors.begin();
+           iter != ancestors.end() && !event->propagation_stopped(); ++iter) {
+        (*iter)->FireEventOnListeners(event);
+      }
+      if (window) {
+        window->FireEventOnListeners(event);
+      }
     }
   }
 
@@ -139,14 +161,14 @@
 
   // The event has completed being dispatched. Stop tracking it in the global
   // stats.
-  GlobalStats::GetInstance()->StopDispatchEvent();
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 
   return !event->default_prevented();
 }
 
 // Algorithm for owner_document:
 //   https://www.w3.org/TR/2015/WD-dom-20150618/#dom-node-ownerdocument
-scoped_refptr<Document> Node::owner_document() const {
+Document* Node::owner_document() const {
   // 1. If the context object is a document, return null.
   if (IsDocument()) {
     return NULL;
@@ -155,7 +177,7 @@
   return node_document();
 }
 
-scoped_refptr<Element> Node::parent_element() const {
+Element* Node::parent_element() const {
   return parent_ ? parent_->AsElement() : NULL;
 }
 
@@ -322,7 +344,7 @@
   return HTMLCollection::CreateWithChildElements(this);
 }
 
-scoped_refptr<Element> Node::first_element_child() const {
+Element* Node::first_element_child() const {
   Node* child = first_child();
   while (child) {
     if (child->IsElement()) {
@@ -333,7 +355,7 @@
   return NULL;
 }
 
-scoped_refptr<Element> Node::last_element_child() const {
+Element* Node::last_element_child() const {
   Node* child = last_child();
   while (child) {
     if (child->IsElement()) {
@@ -366,7 +388,7 @@
       this, selectors, node_document_->html_element_context()->css_parser());
 }
 
-scoped_refptr<Element> Node::previous_element_sibling() const {
+Element* Node::previous_element_sibling() const {
   Node* sibling = previous_sibling();
   while (sibling) {
     if (sibling->IsElement()) {
@@ -377,7 +399,7 @@
   return NULL;
 }
 
-scoped_refptr<Element> Node::next_element_sibling() const {
+Element* Node::next_element_sibling() const {
   Node* sibling = next_sibling();
   while (sibling) {
     if (sibling->IsElement()) {
@@ -415,25 +437,25 @@
   // 4. Not needed by Cobalt.
 }
 
-scoped_refptr<Node> Node::GetRootNode() {
+Node* Node::GetRootNode() {
   Node* root = this;
   while (root->parent_node()) {
     root = root->parent_node();
   }
-  return make_scoped_refptr(root);
+  return root;
 }
 
-scoped_refptr<CDATASection> Node::AsCDATASection() { return NULL; }
+CDATASection* Node::AsCDATASection() { return NULL; }
 
-scoped_refptr<Comment> Node::AsComment() { return NULL; }
+Comment* Node::AsComment() { return NULL; }
 
-scoped_refptr<Document> Node::AsDocument() { return NULL; }
+Document* Node::AsDocument() { return NULL; }
 
-scoped_refptr<DocumentType> Node::AsDocumentType() { return NULL; }
+DocumentType* Node::AsDocumentType() { return NULL; }
 
-scoped_refptr<Element> Node::AsElement() { return NULL; }
+Element* Node::AsElement() { return NULL; }
 
-scoped_refptr<Text> Node::AsText() { return NULL; }
+Text* Node::AsText() { return NULL; }
 
 void Node::TraceMembers(script::Tracer* tracer) {
   EventTarget::TraceMembers(tracer);
diff --git a/src/cobalt/dom/node.h b/src/cobalt/dom/node.h
index e1fbecb..82cce91 100644
--- a/src/cobalt/dom/node.h
+++ b/src/cobalt/dom/node.h
@@ -119,15 +119,15 @@
   virtual NodeType node_type() const = 0;
   virtual base::Token node_name() const = 0;
 
-  scoped_refptr<Document> owner_document() const;
-  scoped_refptr<Node> parent_node() const { return parent_; }
-  scoped_refptr<Element> parent_element() const;
+  Document* owner_document() const;
+  Node* parent_node() const { return parent_; }
+  Element* parent_element() const;
   bool HasChildNodes() const;
   scoped_refptr<NodeList> child_nodes() const;
-  scoped_refptr<Node> first_child() const { return first_child_; }
-  scoped_refptr<Node> last_child() const { return last_child_; }
-  scoped_refptr<Node> next_sibling() const { return next_sibling_; }
-  scoped_refptr<Node> previous_sibling() const { return previous_sibling_; }
+  Node* first_child() const { return first_child_; }
+  Node* last_child() const { return last_child_; }
+  Node* next_sibling() const { return next_sibling_; }
+  Node* previous_sibling() const { return previous_sibling_; }
 
   virtual base::optional<std::string> node_value() const {
     return base::nullopt;
@@ -158,8 +158,8 @@
   //   https://www.w3.org/TR/dom/#parentnode
   //
   scoped_refptr<HTMLCollection> children() const;
-  scoped_refptr<Element> first_element_child() const;
-  scoped_refptr<Element> last_element_child() const;
+  Element* first_element_child() const;
+  Element* last_element_child() const;
   unsigned int child_element_count() const;
 
   scoped_refptr<Element> QuerySelector(const std::string& selectors);
@@ -169,8 +169,8 @@
   // The NonDocumentTypeChildNode interface contains methods that are particular
   // to Node objects that can have a parent.
   //   https://www.w3.org/TR/2014/WD-dom-20140710/#interface-nondocumenttypechildnode
-  scoped_refptr<Element> previous_element_sibling() const;
-  scoped_refptr<Element> next_element_sibling() const;
+  Element* previous_element_sibling() const;
+  Element* next_element_sibling() const;
 
   // From the spec: Node.
   //
@@ -190,7 +190,7 @@
 
   // Returns the root Node of the tree this node belongs to. If this node is the
   // root, it will return this Node.
-  scoped_refptr<Node> GetRootNode();
+  Node* GetRootNode();
 
   bool IsCDATASection() const { return node_type() == kCdataSectionNode; }
   bool IsComment() const { return node_type() == kCommentNode; }
@@ -201,12 +201,12 @@
 
   // Safe type conversion methods that will downcast to the required type if
   // possible or return NULL otherwise.
-  virtual scoped_refptr<CDATASection> AsCDATASection();
-  virtual scoped_refptr<Comment> AsComment();
-  virtual scoped_refptr<Document> AsDocument();
-  virtual scoped_refptr<DocumentType> AsDocumentType();
-  virtual scoped_refptr<Element> AsElement();
-  virtual scoped_refptr<Text> AsText();
+  virtual CDATASection* AsCDATASection();
+  virtual Comment* AsComment();
+  virtual Document* AsDocument();
+  virtual DocumentType* AsDocumentType();
+  virtual Element* AsElement();
+  virtual Text* AsText();
 
   // Node generation counter that will be modified for every content change
   // that affects the topology of the subtree defined by this node.
@@ -248,6 +248,8 @@
   // removed from to its owner document.
   virtual void OnRemovedFromDocument();
 
+  virtual bool IsInDocument() const { return inserted_into_document_; }
+
   virtual void PurgeCachedBackgroundImagesOfNodeAndDescendants();
   virtual void InvalidateComputedStylesOfNodeAndDescendants();
   virtual void InvalidateLayoutBoxesOfNodeAndAncestors();
diff --git a/src/cobalt/dom/node_list_live_test.cc b/src/cobalt/dom/node_list_live_test.cc
index a9762af..26d3b6f 100644
--- a/src/cobalt/dom/node_list_live_test.cc
+++ b/src/cobalt/dom/node_list_live_test.cc
@@ -29,7 +29,8 @@
       : dom_stat_tracker_("NodeListLiveTest"),
         html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL,
-                              &dom_stat_tracker_, ""),
+                              &dom_stat_tracker_, "",
+                              base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)) {}
 
   ~NodeListLiveTest() OVERRIDE {}
diff --git a/src/cobalt/dom/node_list_test.cc b/src/cobalt/dom/node_list_test.cc
index df03cba..392963f 100644
--- a/src/cobalt/dom/node_list_test.cc
+++ b/src/cobalt/dom/node_list_test.cc
@@ -29,7 +29,8 @@
       : dom_stat_tracker_(new DomStatTracker("NodeListTest")),
         html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL,
-                              dom_stat_tracker_.get(), ""),
+                              dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)) {}
 
   ~NodeListTest() OVERRIDE {}
diff --git a/src/cobalt/dom/pointer_event.cc b/src/cobalt/dom/pointer_event.cc
new file mode 100644
index 0000000..7cc2f38
--- /dev/null
+++ b/src/cobalt/dom/pointer_event.cc
@@ -0,0 +1,71 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pointer_event.h"
+
+#include <string>
+
+namespace cobalt {
+namespace dom {
+
+PointerEvent::PointerEvent(const std::string& type)
+    : MouseEvent(type),
+      pointer_id_(0),
+      width_(0),
+      height_(0),
+      pressure_(0),
+      tilt_x_(0),
+      tilt_y_(0),
+      is_primary_(false) {}
+
+PointerEvent::PointerEvent(const std::string& type,
+                           const PointerEventInit& init_dict)
+    : MouseEvent(type, init_dict),
+      pointer_id_(init_dict.pointer_id()),
+      width_(init_dict.width()),
+      height_(init_dict.height()),
+      pressure_(init_dict.pressure()),
+      tilt_x_(init_dict.tilt_x()),
+      tilt_y_(init_dict.tilt_y()),
+      pointer_type_(init_dict.pointer_type()),
+      is_primary_(init_dict.is_primary()) {}
+
+PointerEvent::PointerEvent(base::Token type, const scoped_refptr<Window>& view,
+                           const PointerEventInit& init_dict)
+    : MouseEvent(type, view, init_dict),
+      pointer_id_(init_dict.pointer_id()),
+      width_(init_dict.width()),
+      height_(init_dict.height()),
+      pressure_(init_dict.pressure()),
+      tilt_x_(init_dict.tilt_x()),
+      tilt_y_(init_dict.tilt_y()),
+      pointer_type_(init_dict.pointer_type()),
+      is_primary_(init_dict.is_primary()) {}
+
+PointerEvent::PointerEvent(base::Token type, Bubbles bubbles,
+                           Cancelable cancelable,
+                           const scoped_refptr<Window>& view,
+                           const PointerEventInit& init_dict)
+    : MouseEvent(type, bubbles, cancelable, view, init_dict),
+      pointer_id_(init_dict.pointer_id()),
+      width_(init_dict.width()),
+      height_(init_dict.height()),
+      pressure_(init_dict.pressure()),
+      tilt_x_(init_dict.tilt_x()),
+      tilt_y_(init_dict.tilt_y()),
+      pointer_type_(init_dict.pointer_type()),
+      is_primary_(init_dict.is_primary()) {}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/pointer_event.h b/src/cobalt/dom/pointer_event.h
new file mode 100644
index 0000000..489126f
--- /dev/null
+++ b/src/cobalt/dom/pointer_event.h
@@ -0,0 +1,69 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_POINTER_EVENT_H_
+#define COBALT_DOM_POINTER_EVENT_H_
+
+#include <string>
+
+#include "cobalt/base/token.h"
+#include "cobalt/dom/mouse_event.h"
+#include "cobalt/dom/pointer_event_init.h"
+
+namespace cobalt {
+namespace dom {
+
+// The PointerEvent provides specific contextual information associated with
+// pointer devices. A pointer is a hardware agnostic representation of input
+// devices that can target a specific coordinate (or set of coordinates) on a
+// screen.
+//   https://www.w3.org/TR/2015/REC-pointerevents-20150224/
+class PointerEvent : public MouseEvent {
+ public:
+  explicit PointerEvent(const std::string& type);
+  PointerEvent(const std::string& type, const PointerEventInit& init_dict);
+  PointerEvent(base::Token type, const scoped_refptr<Window>& view,
+               const PointerEventInit& init_dict);
+  PointerEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+               const scoped_refptr<Window>& view,
+               const PointerEventInit& init_dict);
+
+  int32_t pointer_id() const { return pointer_id_; }
+  double width() const { return width_; }
+  double height() const { return height_; }
+  float pressure() const { return pressure_; }
+  int32_t tilt_x() const { return tilt_x_; }
+  int32_t tilt_y() const { return tilt_y_; }
+  const std::string& pointer_type() const { return pointer_type_; }
+  int32_t is_primary() const { return is_primary_; }
+
+  DEFINE_WRAPPABLE_TYPE(PointerEvent);
+
+ private:
+  ~PointerEvent() OVERRIDE {}
+
+  int32_t pointer_id_;
+  double width_;
+  double height_;
+  float pressure_;
+  int32_t tilt_x_;
+  int32_t tilt_y_;
+  std::string pointer_type_;
+  bool is_primary_;
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_POINTER_EVENT_H_
diff --git a/src/cobalt/dom/pointer_event.idl b/src/cobalt/dom/pointer_event.idl
new file mode 100644
index 0000000..0fd4b20
--- /dev/null
+++ b/src/cobalt/dom/pointer_event.idl
@@ -0,0 +1,18 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2015/REC-pointerevents-20150224/
+
+[Constructor(DOMString type)]
+interface PointerEvent : MouseEvent {};
diff --git a/src/cobalt/dom/pointer_event_init.idl b/src/cobalt/dom/pointer_event_init.idl
new file mode 100644
index 0000000..83f279e
--- /dev/null
+++ b/src/cobalt/dom/pointer_event_init.idl
@@ -0,0 +1,26 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/pointerevents/#idl-def-PointerEventInit
+
+dictionary PointerEventInit : MouseEventInit {
+  long pointerId = 0;
+  double width = 0;
+  double height = 0;
+  float pressure = 0;
+  long tiltX = 0;
+  long tiltY = 0;
+  DOMString pointerType = "";
+  boolean isPrimary = false;
+};
diff --git a/src/cobalt/dom/progress_event.cc b/src/cobalt/dom/progress_event.cc
index ab00d02..5d20690 100644
--- a/src/cobalt/dom/progress_event.cc
+++ b/src/cobalt/dom/progress_event.cc
@@ -18,14 +18,20 @@
 namespace dom {
 
 ProgressEvent::ProgressEvent(const std::string& type)
-    : Event(type), loaded_(0), total_(0), length_computable_(false) {}
+    : Event(base::Token(type), kNotBubbles, kNotCancelable),
+      loaded_(0),
+      total_(0),
+      length_computable_(false) {}
 
 ProgressEvent::ProgressEvent(base::Token type)
-    : Event(type), loaded_(0), total_(0), length_computable_(false) {}
+    : Event(type, kNotBubbles, kNotCancelable),
+      loaded_(0),
+      total_(0),
+      length_computable_(false) {}
 
 ProgressEvent::ProgressEvent(base::Token type, uint64 loaded, uint64 total,
                              bool length_computable)
-    : Event(type),
+    : Event(type, kNotBubbles, kNotCancelable),
       loaded_(loaded),
       total_(total),
       length_computable_(length_computable) {}
diff --git a/src/cobalt/dom/progress_event.h b/src/cobalt/dom/progress_event.h
index 3847781..d8c0358 100644
--- a/src/cobalt/dom/progress_event.h
+++ b/src/cobalt/dom/progress_event.h
@@ -23,6 +23,8 @@
 namespace cobalt {
 namespace dom {
 
+// Events using the ProgressEvent interface indicate some kind of progression.
+//   https://www.w3.org/TR/progress-events/#interface-progressevent
 class ProgressEvent : public Event {
  public:
   explicit ProgressEvent(const std::string& type);
diff --git a/src/cobalt/dom/rule_matching.cc b/src/cobalt/dom/rule_matching.cc
index 2e1348a..1437827 100644
--- a/src/cobalt/dom/rule_matching.cc
+++ b/src/cobalt/dom/rule_matching.cc
@@ -260,8 +260,10 @@
   // device that does not detect hovering) are still conforming.
   //   https://www.w3.org/TR/selectors4/#hover-pseudo
   void VisitHoverPseudoClass(cssom::HoverPseudoClass*) OVERRIDE {
-    NOTIMPLEMENTED();
-    element_ = NULL;
+    if (!element_->AsHTMLElement() ||
+        !element_->AsHTMLElement()->IsDesignated()) {
+      element_ = NULL;
+    }
   }
 
   // The negation pseudo-class, :not(), is a functional pseudo-class taking a
@@ -523,6 +525,14 @@
                                     &candidate_nodes);
       }
 
+      // Hover pseudo class.
+      if (node->HasPseudoClass(cssom::kHoverPseudoClass, combinator_type) &&
+          element->IsDesignated()) {
+        GatherCandidateNodesFromSet(cssom::kHoverPseudoClass, combinator_type,
+                                    node->pseudo_class_nodes(),
+                                    &candidate_nodes);
+      }
+
       // Not pseudo class.
       if (node->HasPseudoClass(cssom::kNotPseudoClass, combinator_type)) {
         GatherCandidateNodesFromSet(cssom::kNotPseudoClass, combinator_type,
diff --git a/src/cobalt/dom/rule_matching_test.cc b/src/cobalt/dom/rule_matching_test.cc
index 5323f3d..eac8ded 100644
--- a/src/cobalt/dom/rule_matching_test.cc
+++ b/src/cobalt/dom/rule_matching_test.cc
@@ -45,7 +45,8 @@
         dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")),
         html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, dom_stat_tracker_.get(), ""),
+                              NULL, NULL, dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)),
         root_(document_->CreateElement("html")->AsHTMLElement()) {
     document_->AppendChild(root_);
diff --git a/src/cobalt/dom/serializer_test.cc b/src/cobalt/dom/serializer_test.cc
index 625e4cc..60cb8e0 100644
--- a/src/cobalt/dom/serializer_test.cc
+++ b/src/cobalt/dom/serializer_test.cc
@@ -46,7 +46,8 @@
       dom_stat_tracker_(new DomStatTracker("SerializerTest")),
       html_element_context_(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL,
-                            dom_stat_tracker_.get(), ""),
+                            dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted),
       document_(new Document(&html_element_context_)),
       root_(new Element(document_, base::Token("root"))),
       source_location_(base::SourceLocation("[object SerializerTest]", 1, 1)) {}
diff --git a/src/cobalt/dom/testing/stub_window.h b/src/cobalt/dom/testing/stub_window.h
index ffe7834..39f3115 100644
--- a/src/cobalt/dom/testing/stub_window.h
+++ b/src/cobalt/dom/testing/stub_window.h
@@ -52,9 +52,9 @@
     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, NULL, NULL,
-        &local_storage_database_, stub_media_module_.get(),
+        1920, 1080, base::kApplicationStateStarted, css_parser_.get(),
+        dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL, NULL,
+        NULL, &local_storage_database_, stub_media_module_.get(),
         stub_media_module_.get(), NULL, NULL, NULL, NULL,
         dom_stat_tracker_.get(), url_, "", "en-US",
         base::Callback<void(const GURL&)>(), base::Bind(&StubErrorCallback),
@@ -63,8 +63,7 @@
         base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         base::Closure() /* window_close */,
-        base::Closure() /* window_minimize */,
-        NULL, NULL, NULL);
+        base::Closure() /* window_minimize */, NULL, NULL, NULL);
     global_environment_->CreateGlobalObject(window_, &environment_settings_);
   }
 
diff --git a/src/cobalt/dom/text.h b/src/cobalt/dom/text.h
index 4e00491..37314f3 100644
--- a/src/cobalt/dom/text.h
+++ b/src/cobalt/dom/text.h
@@ -41,7 +41,7 @@
 
   // Custom, not in any spec: Node.
   //
-  scoped_refptr<Text> AsText() OVERRIDE { return this; }
+  Text* AsText() OVERRIDE { return this; }
 
   void Accept(NodeVisitor* visitor) OVERRIDE;
   void Accept(ConstNodeVisitor* visitor) const OVERRIDE;
diff --git a/src/cobalt/dom/ui_event.cc b/src/cobalt/dom/ui_event.cc
index 2b4d274..aa0a0e4 100644
--- a/src/cobalt/dom/ui_event.cc
+++ b/src/cobalt/dom/ui_event.cc
@@ -20,23 +20,36 @@
 namespace cobalt {
 namespace dom {
 
-UIEvent::UIEvent(const std::string& type) : Event(type) {}
+UIEvent::UIEvent(const std::string& type) : Event(type), detail_(0) {}
+UIEvent::UIEvent(const std::string& type, const UIEventInit& init_dict)
+    : Event(type, init_dict),
+      view_(init_dict.view()),
+      detail_(init_dict.detail()),
+      which_(init_dict.which()) {}
 
 UIEvent::UIEvent(UninitializedFlag uninitialized_flag)
-    : Event(uninitialized_flag) {}
+    : Event(uninitialized_flag), detail_(0), which_(0) {}
 
-UIEvent::UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable)
-    : Event(type, bubbles, cancelable) {}
+UIEvent::UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                 const scoped_refptr<Window>& view)
+    : Event(type, bubbles, cancelable), view_(view), detail_(0), which_(0) {}
 
 void UIEvent::InitUIEvent(const std::string& type, bool bubbles,
                           bool cancelable, const scoped_refptr<Window>& view,
                           int32 detail) {
-  UNREFERENCED_PARAMETER(detail);
   InitEvent(type, bubbles, cancelable);
   view_ = view;
+  detail_ = detail;
 }
 
-UIEvent::UIEvent(base::Token type) : Event(type) {}
+UIEvent::UIEvent(base::Token type) : Event(type), detail_(0), which_(0) {}
+UIEvent::UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                 const scoped_refptr<Window>& view,
+                 const UIEventInit& init_dict)
+    : Event(type, bubbles, cancelable),
+      view_(view),
+      detail_(init_dict.detail()),
+      which_(init_dict.which()) {}
 
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/ui_event.h b/src/cobalt/dom/ui_event.h
index 6659231..4bba136 100644
--- a/src/cobalt/dom/ui_event.h
+++ b/src/cobalt/dom/ui_event.h
@@ -20,6 +20,7 @@
 #include "base/string_piece.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/event.h"
+#include "cobalt/dom/ui_event_init.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/wrappable.h"
 
@@ -28,15 +29,17 @@
 
 // The UIEvent provides specific contextual information associated with User
 // Interface events.
-//   https://www.w3.org/TR/DOM-Level-3-Events/#events-uievents
+//   https://www.w3.org/TR/2016/WD-uievents-20160804/#events-uievents
 class UIEvent : public Event {
  public:
   explicit UIEvent(const std::string& type);
+  UIEvent(const std::string& type, const UIEventInit& init_dict);
 
   // Creates an event with its "initialized flag" unset.
   explicit UIEvent(UninitializedFlag uninitialized_flag);
 
-  UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable);
+  UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+          const scoped_refptr<Window>& view);
 
   // Web API: UIEvent
   //
@@ -44,20 +47,27 @@
                    const scoped_refptr<Window>& view, int32 detail);
 
   const scoped_refptr<Window>& view() const { return view_; }
+  int32 detail() const { return detail_; }
+
   // The following properties are defined inside UIEvent but are not valid for
   // all UIEvent subtypes.  Subtypes should override the getters when necessary.
   virtual int32 page_x() const { return 0; }
   virtual int32 page_y() const { return 0; }
-  virtual uint32 which() const { return 0; }
+  virtual uint32 which() const { return which_; }
 
   DEFINE_WRAPPABLE_TYPE(UIEvent);
 
  protected:
   explicit UIEvent(base::Token type);
+  explicit UIEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                   const scoped_refptr<Window>& view,
+                   const UIEventInit& init_dict);
 
   ~UIEvent() OVERRIDE {}
 
   scoped_refptr<Window> view_;
+  int32 detail_;
+  uint32 which_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/ui_event.idl b/src/cobalt/dom/ui_event.idl
index aeb0ad6..4a8e0aa 100644
--- a/src/cobalt/dom/ui_event.idl
+++ b/src/cobalt/dom/ui_event.idl
@@ -14,11 +14,10 @@
 
 // https://www.w3.org/TR/2015/WD-uievents-20151215/#interface-UIEvent
 
-[Constructor(DOMString type)]
+[Constructor(DOMString type, optional UIEventInit eventInitDict)]
 interface UIEvent : Event {
-  void initUIEvent(DOMString type, boolean bubbles, boolean cancelable,
-                   Window? view, long detail);
   readonly attribute Window? view;
+  readonly attribute long detail;
 
   // Note that the following properties are not part of any specification but
   // are commonly implemented and required by Cobalt.
@@ -29,4 +28,10 @@
   // The numeric keyCode of the key pressed, or the character code (charCode)
   // for an alphanumeric key pressed.
   readonly attribute unsigned long which;
+
+  void initUIEvent(DOMString type,
+                   optional boolean bubbles = false,
+                   optional boolean cancelable = false,
+                   optional Window? view = null,
+                   optional long detail = 0);
 };
diff --git a/src/cobalt/dom/ui_event_init.idl b/src/cobalt/dom/ui_event_init.idl
new file mode 100644
index 0000000..9b6204d
--- /dev/null
+++ b/src/cobalt/dom/ui_event_init.idl
@@ -0,0 +1,22 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/uievents/#dictdef-uieventinit-uieventinit
+// https://w3c.github.io/uievents/#legacy-dictionary-UIEventInit
+
+dictionary UIEventInit : EventInit {
+  Window? view = null;
+  long detail = 0;
+  unsigned long which = 0;
+};
diff --git a/src/cobalt/dom/ui_event_with_key_state.cc b/src/cobalt/dom/ui_event_with_key_state.cc
index 16fc64c..7a5c9bf 100644
--- a/src/cobalt/dom/ui_event_with_key_state.cc
+++ b/src/cobalt/dom/ui_event_with_key_state.cc
@@ -14,20 +14,54 @@
 
 #include "cobalt/dom/ui_event_with_key_state.h"
 
+#include <vector>
+
+#include "base/string_split.h"
 #include "cobalt/system_window/input_event.h"
 
 namespace cobalt {
 namespace dom {
 
-// For simplicity, we make sure these match, to avoid doing a conversion.
-COMPILE_ASSERT(
-    UIEventWithKeyState::kNoModifier ==
-            system_window::InputEvent::kNoModifier &&
-        UIEventWithKeyState::kAltKey == system_window::InputEvent::kAltKey &&
-        UIEventWithKeyState::kCtrlKey == system_window::InputEvent::kCtrlKey &&
-        UIEventWithKeyState::kMetaKey == system_window::InputEvent::kMetaKey &&
-        UIEventWithKeyState::kShiftKey == system_window::InputEvent::kShiftKey,
-    Mismatched_modifier_enums);
+void UIEventWithKeyState::InitUIEventWithKeyState(
+    const std::string& type, bool bubbles, bool cancelable,
+    const scoped_refptr<Window>& view, int32 detail,
+    const std::string& modifierslist) {
+  InitUIEvent(type, bubbles, cancelable, view, detail);
+
+  ctrl_key_ = false;
+  shift_key_ = false;
+  alt_key_ = false;
+  meta_key_ = false;
+
+  std::vector<std::string> modifiers;
+  base::SplitStringAlongWhitespace(modifierslist, &modifiers);
+
+  for (std::vector<std::string>::const_iterator it = modifiers.begin();
+       it != modifiers.end(); ++it) {
+    const std::string& modifier = *it;
+    if (modifier == "Alt") {
+      alt_key_ = true;
+    } else if (modifier == "Control") {
+      ctrl_key_ = true;
+    } else if (modifier == "Meta") {
+      meta_key_ = true;
+    } else if (modifier == "Shift") {
+      shift_key_ = true;
+    }
+  }
+}
+
+void UIEventWithKeyState::InitUIEventWithKeyState(
+    const std::string& type, bool bubbles, bool cancelable,
+    const scoped_refptr<Window>& view, int32 detail, bool ctrl_key,
+    bool alt_key, bool shift_key, bool meta_key) {
+  InitUIEvent(type, bubbles, cancelable, view, detail);
+
+  ctrl_key_ = ctrl_key;
+  shift_key_ = shift_key;
+  alt_key_ = alt_key;
+  meta_key_ = meta_key;
+}
 
 bool UIEventWithKeyState::GetModifierState(const std::string& keyArg) const {
   // Standard names of modifier keys defined here:
@@ -41,7 +75,6 @@
   } else if (keyArg == "Shift") {
     return shift_key();
   }
-
   return false;
 }
 
diff --git a/src/cobalt/dom/ui_event_with_key_state.h b/src/cobalt/dom/ui_event_with_key_state.h
index c9055bc..1fbc87c 100644
--- a/src/cobalt/dom/ui_event_with_key_state.h
+++ b/src/cobalt/dom/ui_event_with_key_state.h
@@ -17,7 +17,9 @@
 
 #include <string>
 
+#include "cobalt/dom/event_modifier_init.h"
 #include "cobalt/dom/ui_event.h"
+#include "cobalt/dom/ui_event_init.h"
 
 namespace cobalt {
 namespace dom {
@@ -28,30 +30,55 @@
  public:
   // Web API: KeyboardEvent, MouseEvent
   //
-  static const uint32 kNoModifier = 0;
-  static const uint32 kAltKey = 1 << 0;
-  static const uint32 kCtrlKey = 1 << 1;
-  static const uint32 kMetaKey = 1 << 2;
-  static const uint32 kShiftKey = 1 << 3;
+  bool ctrl_key() const { return ctrl_key_; }
+  bool shift_key() const { return shift_key_; }
+  bool alt_key() const { return alt_key_; }
+  bool meta_key() const { return meta_key_; }
 
-  uint32 modifiers() const { return modifiers_; }
-  void set_modifiers(uint32 modifiers) { modifiers_ = modifiers; }
-
-  bool alt_key() const { return (modifiers_ & kAltKey) != 0; }
-  bool ctrl_key() const { return (modifiers_ & kCtrlKey) != 0; }
-  bool meta_key() const { return (modifiers_ & kMetaKey) != 0; }
-  bool shift_key() const { return (modifiers_ & kShiftKey) != 0; }
+  void set_ctrl_key(bool value) { ctrl_key_ = value; }
+  void set_shift_key(bool value) { shift_key_ = value; }
+  void set_alt_key(bool value) { alt_key_ = value; }
+  void set_meta_key(bool value) { meta_key_ = value; }
 
   bool GetModifierState(const std::string& keyArg) const;
 
  protected:
   UIEventWithKeyState(base::Token type, Bubbles bubbles, Cancelable cancelable,
-                      uint32 modifiers)
-      : UIEvent(type, bubbles, cancelable), modifiers_(modifiers) {}
+                      const scoped_refptr<Window>& view)
+      : UIEvent(type, bubbles, cancelable, view),
+        ctrl_key_(false),
+        shift_key_(false),
+        alt_key_(false),
+        meta_key_(false) {}
+  UIEventWithKeyState(base::Token type, Bubbles bubbles, Cancelable cancelable,
+                      const scoped_refptr<Window>& view,
+                      const EventModifierInit& init_dict)
+      : UIEvent(type, bubbles, cancelable, view, init_dict) {
+    ctrl_key_ = init_dict.ctrl_key();
+    shift_key_ = init_dict.shift_key();
+    alt_key_ = init_dict.alt_key();
+    meta_key_ = init_dict.meta_key();
+  }
 
+  // Creates an event with its "initialized flag" unset.
+  explicit UIEventWithKeyState(UninitializedFlag uninitialized_flag)
+      : UIEvent(uninitialized_flag) {}
+
+  void InitUIEventWithKeyState(const std::string& type, bool bubbles,
+                               bool cancelable,
+                               const scoped_refptr<Window>& view, int32 detail,
+                               const std::string& modifierslist);
+  void InitUIEventWithKeyState(const std::string& type, bool bubbles,
+                               bool cancelable,
+                               const scoped_refptr<Window>& view, int32 detail,
+                               bool ctrl_key, bool alt_key, bool shift_key,
+                               bool meta_key);
   ~UIEventWithKeyState() OVERRIDE {}
 
-  uint32 modifiers_;
+  bool ctrl_key_;
+  bool shift_key_;
+  bool alt_key_;
+  bool meta_key_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/wheel_event.cc b/src/cobalt/dom/wheel_event.cc
new file mode 100644
index 0000000..7f991fe
--- /dev/null
+++ b/src/cobalt/dom/wheel_event.cc
@@ -0,0 +1,61 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/wheel_event.h"
+
+#include <string>
+
+#include "cobalt/dom/ui_event_with_key_state.h"
+
+namespace cobalt {
+namespace dom {
+
+WheelEvent::WheelEvent(const std::string& type)
+    : MouseEvent(type),
+      delta_x_(0),
+      delta_y_(0),
+      delta_z_(0),
+      delta_mode_(kDomDeltaPixel) {}
+WheelEvent::WheelEvent(const std::string& type, const WheelEventInit& init_dict)
+    : MouseEvent(type, init_dict),
+      delta_x_(init_dict.delta_x()),
+      delta_y_(init_dict.delta_y()),
+      delta_z_(init_dict.delta_z()),
+      delta_mode_(init_dict.delta_mode()) {}
+
+WheelEvent::WheelEvent(base::Token type, const scoped_refptr<Window>& view,
+                       const WheelEventInit& init_dict)
+    : MouseEvent(type, kBubbles, kCancelable, view, init_dict),
+      delta_x_(init_dict.delta_x()),
+      delta_y_(init_dict.delta_y()),
+      delta_z_(init_dict.delta_z()),
+      delta_mode_(init_dict.delta_mode()) {}
+
+void WheelEvent::InitWheelEvent(
+    const std::string& type, bool bubbles, bool cancelable,
+    const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
+    int32 screen_y, int32 client_x, int32 client_y, uint16 button,
+    const scoped_refptr<EventTarget>& related_target,
+    const std::string& modifierslist, double delta_x, double delta_y,
+    double delta_z, uint32 delta_mode) {
+  InitMouseEvent(type, bubbles, cancelable, view, detail, screen_x, screen_y,
+                 client_x, client_y, modifierslist, button, related_target);
+  delta_x_ = delta_x;
+  delta_y_ = delta_y;
+  delta_z_ = delta_z;
+  delta_mode_ = delta_mode;
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/wheel_event.h b/src/cobalt/dom/wheel_event.h
new file mode 100644
index 0000000..a421c84
--- /dev/null
+++ b/src/cobalt/dom/wheel_event.h
@@ -0,0 +1,76 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_WHEEL_EVENT_H_
+#define COBALT_DOM_WHEEL_EVENT_H_
+
+#include <string>
+
+#include "cobalt/base/token.h"
+#include "cobalt/dom/mouse_event.h"
+#include "cobalt/dom/wheel_event_init.h"
+
+namespace cobalt {
+namespace dom {
+
+// The WheelEvent provides specific contextual information associated with
+// wheel devices. Wheels are devices that can be rotated in one or more spatial
+// dimensions, and which can be associated with a pointer device
+//   https://www.w3.org/TR/2016/WD-uievents-20160804/#events-wheelevents
+class WheelEvent : public MouseEvent {
+ public:
+  // Web API: WheelEvent
+  // DeltaMode values as defined by Web API Node.nodeType.
+  typedef uint32 DeltaMode;  // Work around lack of strongly-typed enums
+                             // in C++03.
+  enum DeltaModeInternal {   // A name is given only to pacify the compiler.
+                             // Use |DeltaMode| instead.
+    kDomDeltaPixel = 0x00,
+    kDomDeltaLine = 0x01,
+    kDomDeltaPage = 0x02,
+  };
+
+  explicit WheelEvent(const std::string& type);
+  WheelEvent(const std::string& type, const WheelEventInit& init_dict);
+  WheelEvent(base::Token type, const scoped_refptr<Window>& view,
+             const WheelEventInit& init_dict);
+
+  double delta_x() const { return delta_x_; }
+  double delta_y() const { return delta_y_; }
+  double delta_z() const { return delta_z_; }
+  DeltaMode delta_mode() const { return delta_mode_; }
+
+  void InitWheelEvent(const std::string& type, bool bubbles, bool cancelable,
+                      const scoped_refptr<Window>& view, int32 detail,
+                      int32 screen_x, int32 screen_y, int32 client_x,
+                      int32 client_y, uint16 button,
+                      const scoped_refptr<EventTarget>& related_target,
+                      const std::string& modifierslist, double delta_x,
+                      double delta_y, double delta_z, uint32 delta_mode);
+
+  DEFINE_WRAPPABLE_TYPE(WheelEvent);
+
+ private:
+  double delta_x_;
+  double delta_y_;
+  double delta_z_;
+  DeltaMode delta_mode_;
+
+  ~WheelEvent() OVERRIDE {}
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_WHEEL_EVENT_H_
diff --git a/src/cobalt/dom/wheel_event.idl b/src/cobalt/dom/wheel_event.idl
new file mode 100644
index 0000000..c24ead0
--- /dev/null
+++ b/src/cobalt/dom/wheel_event.idl
@@ -0,0 +1,46 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/2016/WD-uievents-20160804/#interface-wheelevent
+// https://www.w3.org/TR/2016/WD-uievents-20160804/#idl-interface-WheelEvent-initializers
+
+[Constructor(DOMString type, optional WheelEventInit eventInitDict)]
+interface WheelEvent : MouseEvent {
+  // DeltaModeCode
+  const unsigned long DOM_DELTA_PIXEL = 0x00;
+  const unsigned long DOM_DELTA_LINE  = 0x01;
+  const unsigned long DOM_DELTA_PAGE  = 0x02;
+
+  readonly attribute double deltaX;
+  readonly attribute double deltaY;
+  readonly attribute double deltaZ;
+  readonly attribute unsigned long deltaMode;
+
+  void initWheelEvent(DOMString type,
+                   optional boolean bubbles = false,
+                   optional boolean cancelable = false,
+                   optional Window? view = null,
+                   optional long detail = 0,
+                   optional long screenX = 0,
+                   optional long screenY = 0,
+                   optional long clientX = 0,
+                   optional long clientY = 0,
+                   optional unsigned short button = 0,
+                   optional EventTarget? relatedTarget = null,
+                   optional DOMString modifiersList = "",
+                   optional double deltaX = 0,
+                   optional double deltaY = 0,
+                   optional double deltaZ = 0,
+                   optional unsigned long deltaMode = 0);
+};
\ No newline at end of file
diff --git a/src/cobalt/dom/wheel_event_init.idl b/src/cobalt/dom/wheel_event_init.idl
new file mode 100644
index 0000000..20451a5
--- /dev/null
+++ b/src/cobalt/dom/wheel_event_init.idl
@@ -0,0 +1,22 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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.w3.org/TR/uievents/#dictdef-wheeleventinit
+
+dictionary WheelEventInit : MouseEventInit {
+  double deltaX = 0.0;
+  double deltaY = 0.0;
+  double deltaZ = 0.0;
+  unsigned long deltaMode = 0;
+};
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 554ce06..7a089d4 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -24,6 +24,7 @@
 #include "cobalt/cssom/user_agent_style_sheet.h"
 #include "cobalt/dom/camera_3d.h"
 #include "cobalt/dom/console.h"
+#include "cobalt/dom/device_orientation_event.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/element.h"
@@ -31,13 +32,17 @@
 #include "cobalt/dom/history.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/html_element_context.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/location.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
 #include "cobalt/dom/navigator.h"
 #include "cobalt/dom/performance.h"
+#include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/screen.h"
 #include "cobalt/dom/storage.h"
+#include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom/window_timers.h"
 #include "cobalt/media_session/media_session_client.h"
 #include "cobalt/script/javascript_engine.h"
@@ -66,8 +71,10 @@
   DISALLOW_COPY_AND_ASSIGN(RelayLoadEvent);
 };
 
-Window::Window(int width, int height, cssom::CSSParser* css_parser,
-               Parser* dom_parser, loader::FetcherFactory* fetcher_factory,
+Window::Window(int width, int height,
+               base::ApplicationState initial_application_state,
+               cssom::CSSParser* css_parser, Parser* dom_parser,
+               loader::FetcherFactory* fetcher_factory,
                render_tree::ResourceProvider** resource_provider,
                loader::image::AnimatedImageTracker* animated_image_tracker,
                loader::image::ImageCache* image_cache,
@@ -107,7 +114,7 @@
           media_source_registry, resource_provider, animated_image_tracker,
           image_cache, reduced_image_cache_capacity_manager,
           remote_typeface_cache, mesh_cache, dom_stat_tracker, language,
-          video_playback_rate_multiplier)),
+          initial_application_state, video_playback_rate_multiplier)),
       performance_(new Performance(new base::SystemMonotonicClock())),
       ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
           html_element_context_.get(),
@@ -147,6 +154,7 @@
   test_runner_ = new TestRunner();
 #endif  // ENABLE_TEST_RUNNER
   document_->AddObserver(relay_on_load_event_.get());
+  html_element_context_->page_visibility_state()->AddObserver(this);
 
   if (system_window_) {
     SbWindow sb_window = system_window_->GetSbWindow();
@@ -166,6 +174,7 @@
   MessageLoop::current()->PostTask(
       FROM_HERE, base::Bind(&Window::StartDocumentLoad, this, fetcher_factory,
                             url, dom_parser, error_callback));
+  camera_3d_->StartOrientationEvents(base::AsWeakPtr(this));
 }
 
 void Window::StartDocumentLoad(
@@ -399,29 +408,45 @@
 
 void Window::InjectEvent(const scoped_refptr<Event>& event) {
   // Forward the event on to the correct object in DOM.
-  if (event->type() == base::Tokens::keydown() ||
-      event->type() == base::Tokens::keypress() ||
-      event->type() == base::Tokens::keyup()) {
+  if (event->GetWrappableType() == base::GetTypeId<KeyboardEvent>()) {
     // Event.target:focused element processing the key event or if no element
     // focused, then the body element if available, otherwise the root element.
-    //   https://www.w3.org/TR/DOM-Level-3-Events/#event-type-keydown
-    //   https://www.w3.org/TR/DOM-Level-3-Events/#event-type-keypress
-    //   https://www.w3.org/TR/DOM-Level-3-Events/#event-type-keyup
+    //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keydown
+    //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keypress
+    //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-keyup
     if (document_->active_element()) {
       document_->active_element()->DispatchEvent(event);
     } else {
       document_->DispatchEvent(event);
     }
+  } else if (event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
+             event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
+             event->GetWrappableType() == base::GetTypeId<WheelEvent>()) {
+    document_->QueuePointerEvent(event);
   } else {
-    NOTREACHED();
+    SB_NOTREACHED();
   }
 }
 
+void Window::SetApplicationState(base::ApplicationState state) {
+  html_element_context_->page_visibility_state()->SetApplicationState(state);
+}
+
 void Window::SetSynchronousLayoutCallback(
     const base::Closure& synchronous_layout_callback) {
   document_->set_synchronous_layout_callback(synchronous_layout_callback);
 }
 
+void Window::OnWindowFocusChanged(bool has_focus) {
+  DispatchEvent(
+      new Event(has_focus ? base::Tokens::focus() : base::Tokens::blur()));
+}
+
+void Window::OnVisibilityStateChanged(
+    page_visibility::VisibilityState visibility_state) {
+  UNREFERENCED_PARAMETER(visibility_state);
+}
+
 void Window::TraceMembers(script::Tracer* tracer) {
   tracer->Trace(performance_);
   tracer->Trace(document_);
@@ -436,7 +461,9 @@
   tracer->Trace(screen_);
 }
 
-Window::~Window() {}
+Window::~Window() {
+  html_element_context_->page_visibility_state()->RemoveObserver(this);
+}
 
 void Window::FireHashChangeEvent() {
   PostToDispatchEvent(FROM_HERE, base::Tokens::hashchange());
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 0fc144b..c405a0e 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -22,6 +22,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/timer.h"
+#include "cobalt/base/application_state.h"
 #include "cobalt/cssom/css_parser.h"
 #include "cobalt/cssom/css_style_declaration.h"
 #include "cobalt/dom/animation_frame_request_callback_list.h"
@@ -31,12 +32,10 @@
 #include "cobalt/dom/event_target.h"
 #include "cobalt/dom/media_query_list.h"
 #include "cobalt/dom/parser.h"
-#include "cobalt/dom/url_registry.h"
-#include "cobalt/network_bridge/cookie_jar.h"
-#include "cobalt/network_bridge/net_poster.h"
 #if defined(ENABLE_TEST_RUNNER)
 #include "cobalt/dom/test_runner.h"
 #endif  // ENABLE_TEST_RUNNER
+#include "cobalt/dom/url_registry.h"
 #include "cobalt/dom/window_timers.h"
 #include "cobalt/input/camera_3d.h"
 #include "cobalt/loader/decoder.h"
@@ -48,6 +47,9 @@
 #include "cobalt/loader/mesh/mesh_cache.h"
 #include "cobalt/media/can_play_type_handler.h"
 #include "cobalt/media/web_media_player_factory.h"
+#include "cobalt/network_bridge/cookie_jar.h"
+#include "cobalt/network_bridge/net_poster.h"
+#include "cobalt/page_visibility/page_visibility_state.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/execution_state.h"
@@ -89,7 +91,8 @@
 //   https://www.w3.org/TR/html5/browsers.html#the-window-object
 //
 // TODO: Properly handle viewport resolution change event.
-class Window : public EventTarget {
+class Window : public EventTarget,
+               public page_visibility::PageVisibilityState::Observer {
  public:
   typedef AnimationFrameRequestCallbackList::FrameRequestCallback
       FrameRequestCallback;
@@ -102,7 +105,8 @@
   typedef UrlRegistry<MediaSource> MediaSourceRegistry;
 
   Window(
-      int width, int height, cssom::CSSParser* css_parser, Parser* dom_parser,
+      int width, int height, base::ApplicationState initial_application_state,
+      cssom::CSSParser* css_parser, Parser* dom_parser,
       loader::FetcherFactory* fetcher_factory,
       render_tree::ResourceProvider** resource_provider,
       loader::image::AnimatedImageTracker* animated_image_tracker,
@@ -279,6 +283,16 @@
   void SetSynchronousLayoutCallback(
       const base::Closure& synchronous_layout_callback);
 
+  // Sets the current application state, forwarding on to the
+  // PageVisibilityState associated with it and its document, causing
+  // precipitate events to be dispatched.
+  void SetApplicationState(base::ApplicationState state);
+
+  // page_visibility::PageVisibilityState::Observer implementation.
+  void OnWindowFocusChanged(bool has_focus) OVERRIDE;
+  void OnVisibilityStateChanged(
+      page_visibility::VisibilityState visibility_state) OVERRIDE;
+
   void TraceMembers(script::Tracer* tracer) OVERRIDE;
 
   DEFINE_WRAPPABLE_TYPE(Window);
diff --git a/src/cobalt/dom/window_test.cc b/src/cobalt/dom/window_test.cc
index f087c28..5fa0db3 100644
--- a/src/cobalt/dom/window_test.cc
+++ b/src/cobalt/dom/window_test.cc
@@ -51,9 +51,9 @@
         stub_media_module_(new media::MediaModuleStub()),
         url_("about:blank"),
         window_(new Window(
-            1920, 1080, css_parser_.get(), dom_parser_.get(),
-            fetcher_factory_.get(), NULL, NULL, NULL, NULL, NULL, NULL,
-            &local_storage_database_, stub_media_module_.get(),
+            1920, 1080, base::kApplicationStateStarted, css_parser_.get(),
+            dom_parser_.get(), fetcher_factory_.get(), NULL, NULL, NULL, NULL,
+            NULL, NULL, &local_storage_database_, stub_media_module_.get(),
             stub_media_module_.get(), NULL, NULL, NULL, NULL, NULL, url_, "",
             "en-US", base::Callback<void(const GURL &)>(),
             base::Bind(&MockErrorCallback::Run,
diff --git a/src/cobalt/dom/window_timers.cc b/src/cobalt/dom/window_timers.cc
index 792ae47..265d0db 100644
--- a/src/cobalt/dom/window_timers.cc
+++ b/src/cobalt/dom/window_timers.cc
@@ -18,6 +18,8 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/debug/trace_event.h"
+#include "cobalt/dom/global_stats.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
@@ -85,8 +87,13 @@
 }
 
 void WindowTimers::RunTimerCallback(int handle) {
+  TRACE_EVENT0("cobalt::dom", "WindowTimers::RunTimerCallback");
   Timers::iterator timer = timers_.find(handle);
   DCHECK(timer != timers_.end());
+
+  // The callback is now being run. Track it in the global stats.
+  GlobalStats::GetInstance()->StartJavaScriptEvent();
+
   // Keep a |TimerInfo| reference, so it won't be released when running the
   // callback.
   scoped_refptr<TimerInfo> timer_info = timer->second;
@@ -99,6 +106,9 @@
   if (timer != timers_.end() && !timer->second->timer()->IsRunning()) {
     timers_.erase(timer);
   }
+
+  // The callback has finished running. Stop tracking it in the global stats.
+  GlobalStats::GetInstance()->StopJavaScriptEvent();
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom_parser/dom_parser.gyp b/src/cobalt/dom_parser/dom_parser.gyp
index f64e8d9..99775c7 100644
--- a/src/cobalt/dom_parser/dom_parser.gyp
+++ b/src/cobalt/dom_parser/dom_parser.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc
index 367607d..d4e89bb 100644
--- a/src/cobalt/dom_parser/html_decoder_test.cc
+++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -68,9 +68,9 @@
           &fetcher_factory_, &stub_css_parser_, dom_parser_.get(),
           NULL /* can_play_type_handler */, NULL /* web_media_player_factory */,
           &stub_script_runner_, NULL /* script_value_factory */, NULL, NULL,
-          NULL, NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), ""),
-      document_(
-          new dom::Document(&html_element_context_, dom::Document::Options())),
+          NULL, NULL, NULL, NULL, NULL, dom_stat_tracker_.get(), "",
+          base::kApplicationStateStarted),
+      document_(new dom::Document(&html_element_context_)),
       root_(new dom::Element(document_, base::Token("element"))),
       source_location_(base::SourceLocation("[object HTMLDecoderTest]", 1, 1)) {
 }
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
index 5b7e6bc..7d2fbd9 100644
--- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc
+++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -215,6 +215,11 @@
   if (node_stack_.empty()) {
     LOG(WARNING) << "OnEndDocument is called without OnStartDocument.";
   } else {
+    while (parent_node_ != node_stack_.top()) {
+      LOG(WARNING) << "some elements did not get called on OnEndElement()";
+      node_stack_.pop();
+    }
+    DCHECK_GT(node_stack_.size(), static_cast<uint64_t>(0));
     DCHECK_EQ(parent_node_, node_stack_.top());
     node_stack_.pop();
   }
diff --git a/src/cobalt/dom_parser/parser.cc b/src/cobalt/dom_parser/parser.cc
index be0da9c..03150ea 100644
--- a/src/cobalt/dom_parser/parser.cc
+++ b/src/cobalt/dom_parser/parser.cc
@@ -28,7 +28,7 @@
     const base::SourceLocation& input_location) {
   DCHECK(html_element_context);
   scoped_refptr<dom::Document> document =
-      new dom::Document(html_element_context, dom::Document::Options());
+      new dom::Document(html_element_context);
   HTMLDecoder html_decoder(document, document, NULL, dom_max_element_depth_,
                            input_location, base::Closure(), error_callback_,
                            false);
diff --git a/src/cobalt/fetch/embedded_scripts/fetch.js b/src/cobalt/fetch/embedded_scripts/fetch.js
index 48db9cd..b602982 100644
--- a/src/cobalt/fetch/embedded_scripts/fetch.js
+++ b/src/cobalt/fetch/embedded_scripts/fetch.js
@@ -1,14 +1,20 @@
-'use strict';(function(e){function n(a){"string"!==typeof a&&(a=String(a));if(/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(a))throw new g("Invalid character in header field name");return a.toLowerCase()}function x(a){"string"!==typeof a&&(a=String(a));var b;var c=0;for(b=a.length;c<b;c++){var d=a.charCodeAt(c);if(9!==d&&10!==d&&13!==d&&32!==d)break}for(b=a.length-1;b>c&&(d=a.charCodeAt(b),9===d||10===d||13===d||32===d);b--);a=a.substring(c,b+1);c=0;for(b=a.length;c<b;c++)if(d=a.charCodeAt(c),256<=d||0===d||
-10===d||13===d)throw new g("Invalid character in header field value");return a}function f(a){this.map=new r;if(void 0!==a){if(null===a||"object"!==typeof a)throw new g("Constructing Headers with invalid parameters");a instanceof f?a.forEach(function(a,c){this.append(c,a)},this):m.isArray(a)?a.forEach(function(a){if(2!==a.length)throw new g("Constructing Headers with invalid parameters");this.append(a[0],a[1])},this):Object.getOwnPropertyNames(a).forEach(function(b){this.append(b,a[b])},this)}}function t(a){if(a.bodyUsed)return u(new g("Body already read"));
-if(null===a.body)return C(new k(0));if(D(a.body))return u(new g("ReadableStream already locked"));var b=a.body.getReader(),c=[],d=0;return b.read().then(function p(a){if(a.done){if(0===c.length)a=new k(0);else if(1===c.length)a=new k(c[0]);else{a=new k(d);for(var e=0,f=c.length,h=0;e<f;e++)a.set(c[e],h),h+=c[e].length}return a}return a.value instanceof k?(d+=a.value.length,c.push(a.value),b.read().then(p)):u(new g("Invalid stream read value type"))})}function E(a){a=unescape(encodeURIComponent(a));
-for(var b=new k(a.length),c=0,d=a.length;c<d;c++)b[c]=a.charCodeAt(c);return b}function y(){this._initBody=function(a){this._bodyUsed=!1;this.body=null===a||void 0===a?null:a instanceof v?a:new v({start:function(b){if(a)if("string"===typeof a)b.enqueue(E(a));else if(z.prototype.isPrototypeOf(a))b.enqueue(new k(a));else if(F(a))b.enqueue(new k(a.buffer));else throw new g("Unsupported BodyInit type");b.close()}});this.headers.get("content-type")||"string"===typeof a&&this.headers.set("content-type",
-"text/plain;charset=UTF-8")};Object.defineProperty(this,"bodyUsed",{get:function(){return this._bodyUsed?!0:this.body?!!G(this.body):!1}});this.arrayBuffer=function(){return t(this).then(function(a){return a.buffer})};this.text=function(){return t(this).then(function(a){return 0===a.length?"":decodeURIComponent(escape(String.fromCharCode.apply(null,a)))})};this.json=function(){return this.text().then(JSON.parse)};return this}function q(a,b){b=b||{};var c=b.body;if(a instanceof q){if(a.bodyUsed)throw new g("Request body already read");
-this.url=a.url;this.credentials=a.credentials;b.headers||(this.headers=new f(a.headers));this.method=a.method;this.mode=a.mode;c||null===a.body||(c=a.body,a._bodyUsed=!0)}else this.url=String(a);this.credentials=b.credentials||this.credentials||"omit";if(b.headers||!this.headers)this.headers=new f(b.headers);a=b.method||this.method||"GET";var d=a.toUpperCase();this.method=-1<H.indexOf(d)?d:a;this.mode=b.mode||this.mode||null;this.referrer=null;if(("GET"===this.method||"HEAD"===this.method)&&c)throw new g("Body not allowed for GET or HEAD requests");
-this._initBody(c)}function I(a){var b=new f;a.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(a){var c=a.split(":");if(a=c.shift().trim())c=c.join(":").trim(),b.append(a,c)});return b}function l(a,b){b||(b={});this.type="default";this.status="status"in b?b.status:200;if(200>this.status||599<this.status)throw new A("Invalid response status");this.ok=200<=this.status&&300>this.status;if("statusText"in b){var c=b.statusText;for(var d=0,e=c.length,p;d<e;d++)if(p=c.charCodeAt(d),9!==p&&(32>
-p||255<p||127===p))throw g("Invalid status text");}else c="OK";this.statusText=c;this.headers=new f(b.headers);this.url=b.url||"";if(a&&-1<J.indexOf(this.status))throw new g("Body not allowed with a null body status");this._initBody(a)}if(!e.fetch){var m=e.Array,z=e.ArrayBuffer,K=e.Symbol.iterator,r=e.Map,A=e.RangeError,g=e.TypeError,k=e.Uint8Array,w=e.Promise,u=w.reject,C=w.resolve,v=e.ReadableStream,B=e.ReadableStreamTee,G=e.IsReadableStreamDisturbed,D=e.IsReadableStreamLocked,L="[object Int8Array];[object Uint8Array];[object Uint8ClampedArray];[object Int16Array];[object Uint16Array];[object Int32Array];[object Uint32Array];[object Float32Array];[object Float64Array]".split(";"),
-F=z.isView||function(a){return a&&-1<L.indexOf(Object.prototype.toString.call(a))};f.prototype.append=function(a,b){if(2!==arguments.length)throw g("Invalid parameters to append");a=n(a);b=x(b);this.map.has(a)?this.map.set(a,this.map.get(a)+", "+b):this.map.set(a,b)};f.prototype["delete"]=function(a){if(1!==arguments.length)throw g("Invalid parameters to delete");this.map.delete(n(a))};f.prototype.get=function(a){if(1!==arguments.length)throw g("Invalid parameters to get");a=n(a);var b=this.map.get(a);
-return void 0!==b?b:null};f.prototype.has=function(a){if(1!==arguments.length)throw g("Invalid parameters to has");return this.map.has(n(a))};f.prototype.set=function(a,b){if(2!==arguments.length)throw g("Invalid parameters to set");this.map.set(n(a),x(b))};f.prototype.forEach=function(a,b){var c=this;m.from(this.map.entries()).sort().forEach(function(d){a.call(b,d[1],d[0],c)})};f.prototype.keys=function(){return(new r(m.from(this.map.entries()).sort())).keys()};f.prototype.values=function(){return(new r(m.from(this.map.entries()).sort())).values()};
-f.prototype.entries=function(){return(new r(m.from(this.map.entries()).sort())).entries()};f.prototype[K]=f.prototype.entries;var H="DELETE GET HEAD OPTIONS POST PUT".split(" ");q.prototype.clone=function(){var a=null;null!==this.body&&(a=B(this.body,!0),this.body=a[0],a=a[1]);return new q(this,{body:a})};y.call(q.prototype);var J=[101,204,205,304];y.call(l.prototype);l.prototype.clone=function(){var a=null;null!==this.body&&(a=B(this.body,!0),this.body=a[0],a=a[1]);return new l(a,{status:this.status,
-statusText:this.statusText,headers:new f(this.headers),url:this.url})};l.error=function(){var a=new l(null);a.type="error";a.status=0;a.statusText="";return a};var M=[301,302,303,307,308];l.redirect=function(a,b){if(!FetchInternal.IsUrlValid(a))throw new g("Invalid URL");void 0===b&&(b=302);if(-1===M.indexOf(b))throw new A("Invalid status code");return new l(null,{status:b,headers:{location:a}})};e.Headers=f;e.Request=q;e.Response=l;e.fetch=function(a,b){return new w(function(c,d){var e=!1,f=new q(a,
-b),h=new XMLHttpRequest,k=null,n=new v({start:function(a){k=a},cancel:function(a){e=!0;h.abort()}});h.onload=function(){k.close()};h.onreadystatechange=function(){if(h.readyState===h.HEADERS_RECEIVED){var a={status:h.status,statusText:h.statusText,headers:I(h.getAllResponseHeaders()||"")};a.url="responseURL"in h?h.responseURL:a.headers.get("X-Request-URL");c(new l(n,a))}};h.onerror=function(){k.error(new g("Network request failed"));d(new g("Network request failed"))};h.ontimeout=function(){k.error(new g("Network request failed"));
-d(new g("Network request failed"))};h.open(f.method,f.url,!0);"include"===f.credentials&&(h.withCredentials=!0);f.headers.forEach(function(a,b){h.setRequestHeader(b,a)});var m=function(a){e||k.enqueue(a)};null===f.body?h.fetch(m,null):t(f).then(function(a){h.fetch(m,a)})})};e.fetch.polyfill=!0}})(this);
\ No newline at end of file
+'use strict';(function(d){function B(a){"string"!==typeof a&&(a=String(a));if(/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(a))throw new e("Invalid character in header field name");return a.toLowerCase()}function P(a){"string"!==typeof a&&(a=String(a));var b;var c=0;for(b=a.length;c<b;c++){var g=a.charCodeAt(c);if(9!==g&&10!==g&&13!==g&&32!==g)break}for(b=a.length-1;b>c&&(g=a.charCodeAt(b),9===g||10===g||13===g||32===g);b--);a=a.substring(c,b+1);c=0;for(b=a.length;c<b;c++)if(g=a.charCodeAt(c),256<=g||0===g||
+10===g||13===g)throw new e("Invalid character in header field value");return a}function V(a,b){throw new e("Immutable header cannot be modified");}function W(a,b){return!1}function X(a,b){a=a.toLowerCase();return-1<Y.indexOf(a)||a.startsWith("proxy-")||a.startsWith("sec-")?!0:!1}function Z(a,b){a=a.toLowerCase();return-1<aa.indexOf(a)||"content-type"===a&&(b=b.split(";")[0].toLowerCase(),-1<ba.indexOf(b))?!1:!0}function I(a,b){return-1<ca.indexOf(a.toLowerCase())?!0:!1}function h(a){this[l]=new F;
+void 0===this[t]&&(this[t]=W);if(void 0!==a){if(null===a||"object"!==typeof a)throw new e("Constructing Headers with invalid parameters");a instanceof h?a.forEach(function(a,c){this.append(c,a)},this):C.isArray(a)?a.forEach(function(a){if(2!==a.length)throw new e("Constructing Headers with invalid parameters");this.append(a[0],a[1])},this):Object.getOwnPropertyNames(a).forEach(function(b){this.append(b,a[b])},this)}}function D(a,b){var c=da(h.prototype);c[t]=b;h.call(c,a);return c}function J(a){if(a.bodyUsed)return K(new e("Body was already read"));
+if(null===a.body)return ea(new u(0));if(fa(a.body))return K(new e("ReadableStream was already locked"));var b=a.body.getReader(),c=[],g=0;return b.read().then(function ga(a){if(a.done){if(0===c.length)a=new u(0);else if(1===c.length)a=new u(c[0]);else{a=new u(g);for(var d=0,k=c.length,f=0;d<k;d++)a.set(c[d],f),f+=c[d].length}return a}return a.value instanceof u?(g+=a.value.length,c.push(a.value),b.read().then(ga)):K(new e("Invalid stream data type"))})}function Q(){this._initBody=function(a){this[L]=
+!1;this[m]=null===a||void 0===a?null:a instanceof M?a:new M({start:function(b){if(a)if("string"===typeof a)b.enqueue(FetchInternal.encodeToUTF8(a));else if(R.prototype.isPrototypeOf(a))b.enqueue(new u(a));else if(ha(a))b.enqueue(new u(a.buffer));else throw new e("Unsupported BodyInit type");b.close()}});this[r].get("content-type")||"string"===typeof a&&this[r].set("content-type","text/plain;charset=UTF-8")};N(this,{body:{get:function(){return this[m]}},bodyUsed:{get:function(){return this[L]?!0:this[m]?
+!!ia(this[m]):!1}}});this.arrayBuffer=function(){return J(this).then(function(a){return a.buffer})};this.text=function(){return J(this).then(function(a){return FetchInternal.decodeFromUTF8(a)})};this.json=function(){return this.text().then(JSON.parse)};return this}function v(a,b){var c=void 0!==b&&null!==b&&void 0===b.cloneBody;b=b||{};var g=b.body||b.cloneBody,d=b.headers;if(a instanceof v){if(a.bodyUsed)throw new e("Request body was already read");this[w]=a.url;this[y]=a.cache;this[z]=a.credentials;
+void 0===d&&(d=a.headers);this[A]=a.integrity;this[x]=a.method;this[n]=a.mode;c&&"navigate"===this[n]&&(this[n]="same-origin");this[E]=a.redirect;g||null===a.body||(g=a.body,a[L]=!0)}else{this[w]=String(a);if(!FetchInternal.isUrlValid(this[w],!1))throw new e("Invalid request URL");this[n]="cors";this[z]="omit"}if(void 0!==b.window&&null!==b.window)throw new e("Invalid request window");this[y]=b.cache||this[y]||"default";if(-1===ja.indexOf(this[y]))throw new e("Invalid request cache mode");this[z]=
+b.credentials||this[z]||"omit";if(-1===ka.indexOf(this[z]))throw new e("Invalid request credentials");void 0!==b.integrity?this[A]=b.integrity:void 0===this[A]&&(this[A]="");a=(b.method||this[x]||"GET").toUpperCase();if(-1===la.indexOf(a))throw new e("Invalid request method");this[x]=a;if(b.mode&&-1===ma.indexOf(b.mode))throw new e("Invalid request mode");this[n]=b.mode||this[n]||"no-cors";if("no-cors"===this[n]){if(-1===na.indexOf(this[x]))throw new e("Invalid request method for no-cors");if(""!==
+this[A])throw new e("Request integrity data is not allowed with no-cors");}if("same-origin"!==this[n]&&"only-if-cached"===this[y])throw new e("Request mode must be same-origin for only-if-cached");this[E]=b.redirect||this[E]||"follow";if(-1===oa.indexOf(this[E]))throw new e("Invalid request redirect mode");this[r]="no-cors"===this[n]?D(d,Z):D(d,X);if(("GET"===this[x]||"HEAD"===this[x])&&g)throw new e("Request body is not allowed for GET or HEAD");this._initBody(g)}function pa(a,b){var c=D(void 0,
+b);a.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(a){var b=a.split(":");if(a=b.shift().trim())b=b.join(":").trim(),c.append(a,b)});return c}function p(a,b){b||(b={});this[G]="default";this[q]="status"in b?b.status:200;if(200>this[q]||599<this[q])throw new S("Invalid response status");this[T]=200<=this[q]&&300>this[q];if("statusText"in b){var c=b.statusText;for(var g=0,d=c.length,f;g<d;g++)if(f=c.charCodeAt(g),9!==f&&(32>f||255<f||127===f))throw e("Invalid response status text");}else c=
+"OK";this[H]=c;this[r]=D(b.headers,I);this[w]=b.url||"";if(a&&-1<qa.indexOf(this[q]))throw new e("Response body is not allowed with a null body status");this._initBody(a)}if(!d.fetch){var C=d.Array,R=d.ArrayBuffer,da=d.Object.create,N=d.Object.defineProperties,f=d.Symbol,ra=f.iterator,F=d.Map,S=d.RangeError,e=d.TypeError,u=d.Uint8Array,O=d.Promise,K=O.reject,ea=O.resolve,M=d.ReadableStream,U=d.ReadableStreamTee,ia=d.IsReadableStreamDisturbed,fa=d.IsReadableStreamLocked,m=f("body"),L=f("bodyUsed"),
+y=f("cache"),z=f("credentials"),t=f("guardCallback"),r=f("headers"),A=f("integrity"),l=f("map"),x=f("method"),n=f("mode"),T=f("ok"),E=f("redirect"),q=f("status"),H=f("statusText"),G=f("type"),w=f("url"),Y="accept-charset accept-encoding access-control-request-headers access-control-request-method connection content-length cookie cookie2 date dnt expect host keep-alive origin referer te trailer transfer-encoding upgrade via".split(" "),ca=["set-cookie","set-cookie2"],aa=["accept","accept-language",
+"content-language"],ba=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],ja="default no-store reload no-cache force-cache only-if-cached".split(" "),ka=["omit","same-origin","include"],la="DELETE GET HEAD OPTIONS POST PUT".split(" "),na=["GET","HEAD","POST"],ma=["same-origin","no-cors","cors"],oa=["follow","error","manual"],qa=[101,204,205,304],sa=[301,302,303,307,308],ta="[object Int8Array];[object Uint8Array];[object Uint8ClampedArray];[object Int16Array];[object Uint16Array];[object Int32Array];[object Uint32Array];[object Float32Array];[object Float64Array]".split(";"),
+ha=R.isView||function(a){return a&&-1<ta.indexOf(Object.prototype.toString.call(a))};h.prototype.append=function(a,b){if(2!==arguments.length)throw e("Invalid parameters to append");a=B(a);b=P(b);this[t](a,b)||(this[l].has(a)?this[l].set(a,this[l].get(a)+", "+b):this[l].set(a,b))};h.prototype["delete"]=function(a){if(1!==arguments.length)throw e("Invalid parameters to delete");this[t](a,"invalid")||this[l].delete(B(a))};h.prototype.get=function(a){if(1!==arguments.length)throw e("Invalid parameters to get");
+a=B(a);var b=this[l].get(a);return void 0!==b?b:null};h.prototype.has=function(a){if(1!==arguments.length)throw e("Invalid parameters to has");return this[l].has(B(a))};h.prototype.set=function(a,b){if(2!==arguments.length)throw e("Invalid parameters to set");a=B(a);b=P(b);this[t](a,b)||this[l].set(a,b)};h.prototype.forEach=function(a,b){var c=this;C.from(this[l].entries()).sort().forEach(function(e){a.call(b,e[1],e[0],c)})};h.prototype.keys=function(){return(new F(C.from(this[l].entries()).sort())).keys()};
+h.prototype.values=function(){return(new F(C.from(this[l].entries()).sort())).values()};h.prototype.entries=function(){return(new F(C.from(this[l].entries()).sort())).entries()};h.prototype[ra]=h.prototype.entries;v.prototype.clone=function(){var a=null;null!==this[m]&&(a=U(this[m],!0),this[m]=a[0],a=a[1]);return new v(this,{cloneBody:a})};N(v.prototype,{cache:{get:function(){return this[y]}},credentials:{get:function(){return this[z]}},headers:{get:function(){return this[r]}},integrity:{get:function(){return this[A]}},
+method:{get:function(){return this[x]}},mode:{get:function(){return this[n]}},redirect:{get:function(){return this[E]}},url:{get:function(){return this[w]}}});Q.call(v.prototype);Q.call(p.prototype);p.prototype.clone=function(){var a=null;null!==this[m]&&(a=U(this[m],!0),this[m]=a[0],a=a[1]);return new p(a,{status:this[q],statusText:this[H],headers:D(this[r],I),url:this[w]})};N(p.prototype,{headers:{get:function(){return this[r]}},ok:{get:function(){return this[T]}},status:{get:function(){return this[q]}},
+statusText:{get:function(){return this[H]}},type:{get:function(){return this[G]}},url:{get:function(){return this[w]}}});p.error=function(){var a=new p(null);a[r][t]=V;a[G]="error";a[q]=0;a[H]="";return a};p.redirect=function(a,b){if(!FetchInternal.isUrlValid(a,!0))throw new e("Invalid URL for response redirect");void 0===b&&(b=302);if(-1===sa.indexOf(b))throw new S("Invalid status code for response redirect");return new p(null,{status:b,headers:{location:a}})};d.Headers=h;d.Request=v;d.Response=
+p;d.fetch=function(a,b){return new O(function(c,d){var g=!1,f=new v(a,b),k=new XMLHttpRequest,h=null,l=new M({start:function(a){h=a},cancel:function(a){g=!0;k.abort()}});k.onload=function(){h.close()};k.onreadystatechange=function(){if(k.readyState===k.HEADERS_RECEIVED){var a={status:k.status,statusText:k.statusText,headers:pa(k.getAllResponseHeaders()||"",I)};a.url="responseURL"in k?k.responseURL:a.headers.get("X-Request-URL");try{var b=new p(l,a);b[G]="basic";c(b)}catch(ua){d(ua)}}};k.onerror=function(){h.error(new e("Network request failed"));
+d(new e("Network request failed"))};k.ontimeout=function(){h.error(new e("Network request failed"));d(new e("Network request failed"))};k.open(f.method,f.url,!0);"include"===f.credentials&&(k.withCredentials=!0);f.headers.forEach(function(a,b){k.setRequestHeader(b,a)});var m=function(a){g||h.enqueue(a)};null===f.body?k.fetch(m,null):J(f).then(function(a){k.fetch(m,a)})})};d.fetch.polyfill=!0}})(this);
\ No newline at end of file
diff --git a/src/cobalt/fetch/fetch.js b/src/cobalt/fetch/fetch.js
index 6248427..b1971e3 100644
--- a/src/cobalt/fetch/fetch.js
+++ b/src/cobalt/fetch/fetch.js
@@ -37,8 +37,11 @@
 
   const Array = self.Array
   const ArrayBuffer = self.ArrayBuffer
+  const create = self.Object.create
+  const defineProperties = self.Object.defineProperties
   const Error = self.Error
-  const Symbol_iterator = self.Symbol.iterator
+  const Symbol = self.Symbol
+  const Symbol_iterator = Symbol.iterator
   const Map = self.Map
   const RangeError = self.RangeError
   const TypeError = self.TypeError
@@ -53,10 +56,105 @@
   const IsReadableStreamDisturbed = self.IsReadableStreamDisturbed
   const IsReadableStreamLocked = self.IsReadableStreamLocked
 
-  const err_InvalidHeadersInit = 'Constructing Headers with invalid parameters'
-  const err_NetworkRequestFailed = 'Network request failed'
+  const ERROR_INVALID_HEADERS_INIT =
+      'Constructing Headers with invalid parameters'
+  const ERROR_NETWORK_REQUEST_FAILED = 'Network request failed'
 
-  const viewClasses = [
+  // Internal slots for objects.
+  const BODY_SLOT = Symbol('body')
+  const BODY_USED_SLOT = Symbol('bodyUsed')
+  const CACHE_SLOT = Symbol('cache')
+  const CREDENTIALS_SLOT = Symbol('credentials')
+  const GUARD_CALLBACK_SLOT = Symbol('guardCallback')
+  const HEADERS_SLOT = Symbol('headers')
+  const INTEGRITY_SLOT = Symbol('integrity')
+  const MAP_SLOT = Symbol('map')
+  const METHOD_SLOT = Symbol('method')
+  const MODE_SLOT = Symbol('mode')
+  const OK_SLOT = Symbol('ok')
+  const REDIRECT_SLOT = Symbol('redirect')
+  const STATUS_SLOT = Symbol('status')
+  const STATUS_TEXT_SLOT = Symbol('statusText')
+  const TYPE_SLOT = Symbol('type')
+  const URL_SLOT = Symbol('url')
+
+  // Forbidden headers corresponding to various header guard types.
+  const INVALID_HEADERS_REQUEST = [
+    'accept-charset',
+    'accept-encoding',
+    'access-control-request-headers',
+    'access-control-request-method',
+    'connection',
+    'content-length',
+    'cookie',
+    'cookie2',
+    'date',
+    'dnt',
+    'expect',
+    'host',
+    'keep-alive',
+    'origin',
+    'referer',    // [sic]
+    'te',
+    'trailer',
+    'transfer-encoding',
+    'upgrade',
+    'via'
+  ]
+
+  const INVALID_HEADERS_RESPONSE = [
+    'set-cookie',
+    'set-cookie2'
+  ]
+
+  // The header guard for no-cors requests is special. Only these headers are
+  // allowed. And only certain values for content-type are allowed.
+  const VALID_HEADERS_NOCORS = [
+    'accept',
+    'accept-language',
+    'content-language'
+    // 'content-type' is treated specially.
+  ]
+
+  const VALID_HEADERS_NOCORS_CONTENT_TYPE = [
+    'application/x-www-form-urlencoded',
+    'multipart/form-data',
+    'text/plain'
+  ]
+
+  // Values that are allowed for Request cache mode.
+  const VALID_CACHE_MODES = [
+    'default',
+    'no-store',
+    'reload',
+    'no-cache',
+    'force-cache',
+    'only-if-cached'
+  ]
+
+  // Values that are allowed for Request credentials.
+  const VALID_CREDENTIALS = ['omit', 'same-origin', 'include']
+
+  // HTTP methods whose capitalization should be normalized.
+  const VALID_METHODS = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
+
+  // Methods that are allowed for Request mode: no-cors.
+  const VALID_METHODS_NOCORS = ['GET', 'HEAD', 'POST']
+
+  // Modes that are allowed for RequestInit. Although 'navigate' is a valid
+  // request mode, it is not allowed in the RequestInit parameter.
+  const VALID_MODES = ['same-origin', 'no-cors', 'cors']
+
+  // Values that are allowed for Request redirect mode.
+  const VALID_REDIRECT_MODES = ['follow', 'error', 'manual']
+
+  // Body is not allowed in responses with a null body status.
+  const NULL_BODY_STATUSES = [101, 204, 205, 304 ]
+
+  // Statuses that are allowed for Response.redirect.
+  const REDIRECT_STATUSES = [301, 302, 303, 307, 308]
+
+  const ARRAY_BUFFER_VIEW_CLASSES = [
     '[object Int8Array]',
     '[object Uint8Array]',
     '[object Uint8ClampedArray]',
@@ -69,7 +167,8 @@
   ]
 
   var isArrayBufferView = ArrayBuffer.isView || function(obj) {
-    return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
+    return obj && ARRAY_BUFFER_VIEW_CLASSES.indexOf(
+        Object.prototype.toString.call(obj)) > -1
   }
 
   function normalizeName(name) {
@@ -120,44 +219,98 @@
     return value
   }
 
-  // https://fetch.spec.whatwg.org/#headers-class
-  function Headers(headers) {
-    this.map = new Map();
+  // Callbacks to determine whether a given header name & value are forbidden
+  // for various header guard types.
+  function guardImmutableCallback(name, value) {
+    throw new TypeError('Immutable header cannot be modified')
+  }
 
-    if (headers === undefined) {
+  function guardNoneCallback(name, value) {
+    return false
+  }
+
+  function guardRequestCallback(name, value) {
+    var nameLower = name.toLowerCase()
+    if (INVALID_HEADERS_REQUEST.indexOf(nameLower) > -1 ||
+        nameLower.startsWith('proxy-') || nameLower.startsWith('sec-')) {
+      return true
+    }
+    return false
+  }
+
+  function guardRequestNoCorsCallback(name, value) {
+    var nameLower = name.toLowerCase()
+    if (VALID_HEADERS_NOCORS.indexOf(nameLower) > -1) {
+      return false
+    }
+    if (nameLower === 'content-type') {
+      var mimeType = value.split(';')[0].toLowerCase()
+      if (VALID_HEADERS_NOCORS_CONTENT_TYPE.indexOf(mimeType) > -1) {
+        return false
+      }
+    }
+    return true
+  }
+
+  function guardResponseCallback(name, value) {
+    if (INVALID_HEADERS_RESPONSE.indexOf(name.toLowerCase()) > -1) {
+      return true
+    }
+    return false
+  }
+
+  // https://fetch.spec.whatwg.org/#headers-class
+  function Headers(init) {
+    this[MAP_SLOT] = new Map();
+    if (this[GUARD_CALLBACK_SLOT] === undefined) {
+      this[GUARD_CALLBACK_SLOT] = guardNoneCallback
+    }
+
+    if (init === undefined) {
       return
-    } else if (headers === null || typeof headers !== 'object') {
-      throw new TypeError(err_InvalidHeadersInit)
-    } else if (headers instanceof Headers) {
-      // Should use for..of in case |headers| has a custom Symbol.iterator.
-      // However, this results in the ClosureCompiler polyfilling Symbol.
-      headers.forEach(function(value, name) {
+    } else if (init === null || typeof init !== 'object') {
+      throw new TypeError(ERROR_INVALID_HEADERS_INIT)
+    } else if (init instanceof Headers) {
+      // TODO: Use for..of in case |init| has a custom Symbol.iterator.
+      // However, this results in the ClosureCompiler polyfilling Symbol, so
+      // use forEach until ClosureCompiler supports ES6 output.
+      init.forEach(function(value, name) {
         this.append(name, value)
       }, this)
-    } else if (Array.isArray(headers)) {
-      headers.forEach(function(header) {
+    } else if (Array.isArray(init)) {
+      init.forEach(function(header) {
         if (header.length !== 2) {
-          throw new TypeError(err_InvalidHeadersInit)
+          throw new TypeError(ERROR_INVALID_HEADERS_INIT)
         }
         this.append(header[0], header[1])
       }, this)
     } else {
-      Object.getOwnPropertyNames(headers).forEach(function(name) {
-        this.append(name, headers[name])
+      Object.getOwnPropertyNames(init).forEach(function(name) {
+        this.append(name, init[name])
       }, this)
     }
   }
 
+  function CreateHeadersWithGuard(headersInit, guardCallback) {
+    var headers = create(Headers.prototype)
+    headers[GUARD_CALLBACK_SLOT] = guardCallback
+    Headers.call(headers, headersInit)
+    return headers
+  }
+
   Headers.prototype.append = function(name, value) {
     if (arguments.length !== 2) {
       throw TypeError('Invalid parameters to append')
     }
     name = normalizeName(name)
     value = normalizeValue(value)
-    if (this.map.has(name)) {
-      this.map.set(name, this.map.get(name) + ', ' + value)
+    if (this[GUARD_CALLBACK_SLOT](name, value)) {
+      return
+    }
+    if (this[MAP_SLOT].has(name)) {
+      this[MAP_SLOT].set(name, this[MAP_SLOT].get(name) + ', ' + value)
     } else {
-      this.map.set(name, value)
+      this[MAP_SLOT].set(name, value)
     }
   }
 
@@ -165,7 +318,10 @@
     if (arguments.length !== 1) {
       throw TypeError('Invalid parameters to delete')
     }
-    this.map.delete(normalizeName(name))
+    if (this[GUARD_CALLBACK_SLOT](name, 'invalid')) {
+      return
+    }
+    this[MAP_SLOT].delete(normalizeName(name))
   }
 
   Headers.prototype.get = function(name) {
@@ -173,7 +329,7 @@
       throw TypeError('Invalid parameters to get')
     }
     name = normalizeName(name)
-    var value = this.map.get(name)
+    var value = this[MAP_SLOT].get(name)
     return value !== undefined ? value : null
   }
 
@@ -181,18 +337,23 @@
     if (arguments.length !== 1) {
       throw TypeError('Invalid parameters to has')
     }
-    return this.map.has(normalizeName(name))
+    return this[MAP_SLOT].has(normalizeName(name))
   }
 
   Headers.prototype.set = function(name, value) {
     if (arguments.length !== 2) {
       throw TypeError('Invalid parameters to set')
     }
-    this.map.set(normalizeName(name), normalizeValue(value))
+    name = normalizeName(name)
+    value = normalizeValue(value)
+    if (this[GUARD_CALLBACK_SLOT](name, value)) {
+      return
+    }
+    this[MAP_SLOT].set(name, value)
   }
 
   Headers.prototype.forEach = function(callback, thisArg) {
-    var sorted_array = Array.from(this.map.entries()).sort()
+    var sorted_array = Array.from(this[MAP_SLOT].entries()).sort()
     var that = this
     sorted_array.forEach(function(value) {
       callback.call(thisArg, value[1], value[0], that)
@@ -200,17 +361,17 @@
   }
 
   Headers.prototype.keys = function() {
-    var sorted_map = new Map(Array.from(this.map.entries()).sort())
+    var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
     return sorted_map.keys()
   }
 
   Headers.prototype.values = function() {
-    var sorted_map = new Map(Array.from(this.map.entries()).sort())
+    var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
     return sorted_map.values()
   }
 
   Headers.prototype.entries = function() {
-    var sorted_map = new Map(Array.from(this.map.entries()).sort())
+    var sorted_map = new Map(Array.from(this[MAP_SLOT].entries()).sort())
     return sorted_map.entries()
   }
 
@@ -218,7 +379,7 @@
 
   function consumeBodyAsUint8Array(body) {
     if (body.bodyUsed) {
-      return Promise_reject(new TypeError('Body already read'))
+      return Promise_reject(new TypeError('Body was already read'))
     }
 
     if (body.body === null) {
@@ -226,7 +387,7 @@
     }
 
     if (IsReadableStreamLocked(body.body)) {
-      return Promise_reject(new TypeError('ReadableStream already locked'))
+      return Promise_reject(new TypeError('ReadableStream was already locked'))
     }
 
     var reader = body.body.getReader()
@@ -252,31 +413,16 @@
         results.push(result.value)
         return reader.read().then(addResult)
       } else {
-        return Promise_reject(new TypeError('Invalid stream read value type'))
+        return Promise_reject(new TypeError('Invalid stream data type'))
       }
     })
   }
 
-  function encodeStringToUint8Array(str) {
-    // Encode string to UTF-8 then store it in an Uint8Array.
-    var utf8 = unescape(encodeURIComponent(str))
-    var uint8 = new Uint8Array(utf8.length)
-    for (var i = 0, len = utf8.length; i < len; i++) {
-      uint8[i] = utf8.charCodeAt(i)
-    }
-    return uint8
-  }
-
-  function decodeStringFromUint8Array(uint8) {
-    // Decode string from UTF-8 that is stored in the Uint8Array.
-    return decodeURIComponent(escape(String.fromCharCode.apply(null, uint8)))
-  }
-
   // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
   function extractBody(controller, data, errorString) {
     if (!data) {
     } else if (typeof data === 'string') {
-      controller.enqueue(encodeStringToUint8Array(data))
+      controller.enqueue(FetchInternal.encodeToUTF8(data))
     } else if (ArrayBuffer.prototype.isPrototypeOf(data)) {
       controller.enqueue(new Uint8Array(data))
     } else if (isArrayBufferView(data)) {
@@ -291,13 +437,13 @@
   //   Blob types. So only support text(), arrayBuffer(), and json().
   function Body() {
     this._initBody = function(body) {
-      this._bodyUsed = false
+      this[BODY_USED_SLOT] = false
       if (body === null || body === undefined) {
-        this.body = null
+        this[BODY_SLOT] = null
       } else if (body instanceof ReadableStream) {
-        this.body = body
+        this[BODY_SLOT] = body
       } else {
-        this.body = new ReadableStream({
+        this[BODY_SLOT] = new ReadableStream({
           start(controller) {
             extractBody(controller, body, 'Unsupported BodyInit type')
             controller.close()
@@ -305,21 +451,24 @@
         })
       }
 
-      if (!this.headers.get('content-type')) {
+      if (!this[HEADERS_SLOT].get('content-type')) {
         if (typeof body === 'string') {
-          this.headers.set('content-type', 'text/plain;charset=UTF-8')
+          this[HEADERS_SLOT].set('content-type', 'text/plain;charset=UTF-8')
         }
       }
     }
 
-    Object.defineProperty(this, 'bodyUsed', {
-      get: function() {
-        if (this._bodyUsed) {
-          return true
-        } else if (this.body) {
-          return !!IsReadableStreamDisturbed(this.body)
-        } else {
-          return false
+    defineProperties(this, {
+      'body': { get: function() { return this[BODY_SLOT] } },
+      'bodyUsed': {
+        get: function() {
+          if (this[BODY_USED_SLOT]) {
+            return true
+          } else if (this[BODY_SLOT]) {
+            return !!IsReadableStreamDisturbed(this[BODY_SLOT])
+          } else {
+            return false
+          }
         }
       }
     })
@@ -332,11 +481,7 @@
 
     this.text = function() {
       return consumeBodyAsUint8Array(this).then(function(data) {
-        if (data.length === 0) {
-          return ''
-        } else {
-          return decodeStringFromUint8Array(data)
-        }
+        return FetchInternal.decodeFromUTF8(data)
       })
     }
 
@@ -347,66 +492,143 @@
     return this
   }
 
-  // HTTP methods whose capitalization should be normalized
-  const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
-
   function normalizeMethod(method) {
     var upcased = method.toUpperCase()
-    return (methods.indexOf(upcased) > -1) ? upcased : method
+    if (VALID_METHODS.indexOf(upcased) === -1) {
+      throw new TypeError('Invalid request method')
+    }
+    return upcased
   }
 
   // https://fetch.spec.whatwg.org/#request-class
   function Request(input, init) {
+    // When cloning a request, |init| will have the non-standard member
+    // |cloneBody|. This signals that the "!hasInit" path should be used.
+    var hasInit = init !== undefined && init !== null &&
+                  init.cloneBody === undefined
     init = init || {}
-    var body = init.body
+    var body = init.body || init.cloneBody
+    var headersInit = init.headers
 
     if (input instanceof Request) {
       if (input.bodyUsed) {
-        throw new TypeError('Request body already read')
+        throw new TypeError('Request body was already read')
       }
-      this.url = input.url
-      this.credentials = input.credentials
-      if (!init.headers) {
-        this.headers = new Headers(input.headers)
+      this[URL_SLOT] = input.url
+      this[CACHE_SLOT] = input.cache
+      this[CREDENTIALS_SLOT] = input.credentials
+      if (headersInit === undefined) {
+        headersInit = input.headers
       }
-      this.method = input.method
-      this.mode = input.mode
+      this[INTEGRITY_SLOT] = input.integrity
+      this[METHOD_SLOT] = input.method
+      this[MODE_SLOT] = input.mode
+      if (hasInit && this[MODE_SLOT] === 'navigate') {
+        this[MODE_SLOT] = 'same-origin'
+      }
+      this[REDIRECT_SLOT] = input.redirect
       if (!body && input.body !== null) {
         // Take ownership of the stream and mark |input| as disturbed.
         body = input.body
-        input._bodyUsed = true
+        input[BODY_USED_SLOT] = true
       }
     } else {
-      this.url = String(input)
+      this[URL_SLOT] = String(input)
+      if (!FetchInternal.isUrlValid(this[URL_SLOT],
+                                    false /* allowCredentials */)) {
+        throw new TypeError('Invalid request URL')
+      }
+      this[MODE_SLOT] = 'cors'
+      this[CREDENTIALS_SLOT] = 'omit'
     }
 
-    this.credentials = init.credentials || this.credentials || 'omit'
-    if (init.headers || !this.headers) {
-      this.headers = new Headers(init.headers)
+    if (init.window !== undefined && init.window !== null) {
+      throw new TypeError('Invalid request window')
     }
-    this.method = normalizeMethod(init.method || this.method || 'GET')
-    this.mode = init.mode || this.mode || null
-    this.referrer = null
 
-    if ((this.method === 'GET' || this.method === 'HEAD') && body) {
-      throw new TypeError('Body not allowed for GET or HEAD requests')
+    this[CACHE_SLOT] = init.cache || this[CACHE_SLOT] || 'default'
+    if (VALID_CACHE_MODES.indexOf(this[CACHE_SLOT]) === -1) {
+      throw new TypeError('Invalid request cache mode')
+    }
+
+    this[CREDENTIALS_SLOT] = init.credentials || this[CREDENTIALS_SLOT] ||
+                             'omit'
+    if (VALID_CREDENTIALS.indexOf(this[CREDENTIALS_SLOT]) === -1) {
+      throw new TypeError('Invalid request credentials')
+    }
+
+    if (init.integrity !== undefined) {
+      this[INTEGRITY_SLOT] = init.integrity
+    } else if (this[INTEGRITY_SLOT] === undefined) {
+      this[INTEGRITY_SLOT] = ''
+    }
+    this[METHOD_SLOT] = normalizeMethod(init.method || this[METHOD_SLOT] ||
+                                        'GET')
+
+    if (init.mode && VALID_MODES.indexOf(init.mode) === -1) {
+      throw new TypeError('Invalid request mode')
+    }
+    this[MODE_SLOT] = init.mode || this[MODE_SLOT] || 'no-cors'
+    if (this[MODE_SLOT] === 'no-cors') {
+      if (VALID_METHODS_NOCORS.indexOf(this[METHOD_SLOT]) === -1) {
+        throw new TypeError('Invalid request method for no-cors')
+      }
+      if (this[INTEGRITY_SLOT] !== '') {
+        throw new TypeError(
+            'Request integrity data is not allowed with no-cors')
+      }
+    }
+    if (this[MODE_SLOT] !== 'same-origin' &&
+        this[CACHE_SLOT] === 'only-if-cached') {
+      throw new TypeError(
+          'Request mode must be same-origin for only-if-cached')
+    }
+
+    this[REDIRECT_SLOT] = init.redirect || this[REDIRECT_SLOT] || 'follow'
+    if (VALID_REDIRECT_MODES.indexOf(this[REDIRECT_SLOT]) === -1) {
+      throw new TypeError('Invalid request redirect mode')
+    }
+
+    if (this[MODE_SLOT] === 'no-cors') {
+      this[HEADERS_SLOT] =
+          CreateHeadersWithGuard(headersInit, guardRequestNoCorsCallback)
+    } else {
+      this[HEADERS_SLOT] =
+          CreateHeadersWithGuard(headersInit, guardRequestCallback)
+    }
+
+    if ((this[METHOD_SLOT] === 'GET' || this[METHOD_SLOT] === 'HEAD') && body) {
+      throw new TypeError('Request body is not allowed for GET or HEAD')
     }
     this._initBody(body)
   }
 
   Request.prototype.clone = function() {
     var cloneBody = null
-    if (this.body !== null) {
-      var streams = ReadableStreamTee(this.body, true /* cloneForBranch2 */)
-      this.body = streams[0]
+    if (this[BODY_SLOT] !== null) {
+      var streams = ReadableStreamTee(this[BODY_SLOT],
+                                      true /* cloneForBranch2 */)
+      this[BODY_SLOT] = streams[0]
       cloneBody = streams[1]
     }
-    return new Request(this, { body: cloneBody })
+    return new Request(this, { cloneBody: cloneBody })
   }
 
-  function parseHeaders(rawHeaders) {
-    var headers = new Headers()
-    // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
+  defineProperties(Request.prototype, {
+    'cache': { get: function() { return this[CACHE_SLOT] } },
+    'credentials': { get: function() { return this[CREDENTIALS_SLOT] } },
+    'headers': { get: function() { return this[HEADERS_SLOT] } },
+    'integrity': { get: function() { return this[INTEGRITY_SLOT] } },
+    'method': { get: function() { return this[METHOD_SLOT] } },
+    'mode': { get: function() { return this[MODE_SLOT] } },
+    'redirect': { get: function() { return this[REDIRECT_SLOT] } },
+    'url': { get: function() { return this[URL_SLOT] } }
+  })
+
+  function parseHeaders(rawHeaders, guardCallback) {
+    var headers = CreateHeadersWithGuard(undefined, guardCallback)
+    // Replace instances of \r\n and \n followed by at least one space or
+    // horizontal tab with a space.
     // https://tools.ietf.org/html/rfc7230#section-3.2
     var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ')
     preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
@@ -428,32 +650,32 @@
     for (var i = 0, len = text.length, c; i < len; i++) {
       c = text.charCodeAt(i)
       if (c !== 9 && (c < 32 || c > 255 || c === 127)) {
-        throw TypeError('Invalid status text')
+        throw TypeError('Invalid response status text')
       }
     }
     return text
   }
 
-  // Body is not allowed in responses with a null body status.
-  const nullBodyStatuses = [ 101, 204, 205, 304 ]
-
   // https://fetch.spec.whatwg.org/#response-class
   function Response(body, init) {
     if (!init) {
       init = {}
     }
 
-    this.type = 'default'
-    this.status = 'status' in init ? init.status : 200
-    if (this.status < 200 || this.status > 599) {
+    this[TYPE_SLOT] = 'default'
+    this[STATUS_SLOT] = 'status' in init ? init.status : 200
+    if (this[STATUS_SLOT] < 200 || this[STATUS_SLOT] > 599) {
       throw new RangeError('Invalid response status')
     }
-    this.ok = this.status >= 200 && this.status < 300
-    this.statusText = 'statusText' in init ? parseStatusText(init.statusText) : 'OK'
-    this.headers = new Headers(init.headers)
-    this.url = init.url || ''
-    if (body && nullBodyStatuses.indexOf(this.status) > -1) {
-      throw new TypeError('Body not allowed with a null body status')
+    this[OK_SLOT] = this[STATUS_SLOT] >= 200 && this[STATUS_SLOT] < 300
+    this[STATUS_TEXT_SLOT] = 'statusText' in init ?
+                             parseStatusText(init.statusText) : 'OK'
+    this[HEADERS_SLOT] =
+        CreateHeadersWithGuard(init.headers, guardResponseCallback)
+    this[URL_SLOT] = init.url || ''
+    if (body && NULL_BODY_STATUSES.indexOf(this[STATUS_SLOT]) > -1) {
+      throw new TypeError(
+          'Response body is not allowed with a null body status')
     }
     this._initBody(body)
   }
@@ -462,38 +684,48 @@
 
   Response.prototype.clone = function() {
     var cloneBody = null
-    if (this.body !== null) {
-      var streams = ReadableStreamTee(this.body, true /* cloneForBranch2 */)
-      this.body = streams[0]
+    if (this[BODY_SLOT] !== null) {
+      var streams = ReadableStreamTee(this[BODY_SLOT],
+                                      true /* cloneForBranch2 */)
+      this[BODY_SLOT] = streams[0]
       cloneBody = streams[1]
     }
     return new Response(cloneBody, {
-      status: this.status,
-      statusText: this.statusText,
-      headers: new Headers(this.headers),
-      url: this.url
+      status: this[STATUS_SLOT],
+      statusText: this[STATUS_TEXT_SLOT],
+      headers: CreateHeadersWithGuard(this[HEADERS_SLOT],
+                                      guardResponseCallback),
+      url: this[URL_SLOT]
     })
   }
 
+  defineProperties(Response.prototype, {
+    'headers': { get: function() { return this[HEADERS_SLOT] } },
+    'ok': { get: function() { return this[OK_SLOT] } },
+    'status': { get: function() { return this[STATUS_SLOT] } },
+    'statusText': { get: function() { return this[STATUS_TEXT_SLOT] } },
+    'type': { get: function() { return this[TYPE_SLOT] } },
+    'url': { get: function() { return this[URL_SLOT] } }
+  })
+
   Response.error = function() {
     var response = new Response(null)
-    response.type = 'error'
-    response.status = 0
-    response.statusText = ''
+    response[HEADERS_SLOT][GUARD_CALLBACK_SLOT] = guardImmutableCallback
+    response[TYPE_SLOT] = 'error'
+    response[STATUS_SLOT] = 0
+    response[STATUS_TEXT_SLOT] = ''
     return response
   }
 
-  var redirectStatuses = [301, 302, 303, 307, 308]
-
   Response.redirect = function(url, status) {
-    if (!FetchInternal.IsUrlValid(url)) {
-      throw new TypeError('Invalid URL')
+    if (!FetchInternal.isUrlValid(url, true /* allowCredentials */)) {
+      throw new TypeError('Invalid URL for response redirect')
     }
     if (status === undefined) {
       status = 302
     }
-    if (redirectStatuses.indexOf(status) === -1) {
-      throw new RangeError('Invalid status code')
+    if (REDIRECT_STATUSES.indexOf(status) === -1) {
+      throw new RangeError('Invalid status code for response redirect')
     }
 
     return new Response(null, {status: status, headers: {location: url}})
@@ -529,21 +761,31 @@
           var init = {
             status: xhr.status,
             statusText: xhr.statusText,
-            headers: parseHeaders(xhr.getAllResponseHeaders() || '')
+            headers: parseHeaders(xhr.getAllResponseHeaders() || '',
+                                  guardResponseCallback)
           }
-          init.url = 'responseURL' in xhr ? xhr.responseURL : init.headers.get('X-Request-URL')
-          resolve(new Response(responseStream, init))
+          init.url = 'responseURL' in xhr ?
+                     xhr.responseURL : init.headers.get('X-Request-URL')
+          try {
+            var response = new Response(responseStream, init)
+            response[TYPE_SLOT] = 'basic'
+            resolve(response)
+          } catch (err) {
+            reject(err)
+          }
         }
       }
 
       xhr.onerror = function() {
-        responseStreamController.error(new TypeError(err_NetworkRequestFailed))
-        reject(new TypeError(err_NetworkRequestFailed))
+        responseStreamController.error(
+            new TypeError(ERROR_NETWORK_REQUEST_FAILED))
+        reject(new TypeError(ERROR_NETWORK_REQUEST_FAILED))
       }
 
       xhr.ontimeout = function() {
-        responseStreamController.error(new TypeError(err_NetworkRequestFailed))
-        reject(new TypeError(err_NetworkRequestFailed))
+        responseStreamController.error(
+            new TypeError(ERROR_NETWORK_REQUEST_FAILED))
+        reject(new TypeError(ERROR_NETWORK_REQUEST_FAILED))
       }
 
       xhr.open(request.method, request.url, true)
diff --git a/src/cobalt/fetch/fetch_internal.cc b/src/cobalt/fetch/fetch_internal.cc
index ce79f8d..d6bd76e 100644
--- a/src/cobalt/fetch/fetch_internal.cc
+++ b/src/cobalt/fetch/fetch_internal.cc
@@ -14,15 +14,58 @@
 
 #include "cobalt/fetch/fetch_internal.h"
 
+#include "base/string_util.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_settings.h"
 #include "googleurl/src/gurl.h"
 
 namespace cobalt {
 namespace fetch {
 
 // static
-bool FetchInternal::IsUrlValid(const std::string& url) {
-  GURL gurl(url);
-  return gurl.is_valid() || gurl.is_empty();
+bool FetchInternal::IsUrlValid(script::EnvironmentSettings* settings,
+                               const std::string& url, bool allow_credentials) {
+  dom::DOMSettings* dom_settings =
+      base::polymorphic_downcast<dom::DOMSettings*>(settings);
+  GURL gurl = dom_settings->base_url().Resolve(url);
+  return gurl.is_valid() && (allow_credentials ||
+                            (!gurl.has_username() && !gurl.has_password()));
+}
+
+// static
+scoped_refptr<dom::Uint8Array> FetchInternal::EncodeToUTF8(
+    script::EnvironmentSettings* settings, const std::string& text,
+    script::ExceptionState* exception_state) {
+  // The conversion helper already translated the JSValue into a UTF-8 encoded
+  // string. So just wrap the result in a Uint8Array.
+  return new dom::Uint8Array(settings,
+      reinterpret_cast<const uint8*>(text.c_str()),
+      static_cast<uint32>(text.size()),
+      exception_state);
+}
+
+// static
+std::string FetchInternal::DecodeFromUTF8(
+    const scoped_refptr<dom::Uint8Array>& data,
+    script::ExceptionState* exception_state) {
+  // The conversion helper expects the return value to be a UTF-8 encoded
+  // string.
+  base::StringPiece input(reinterpret_cast<const char*>(data->data()),
+                          data->length());
+
+  if (IsStringUTF8(input)) {
+    // Input is already UTF-8. Just strip the byte order mark if it's present.
+    const base::StringPiece kUtf8ByteOrderMarkStringPiece(kUtf8ByteOrderMark);
+    if (input.starts_with(kUtf8ByteOrderMarkStringPiece)) {
+      input = input.substr(kUtf8ByteOrderMarkStringPiece.length());
+    }
+    return input.as_string();
+  }
+
+  // Only UTF-8 is supported.
+  exception_state->SetSimpleException(script::kTypeError,
+                                      "Not a valid UTF-8 string");
+  return std::string();
 }
 
 }  // namespace fetch
diff --git a/src/cobalt/fetch/fetch_internal.h b/src/cobalt/fetch/fetch_internal.h
index f5b3add..946c7b6 100644
--- a/src/cobalt/fetch/fetch_internal.h
+++ b/src/cobalt/fetch/fetch_internal.h
@@ -17,6 +17,10 @@
 
 #include <string>
 
+#include "base/memory/ref_counted.h"
+#include "cobalt/dom/uint8_array.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/exception_state.h"
 #include "cobalt/script/wrappable.h"
 
 namespace cobalt {
@@ -29,7 +33,18 @@
 class FetchInternal : public script::Wrappable {
  public:
   // Return whether the given URL is valid.
-  static bool IsUrlValid(const std::string& url);
+  static bool IsUrlValid(script::EnvironmentSettings* settings,
+      const std::string& url, bool allow_credentials);
+
+  // Return a Uint8Array representing the given text as UTF-8 encoded data.
+  static scoped_refptr<dom::Uint8Array> EncodeToUTF8(
+      script::EnvironmentSettings* settings, const std::string& text,
+      script::ExceptionState* exception_state);
+
+  // Return a UTF-8 encoded string representing the given data.
+  static std::string DecodeFromUTF8(
+      const scoped_refptr<dom::Uint8Array>& data,
+      script::ExceptionState* exception_state);
 
   DEFINE_WRAPPABLE_TYPE(FetchInternal);
 };
diff --git a/src/cobalt/fetch/fetch_internal.idl b/src/cobalt/fetch/fetch_internal.idl
index bd1153b..32aa3b1 100644
--- a/src/cobalt/fetch/fetch_internal.idl
+++ b/src/cobalt/fetch/fetch_internal.idl
@@ -17,5 +17,9 @@
 // This is not meant to be public and should not be used outside of the fetch
 // implementation.
 interface FetchInternal {
-  static boolean IsUrlValid(DOMString url);
+  [CallWith=EnvironmentSettings] static boolean isUrlValid(
+      DOMString url, boolean allowCredentials);
+  [CallWith=EnvironmentSettings,RaisesException] static Uint8Array encodeToUTF8(
+      DOMString text);
+  [RaisesException] static DOMString decodeFromUTF8(Uint8Array data);
 };
diff --git a/src/cobalt/h5vcc/h5vcc.gyp b/src/cobalt/h5vcc/h5vcc.gyp
index 1e71529..9b96e06 100644
--- a/src/cobalt/h5vcc/h5vcc.gyp
+++ b/src/cobalt/h5vcc/h5vcc.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/h5vcc/h5vcc_runtime.cc b/src/cobalt/h5vcc/h5vcc_runtime.cc
index feb236a..6f50618 100644
--- a/src/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/src/cobalt/h5vcc/h5vcc_runtime.cc
@@ -67,10 +67,10 @@
   const system_window::ApplicationEvent* app_event =
       base::polymorphic_downcast<const system_window::ApplicationEvent*>(event);
   if (app_event->type() == system_window::ApplicationEvent::kPause) {
-    DLOG(INFO) << "Got pause event.";
+    // DLOG(INFO) << "Got pause event.";
     on_pause()->DispatchEvent();
   } else if (app_event->type() == system_window::ApplicationEvent::kUnpause) {
-    DLOG(INFO) << "Got unpause event.";
+    // DLOG(INFO) << "Got unpause event.";
     on_resume()->DispatchEvent();
   }
 }
diff --git a/src/cobalt/input/camera_3d.h b/src/cobalt/input/camera_3d.h
index 5138c80..37e25d2 100644
--- a/src/cobalt/input/camera_3d.h
+++ b/src/cobalt/input/camera_3d.h
@@ -19,6 +19,7 @@
 #include "cobalt/base/camera_transform.h"
 #include "cobalt/input/input_poller.h"
 #include "starboard/window.h"
+#include "third_party/glm/glm/gtc/quaternion.hpp"
 
 namespace cobalt {
 namespace input {
@@ -29,12 +30,14 @@
 // have empty default implementations.
 class Camera3D : public base::RefCountedThreadSafe<Camera3D> {
  public:
+  // The rotations around the axes are intrinsic, meaning they are applied in
+  // sequence, each on the rotated frame produced by the previous one.
   enum CameraAxes {
-    // Restricted to [0deg, 360deg]
+    // Applied around the Z axis. Restricted to [0deg, 360deg).
     kCameraRoll = 0x00,
-    // Restricted to [-90deg, 90deg]
+    // Applied around the X axis. Restricted to [-90deg, 90deg)
     kCameraPitch = 0x01,
-    // Restricted to [0deg, 360deg]
+    // Applied around the Y axis. Restricted to [0deg, 360deg)
     kCameraYaw = 0x02,
   };
 
@@ -49,7 +52,7 @@
   virtual void ClearAllKeyMappings() {}
 
   // Return the camera's current view direction.
-  virtual base::CameraOrientation GetOrientation() const = 0;
+  virtual glm::quat GetOrientation() const = 0;
 
   // The above methods are meant as an interface for the Camera3D's WebAPI on
   // the WebModule thread, but the methods below will be accessed on the
@@ -74,6 +77,16 @@
   virtual void Reset() {}
 
   virtual ~Camera3D() {}
+
+  template <typename FloatType>
+  static FloatType DegreesToRadians(FloatType degrees) {
+    return (degrees / 360) * 2 * static_cast<FloatType>(M_PI);
+  }
+
+  template <typename FloatType>
+  static FloatType RadiansToDegrees(FloatType radians) {
+    return radians * 180 / static_cast<FloatType>(M_PI);
+  }
 };
 
 }  // namespace input
diff --git a/src/cobalt/input/camera_3d_input_poller.cc b/src/cobalt/input/camera_3d_input_poller.cc
index 95447a6..8e6234a 100644
--- a/src/cobalt/input/camera_3d_input_poller.cc
+++ b/src/cobalt/input/camera_3d_input_poller.cc
@@ -21,6 +21,7 @@
 
 #include "cobalt/input/create_default_camera_3d.h"
 #include "third_party/glm/glm/gtc/matrix_transform.hpp"
+#include "third_party/glm/glm/gtc/quaternion.hpp"
 #include "third_party/glm/glm/gtx/transform.hpp"
 
 namespace cobalt {
@@ -28,7 +29,10 @@
 
 Camera3DInputPoller::Camera3DInputPoller(
     const scoped_refptr<input::InputPoller>& input_poller)
-    : input_poller_(input_poller),
+    : roll_in_radians_(0.0f),
+      pitch_in_radians_(0.0f),
+      yaw_in_radians_(0.0f),
+      input_poller_(input_poller),
       width_to_height_aspect_ratio_(16.0f / 9.0f),
       vertical_fov_(60.0f) {}
 
@@ -48,33 +52,24 @@
   keycode_map_.clear();
 }
 
-base::CameraOrientation Camera3DInputPoller::GetOrientation() const {
-  base::AutoLock lock(mutex_);
-  return orientation_;
+glm::quat Camera3DInputPoller::orientation() const {
+  return glm::angleAxis(-roll_in_radians_, glm::vec3(0, 0, 1)) *
+         glm::angleAxis(-pitch_in_radians_, glm::vec3(1, 0, 0)) *
+         glm::angleAxis(-yaw_in_radians_, glm::vec3(0, 1, 0));
 }
 
-namespace {
-
-const float kPiF = static_cast<float>(M_PI);
-
-float DegreesToRadians(float degrees) { return (degrees / 360.0f) * 2 * kPiF; }
-
-}  // namespace
+glm::quat Camera3DInputPoller::GetOrientation() const {
+  base::AutoLock lock(mutex_);
+  return orientation();
+}
 
 base::CameraTransform
 Camera3DInputPoller::GetCameraTransformAndUpdateOrientation() {
   base::AutoLock lock(mutex_);
   AccumulateOrientation();
 
-  // Note that we invert the rotation angles since this matrix is applied to
-  // the objects in our scene, and if the camera moves right, the objects,
-  // relatively, would move right.
-  glm::mat4 view_matrix =
-      glm::rotate(-DegreesToRadians(orientation_.roll), glm::vec3(0, 0, 1)) *
-      glm::rotate(-DegreesToRadians(orientation_.pitch), glm::vec3(1, 0, 0)) *
-      glm::rotate(-DegreesToRadians(orientation_.yaw), glm::vec3(0, 1, 0));
+  glm::mat4 view_matrix = glm::mat4_cast(orientation());
 
-  // Setup a (right-handed) perspective projection matrix.
   const float kNearZ = 0.01f;
   const float kFarZ = 1000.0f;
   glm::mat4 projection_matrix = glm::perspectiveRH(
@@ -91,7 +86,9 @@
 
 void Camera3DInputPoller::Reset() {
   base::AutoLock lock(mutex_);
-  orientation_ = base::CameraOrientation();
+  roll_in_radians_ = 0.0f;
+  pitch_in_radians_ = 0.0f;
+  yaw_in_radians_ = 0.0f;
 }
 
 void Camera3DInputPoller::AccumulateOrientation() {
@@ -111,6 +108,7 @@
       delta = kMaxTimeDelta;
     }
 
+    // Accumulate new rotation from all mapped inputs.
     for (KeycodeMap::const_iterator iter = keycode_map_.begin();
          iter != keycode_map_.end(); ++iter) {
       // If the key does not have analog output, the AnalogInput() method will
@@ -127,34 +125,46 @@
       float* target_angle;
       switch (iter->second.axis) {
         case kCameraRoll:
-          target_angle = &orientation_.roll;
+          target_angle = &roll_in_radians_;
           break;
         case kCameraPitch:
-          target_angle = &orientation_.pitch;
+          target_angle = &pitch_in_radians_;
           break;
         case kCameraYaw:
-          target_angle = &orientation_.yaw;
+          target_angle = &yaw_in_radians_;
           break;
       }
 
       // Apply the angle adjustment from the key.
-      *target_angle += value * iter->second.degrees_per_second *
+      *target_angle += value *
+                       DegreesToRadians(iter->second.degrees_per_second) *
                        static_cast<float>(delta.InSecondsF());
-
-      // Apply any clamping or wrapping to the resulting camera angles.
-      if (iter->second.axis == kCameraPitch) {
-        *target_angle = std::min(90.0f, std::max(-90.0f, *target_angle));
-      } else {
-        *target_angle = static_cast<float>(fmod(*target_angle, 360));
-        if (*target_angle < 0) {
-          *target_angle += 360;
-        }
-      }
     }
+
+    // Clamp the angles to ensure that they remain in the valid range.
+    ClampAngles();
   }
   last_update_ = now;
 }
 
+void Camera3DInputPoller::ClampAngles() {
+  float kTwoPi = static_cast<float>(M_PI * 2);
+  float kPiOverTwo = static_cast<float>(M_PI / 2);
+
+  pitch_in_radians_ =
+      std::max(-kPiOverTwo, std::min(kPiOverTwo, pitch_in_radians_));
+
+  roll_in_radians_ = fmod(roll_in_radians_, kTwoPi);
+  if (roll_in_radians_ < 0) {
+    roll_in_radians_ += kTwoPi;
+  }
+
+  yaw_in_radians_ = fmod(yaw_in_radians_, kTwoPi);
+  if (yaw_in_radians_ < 0) {
+    yaw_in_radians_ += kTwoPi;
+  }
+}
+
 scoped_refptr<Camera3D> CreatedDefaultCamera3D(
     SbWindow window, const scoped_refptr<InputPoller>& input_poller) {
   UNREFERENCED_PARAMETER(window);
diff --git a/src/cobalt/input/camera_3d_input_poller.h b/src/cobalt/input/camera_3d_input_poller.h
index bd89ad5..f31d227 100644
--- a/src/cobalt/input/camera_3d_input_poller.h
+++ b/src/cobalt/input/camera_3d_input_poller.h
@@ -40,7 +40,7 @@
                         float degrees_per_second) OVERRIDE;
   void ClearKeyMapping(int keycode) OVERRIDE;
   void ClearAllKeyMappings() OVERRIDE;
-  base::CameraOrientation GetOrientation() const OVERRIDE;
+  glm::quat GetOrientation() const OVERRIDE;
 
   // Returns the camera transforms based on hand-controlled inputs mapped
   // by the functions above
@@ -64,11 +64,16 @@
   typedef std::map<int, KeycodeMappingInfo> KeycodeMap;
 
   void AccumulateOrientation();
+  void ClampAngles();
+
+  glm::quat orientation() const;
 
   mutable base::Lock mutex_;
 
   // The current accumulated camera orientation state.
-  base::CameraOrientation orientation_;
+  float roll_in_radians_;
+  float pitch_in_radians_;
+  float yaw_in_radians_;
 
   // The time that the last update to the camera's state has occurred.
   base::optional<base::TimeTicks> last_update_;
diff --git a/src/cobalt/input/input_device_manager.h b/src/cobalt/input/input_device_manager.h
index 4dae106..1fb1f14 100644
--- a/src/cobalt/input/input_device_manager.h
+++ b/src/cobalt/input/input_device_manager.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_INPUT_INPUT_DEVICE_MANAGER_H_
 #define COBALT_INPUT_INPUT_DEVICE_MANAGER_H_
 
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/input/camera_3d.h"
 #include "cobalt/input/input_poller.h"
 #include "cobalt/input/key_event_handler.h"
@@ -29,6 +31,12 @@
 
 namespace input {
 
+typedef base::Callback<void(base::Token type, const dom::PointerEventInit&)>
+    PointerEventCallback;
+
+typedef base::Callback<void(base::Token type, const dom::WheelEventInit&)>
+    WheelEventCallback;
+
 // InputDeviceManager listens to events from platform-specific input devices
 // and maps them to platform-independent keyboard key events.
 class InputDeviceManager {
@@ -36,7 +44,9 @@
   // Creates an instance using a SystemWindow parameter.
   // This allows us to hook up keyboard events on desktop systems.
   static scoped_ptr<InputDeviceManager> CreateFromWindow(
-      const KeyboardEventCallback& callback,
+      const KeyboardEventCallback& keyboard_event_callback,
+      const PointerEventCallback& pointer_event_callback,
+      const WheelEventCallback& wheel_event_callback,
       system_window::SystemWindow* system_window);
 
   virtual ~InputDeviceManager() {}
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 5925fe8..629b820 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -16,6 +16,14 @@
 
 #include <string>
 
+#include "cobalt/base/token.h"
+#include "cobalt/base/tokens.h"
+#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
+#include "cobalt/dom/pointer_event.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/wheel_event.h"
+#include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/input/create_default_camera_3d.h"
 #include "cobalt/input/input_poller_impl.h"
 #include "cobalt/system_window/input_event.h"
@@ -24,13 +32,17 @@
 namespace input {
 
 InputDeviceManagerDesktop::InputDeviceManagerDesktop(
-    const KeyboardEventCallback& callback,
+    const KeyboardEventCallback& keyboard_event_callback,
+    const PointerEventCallback& pointer_event_callback,
+    const WheelEventCallback& wheel_event_callback,
     system_window::SystemWindow* system_window)
     : system_window_(system_window),
-      keyboard_event_callback_(
+      input_event_callback_(
           base::Bind(&InputDeviceManagerDesktop::HandleInputEvent,
                      base::Unretained(this))),
-      keypress_generator_filter_(callback) {
+      keypress_generator_filter_(keyboard_event_callback),
+      pointer_event_callback_(pointer_event_callback),
+      wheel_event_callback_(wheel_event_callback) {
   input_poller_ = new InputPollerImpl();
   DCHECK(system_window_);
   camera_3d_ =
@@ -39,7 +51,7 @@
   if (system_window_) {
     // Add this object's keyboard event callback to the system window.
     system_window_->event_dispatcher()->AddEventCallback(
-        system_window::InputEvent::TypeId(), keyboard_event_callback_);
+        system_window::InputEvent::TypeId(), input_event_callback_);
   }
 }
 
@@ -47,31 +59,199 @@
   // If we have an associated system window, remove our callback from it.
   if (system_window_) {
     system_window_->event_dispatcher()->RemoveEventCallback(
-        system_window::InputEvent::TypeId(), keyboard_event_callback_);
+        system_window::InputEvent::TypeId(), input_event_callback_);
   }
 }
 
+namespace {
+
+COMPILE_ASSERT(static_cast<uint32_t>(kSbKeyModifiersNone) ==
+                       system_window::InputEvent::kNoModifier &&
+                   static_cast<uint32_t>(kSbKeyModifiersAlt) ==
+                       system_window::InputEvent::kAltKey &&
+                   static_cast<uint32_t>(kSbKeyModifiersCtrl) ==
+                       system_window::InputEvent::kCtrlKey &&
+                   static_cast<uint32_t>(kSbKeyModifiersMeta) ==
+                       system_window::InputEvent::kMetaKey &&
+                   static_cast<uint32_t>(kSbKeyModifiersShift) ==
+                       system_window::InputEvent::kShiftKey,
+               Mismatched_modifier_enums);
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+COMPILE_ASSERT(static_cast<uint32_t>(kSbKeyModifiersPointerButtonLeft) ==
+                       system_window::InputEvent::kLeftButton &&
+                   static_cast<uint32_t>(kSbKeyModifiersPointerButtonRight) ==
+                       system_window::InputEvent::kRightButton &&
+                   static_cast<uint32_t>(kSbKeyModifiersPointerButtonMiddle) ==
+                       system_window::InputEvent::kMiddleButton &&
+                   static_cast<uint32_t>(kSbKeyModifiersPointerButtonBack) ==
+                       system_window::InputEvent::kBackButton &&
+                   static_cast<uint32_t>(kSbKeyModifiersPointerButtonForward) ==
+                       system_window::InputEvent::kForwardButton,
+               Mismatched_modifier_enums);
+#endif
+
+void UpdateEventModifierInit(const system_window::InputEvent* input_event,
+                             EventModifierInit* event) {
+  const uint32 modifiers = input_event->modifiers();
+  event->set_ctrl_key(modifiers & system_window::InputEvent::kCtrlKey);
+  event->set_shift_key(modifiers & system_window::InputEvent::kShiftKey);
+  event->set_alt_key(modifiers & system_window::InputEvent::kAltKey);
+  event->set_meta_key(modifiers & system_window::InputEvent::kMetaKey);
+}
+
+void UpdateMouseEventInitButton(int key_code, MouseEventInit* event) {
+  // The value of the button attribute MUST be as follows:
+  //  https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-button-1
+  if (key_code == kSbKeyMouse1) {
+    // 0 MUST indicate the primary button of the device (in general, the left
+    // button or the only button on single-button devices, used to activate a
+    // user interface control or select text) or the un-initialized value.
+    event->set_button(0);
+  } else if (key_code == kSbKeyMouse2) {
+    // 1 MUST indicate the auxiliary button (in general, the middle button,
+    // often combined with a mouse wheel).
+    event->set_button(1);
+  } else if (key_code == kSbKeyMouse3) {
+    // 2 MUST indicate the secondary button (in general, the right button, often
+    // used to display a context menu).
+    event->set_button(2);
+  }
+}
+
+void UpdateMouseEventInitButtons(uint32 modifiers, MouseEventInit* event) {
+  // The value of the buttons attribute MUST be as follows:
+  //  https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-buttons-3
+
+  // 0 MUST indicate no button is currently active.
+  uint16 buttons = 0;
+  if (modifiers & system_window::InputEvent::kLeftButton) {
+    // 1 MUST indicate the primary button of the device (in general, the left
+    // button or the only button on single-button devices, used to activate a
+    // user interface control or select text).
+    buttons |= 1;
+  }
+  if (modifiers & system_window::InputEvent::kRightButton) {
+    // 2 MUST indicate the secondary button (in general, the right button, often
+    // used to display a context menu), if present.
+    buttons |= 2;
+  }
+  if (modifiers & system_window::InputEvent::kMiddleButton) {
+    // 4 MUST indicate the auxiliary button (in general, the middle button,
+    // often combined with a mouse wheel).
+    buttons |= 4;
+  }
+  event->set_buttons(buttons);
+}
+
+void UpdateMouseEventInit(const system_window::InputEvent* input_event,
+                          dom::MouseEventInit* mouse_event) {
+  UpdateEventModifierInit(input_event, mouse_event);
+  UpdateMouseEventInitButton(input_event->key_code(), mouse_event);
+  UpdateMouseEventInitButtons(input_event->modifiers(), mouse_event);
+
+  mouse_event->set_screen_x(input_event->position().x());
+  mouse_event->set_screen_y(input_event->position().y());
+  mouse_event->set_client_x(input_event->position().x());
+  mouse_event->set_client_y(input_event->position().y());
+}
+
+}  // namespace
+
+void InputDeviceManagerDesktop::HandleKeyboardEvent(
+    bool is_key_down, const system_window::InputEvent* input_event,
+    int key_code) {
+  base::Token type =
+      is_key_down ? base::Tokens::keydown() : base::Tokens::keyup();
+  dom::KeyboardEvent::KeyLocationCode location =
+      dom::KeyboardEvent::KeyCodeToKeyLocation(key_code);
+  dom::KeyboardEventInit keyboard_event;
+  UpdateEventModifierInit(input_event, &keyboard_event);
+  keyboard_event.set_location(location);
+  keyboard_event.set_repeat(input_event->is_repeat());
+  keyboard_event.set_char_code(key_code);
+  keyboard_event.set_key_code(key_code);
+  keypress_generator_filter_.HandleKeyboardEvent(type, keyboard_event);
+}
+
+void InputDeviceManagerDesktop::HandlePointerEvent(
+    base::Token type, const system_window::InputEvent* input_event) {
+  dom::PointerEventInit pointer_event;
+  UpdateMouseEventInit(input_event, &pointer_event);
+
+  pointer_event.set_pointer_id(input_event->device_id());
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  pointer_event.set_width(input_event->size().x());
+  pointer_event.set_height(input_event->size().y());
+  pointer_event.set_pressure(input_event->pressure());
+  pointer_event.set_tilt_x(input_event->tilt().x());
+  pointer_event.set_tilt_y(input_event->tilt().y());
+#endif
+  pointer_event.set_is_primary(true);
+  pointer_event_callback_.Run(type, pointer_event);
+}
+
+void InputDeviceManagerDesktop::HandleWheelEvent(
+    const system_window::InputEvent* input_event) {
+  base::Token type = base::Tokens::wheel();
+  dom::WheelEventInit wheel_event;
+  UpdateMouseEventInit(input_event, &wheel_event);
+
+  wheel_event.set_delta_x(input_event->delta().x());
+  wheel_event.set_delta_y(input_event->delta().y());
+  wheel_event.set_delta_z(0);
+  wheel_event.set_delta_mode(dom::WheelEvent::kDomDeltaLine);
+
+  wheel_event_callback_.Run(type, wheel_event);
+}
+
 void InputDeviceManagerDesktop::HandleInputEvent(const base::Event* event) {
   // The user has pressed a key on the keyboard.
   const system_window::InputEvent* input_event =
       base::polymorphic_downcast<const system_window::InputEvent*>(event);
-  const int key_code = input_event->key_code();
-  const uint32 modifiers = input_event->modifiers();
+  SB_DCHECK(input_event);
+  int key_code = input_event->key_code();
 
-  if (input_event->type() == system_window::InputEvent::kKeyDown ||
-      input_event->type() == system_window::InputEvent::kKeyUp) {
-    dom::KeyboardEvent::KeyLocationCode location =
-        dom::KeyboardEvent::KeyCodeToKeyLocation(key_code);
+  switch (input_event->type()) {
+    case system_window::InputEvent::kKeyDown:
+      HandleKeyboardEvent(true, input_event, key_code);
+      break;
+    case system_window::InputEvent::kKeyUp:
+      HandleKeyboardEvent(false, input_event, key_code);
+      break;
+    case system_window::InputEvent::kPointerDown:
+    case system_window::InputEvent::kPointerUp: {
+      if ((kSbKeyBrowserBack == key_code) ||
+          (kSbKeyBrowserForward == key_code) || (kSbKeyMouse2 == key_code)) {
+        // For consistency with behavior on current browsers, the 'Forward' and
+        // 'Back' mouse buttons are reported as keypress input for the 'Forward'
+        // and 'Back' navigation keys, not as Pointer Events for the X1 and X2
+        // buttons.
 
-    dom::KeyboardEvent::Type key_event_type =
-        input_event->type() == system_window::InputEvent::kKeyDown
-            ? dom::KeyboardEvent::kTypeKeyDown
-            : dom::KeyboardEvent::kTypeKeyUp;
-    dom::KeyboardEvent::Data keyboard_event(key_event_type, location, modifiers,
-                                            key_code, key_code,
-                                            input_event->is_repeat());
-
-    keypress_generator_filter_.HandleKeyboardEvent(keyboard_event);
+        if (kSbKeyMouse2 == key_code) {
+          // Temporarily Report middle button presses as the enter key.
+          key_code = kSbKeyReturn;
+        }
+        HandleKeyboardEvent(
+            input_event->type() == system_window::InputEvent::kPointerDown,
+            input_event, key_code);
+      } else {
+        base::Token type =
+            input_event->type() == system_window::InputEvent::kPointerDown
+                ? base::Tokens::pointerdown()
+                : base::Tokens::pointerup();
+        HandlePointerEvent(type, input_event);
+      }
+      break;
+    }
+    case system_window::InputEvent::kPointerMove: {
+      HandlePointerEvent(base::Tokens::pointermove(), input_event);
+      break;
+    }
+    case system_window::InputEvent::kWheel: {
+      HandleWheelEvent(input_event);
+    }
+    default:
+      break;
   }
 
   InputPollerImpl* input_poller_impl =
diff --git a/src/cobalt/input/input_device_manager_desktop.h b/src/cobalt/input/input_device_manager_desktop.h
index e2b7d57..13dfbf0 100644
--- a/src/cobalt/input/input_device_manager_desktop.h
+++ b/src/cobalt/input/input_device_manager_desktop.h
@@ -24,8 +24,11 @@
 
 class InputDeviceManagerDesktop : public InputDeviceManager {
  public:
-  InputDeviceManagerDesktop(const KeyboardEventCallback& callback,
-                            system_window::SystemWindow* system_window);
+  InputDeviceManagerDesktop(
+      const KeyboardEventCallback& keyboard_event_callback,
+      const PointerEventCallback& pointer_event_callback,
+      const WheelEventCallback& wheel_event_callback,
+      system_window::SystemWindow* system_window);
 
   ~InputDeviceManagerDesktop() OVERRIDE;
 
@@ -35,15 +38,29 @@
   void HandleInputEvent(const base::Event* event);
 
  private:
+  void HandleKeyboardEvent(bool is_key_down,
+                           const system_window::InputEvent* input_event,
+                           int key_code);
+  void HandlePointerEvent(base::Token type,
+                          const system_window::InputEvent* input_event);
+
+  void HandleWheelEvent(const system_window::InputEvent* input_event);
+
   // Reference to the system window that will provide keyboard events.
   system_window::SystemWindow* system_window_;
 
-  // Store a callback wrapping the object event handler, HandleKeyboardEvent.
+  // Store a callback wrapping the object event handler, HandleInputEvent.
   // This is so we can remove it again when this object is destroyed.
-  base::EventCallback keyboard_event_callback_;
+  base::EventCallback input_event_callback_;
 
   // Keyboard event filters to process the events generated.
   KeypressGeneratorFilter keypress_generator_filter_;
+
+  // Called to handle a pointer event.
+  PointerEventCallback pointer_event_callback_;
+
+  // Called to handle a wheel event.
+  WheelEventCallback wheel_event_callback_;
 };
 
 }  // namespace input
diff --git a/src/cobalt/input/input_device_manager_fuzzer.cc b/src/cobalt/input/input_device_manager_fuzzer.cc
index 45b54c1..1215128 100644
--- a/src/cobalt/input/input_device_manager_fuzzer.cc
+++ b/src/cobalt/input/input_device_manager_fuzzer.cc
@@ -16,17 +16,16 @@
 
 #include "base/basictypes.h"
 #include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/message_loop.h"
 #include "base/rand_util.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/keycode.h"
 
 namespace cobalt {
 namespace input {
 
-using base::TimeDelta;
-using dom::KeyboardEvent;
-
 namespace {
 
 // Initialize the set of key events we are able to produce.
@@ -77,7 +76,7 @@
   return key_codes_[index];
 }
 
-TimeDelta InputDeviceManagerFuzzer::KeyInfo::GetRandomDelay() const {
+base::TimeDelta InputDeviceManagerFuzzer::KeyInfo::GetRandomDelay() const {
   if (minimum_delay_ == maximum_delay_) {
     return minimum_delay_;
   }
@@ -85,7 +84,8 @@
       (maximum_delay_ - minimum_delay_).InMicroseconds();
   diff_in_microseconds = static_cast<int64>(
       base::RandGenerator(static_cast<uint64>(diff_in_microseconds)));
-  return minimum_delay_ + TimeDelta::FromMicroseconds(diff_in_microseconds);
+  return minimum_delay_ +
+         base::TimeDelta::FromMicroseconds(diff_in_microseconds);
 }
 
 InputDeviceManagerFuzzer::InputDeviceManagerFuzzer(
@@ -94,13 +94,13 @@
       next_key_index_(0),
       thread_("InputDeviceManagerFuzzer") {
   key_infos_.push_back(KeyInfo(kKeyCodes, arraysize(kKeyCodes),
-                               TimeDelta::FromMilliseconds(400)));
+                               base::TimeDelta::FromMilliseconds(400)));
   // Modify the key_infos_ to use different input patterns.  For example, the
   // following pattern can be used to test play and stop of a video repeatedly.
   //   key_infos_.push_back(KeyInfo(keycode::kReturn,
-  //                        TimeDelta::FromSeconds(1)));
+  //                        base::TimeDelta::FromSeconds(1)));
   //   key_infos_.push_back(KeyInfo(keycode::kEscape,
-  //                        TimeDelta::FromSeconds(1)));
+  //                        base::TimeDelta::FromSeconds(1)));
 
   // Schedule task to send the first key event.  Add an explicit delay to avoid
   // possible conflicts with debug console.
@@ -115,17 +115,11 @@
   DCHECK_LT(next_key_index_, key_infos_.size());
   int key_code = key_infos_[next_key_index_].GetRandomKeyCode();
 
-  KeyboardEvent::Data key_down_event(
-      KeyboardEvent::kTypeKeyDown, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, key_code, 0, false);
+  dom::KeyboardEventInit event_init;
+  event_init.set_key_code(key_code);
 
-  keyboard_event_callback_.Run(key_down_event);
-
-  KeyboardEvent::Data key_up_event(
-      KeyboardEvent::kTypeKeyUp, KeyboardEvent::kDomKeyLocationStandard,
-      KeyboardEvent::kNoModifier, key_code, 0, false);
-
-  keyboard_event_callback_.Run(key_up_event);
+  keyboard_event_callback_.Run(base::Tokens::keydown(), event_init);
+  keyboard_event_callback_.Run(base::Tokens::keyup(), event_init);
 
   MessageLoop::current()->PostDelayedTask(
       FROM_HERE, base::Bind(&InputDeviceManagerFuzzer::OnNextEvent,
diff --git a/src/cobalt/input/input_device_manager_starboard.cc b/src/cobalt/input/input_device_manager_starboard.cc
index 95508a0..9c21515 100644
--- a/src/cobalt/input/input_device_manager_starboard.cc
+++ b/src/cobalt/input/input_device_manager_starboard.cc
@@ -19,10 +19,13 @@
 
 // static
 scoped_ptr<InputDeviceManager> InputDeviceManager::CreateFromWindow(
-    const KeyboardEventCallback& callback,
+    const KeyboardEventCallback& keyboard_event_callback,
+    const PointerEventCallback& pointer_event_callback,
+    const WheelEventCallback& wheel_event_callback,
     system_window::SystemWindow* system_window) {
-  return scoped_ptr<InputDeviceManager>(
-      new InputDeviceManagerDesktop(callback, system_window));
+  return scoped_ptr<InputDeviceManager>(new InputDeviceManagerDesktop(
+      keyboard_event_callback, pointer_event_callback, wheel_event_callback,
+      system_window));
 }
 
 }  // namespace input
diff --git a/src/cobalt/input/input_poller_impl.cc b/src/cobalt/input/input_poller_impl.cc
index 526415c..008c249 100644
--- a/src/cobalt/input/input_poller_impl.cc
+++ b/src/cobalt/input/input_poller_impl.cc
@@ -93,6 +93,12 @@
           NOTREACHED();
       }
     } break;
+    case system_window::InputEvent::kPointerDown:
+    case system_window::InputEvent::kPointerUp:
+    case system_window::InputEvent::kPointerMove:
+    case system_window::InputEvent::kWheel:
+      // Pointer and Wheel events are ignored here.
+      break;
     default:
       NOTREACHED();
   }
diff --git a/src/cobalt/input/key_event_handler.cc b/src/cobalt/input/key_event_handler.cc
index 99b824f..9a1e8c2 100644
--- a/src/cobalt/input/key_event_handler.cc
+++ b/src/cobalt/input/key_event_handler.cc
@@ -18,8 +18,8 @@
 namespace input {
 
 void KeyEventHandler::HandleKeyboardEvent(
-    const dom::KeyboardEvent::Data& keyboard_event) {
-  DispatchKeyboardEvent(keyboard_event);
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) {
+  DispatchKeyboardEvent(type, keyboard_event);
 }
 
 KeyEventHandler::KeyEventHandler(const KeyboardEventCallback& callback)
@@ -29,13 +29,13 @@
     : keyboard_event_filter_(filter) {}
 
 void KeyEventHandler::DispatchKeyboardEvent(
-    const dom::KeyboardEvent::Data& keyboard_event) const {
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) const {
   // If we have a key filter attached to this object, let it filter the event,
   // otherwise call the stored callback function directly.
   if (keyboard_event_filter_) {
-    keyboard_event_filter_->HandleKeyboardEvent(keyboard_event);
+    keyboard_event_filter_->HandleKeyboardEvent(type, keyboard_event);
   } else {
-    keyboard_event_callback_.Run(keyboard_event);
+    keyboard_event_callback_.Run(type, keyboard_event);
   }
 }
 
diff --git a/src/cobalt/input/key_event_handler.h b/src/cobalt/input/key_event_handler.h
index a208139..e418dd5 100644
--- a/src/cobalt/input/key_event_handler.h
+++ b/src/cobalt/input/key_event_handler.h
@@ -16,12 +16,13 @@
 #define COBALT_INPUT_KEY_EVENT_HANDLER_H_
 
 #include "base/callback.h"
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/base/token.h"
+#include "cobalt/dom/keyboard_event_init.h"
 
 namespace cobalt {
 namespace input {
 
-typedef base::Callback<void(const dom::KeyboardEvent::Data&)>
+typedef base::Callback<void(base::Token type, const dom::KeyboardEventInit&)>
     KeyboardEventCallback;
 
 // Base class for objects that can process keyboard events.
@@ -36,7 +37,8 @@
   // DispatchKeyboardEvent. Overridden versions of this function may modify the
   // incoming event and/or generate new events. Overriden versions should call
   // DispatchKeyboardEvent for each event filtered/produced.
-  virtual void HandleKeyboardEvent(const dom::KeyboardEvent::Data& event);
+  virtual void HandleKeyboardEvent(base::Token type,
+                                   const dom::KeyboardEventInit& event);
 
  protected:
   explicit KeyEventHandler(const KeyboardEventCallback& callback);
@@ -48,7 +50,8 @@
   // Called to dispatch a key event. The event will either be sent to the key
   // event filter attached to this object or passed directly to the stored
   // callback function if there is no attached filter.
-  void DispatchKeyboardEvent(const dom::KeyboardEvent::Data& event) const;
+  void DispatchKeyboardEvent(base::Token type,
+                             const dom::KeyboardEventInit& event) const;
 
  private:
   // The event callback should not be called directly by subclasses.
diff --git a/src/cobalt/input/key_repeat_filter.cc b/src/cobalt/input/key_repeat_filter.cc
index ccc0b37..20a0eb4 100644
--- a/src/cobalt/input/key_repeat_filter.cc
+++ b/src/cobalt/input/key_repeat_filter.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/input/key_repeat_filter.h"
 
+#include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
 
 namespace cobalt {
@@ -36,22 +37,22 @@
     : KeyEventHandler(filter) {}
 
 void KeyRepeatFilter::HandleKeyboardEvent(
-    const dom::KeyboardEvent::Data& keyboard_event) {
-  if (keyboard_event.type == dom::KeyboardEvent::kTypeKeyDown) {
-    HandleKeyDown(keyboard_event);
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) {
+  if (type == base::Tokens::keydown()) {
+    HandleKeyDown(type, keyboard_event);
   }
 
-  if (keyboard_event.type == dom::KeyboardEvent::kTypeKeyUp) {
-    HandleKeyUp(keyboard_event);
+  if (type == base::Tokens::keyup()) {
+    HandleKeyUp(type, keyboard_event);
   }
 }
 
 void KeyRepeatFilter::HandleKeyDown(
-    const dom::KeyboardEvent::Data& keyboard_event) {
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) {
   // Record the information of the KeyboardEvent for firing repeat events.
   last_event_data_ = keyboard_event;
 
-  DispatchKeyboardEvent(keyboard_event);
+  DispatchKeyboardEvent(type, keyboard_event);
 
   // This key down event is triggered for the first time, so start the timer
   // with |kRepeatInitialDelay|.
@@ -60,21 +61,21 @@
 }
 
 void KeyRepeatFilter::HandleKeyUp(
-    const dom::KeyboardEvent::Data& keyboard_event) {
-  DispatchKeyboardEvent(keyboard_event);
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) {
+  DispatchKeyboardEvent(type, keyboard_event);
 
   // If it is a key up event and it matches the previous one, stop the key
   // repeat timer.
-  if (last_event_data_->key_code == keyboard_event.key_code) {
+  if (last_event_data_->key_code() == keyboard_event.key_code()) {
     key_repeat_timer_.Stop();
   }
 }
 
 void KeyRepeatFilter::FireKeyRepeatEvent() {
-  dom::KeyboardEvent::Data repeat_event(*last_event_data_);
-  repeat_event.repeat = true;
+  dom::KeyboardEventInit repeat_event(*last_event_data_);
+  repeat_event.set_repeat(true);
 
-  DispatchKeyboardEvent(repeat_event);
+  DispatchKeyboardEvent(base::Tokens::keydown(), repeat_event);
 
   // If |FireKeyRepeatEvent| is triggered for the first time then reset the
   // timer to the repeat rate instead of the initial delay.
diff --git a/src/cobalt/input/key_repeat_filter.h b/src/cobalt/input/key_repeat_filter.h
index e1569b7..6b1dd0c 100644
--- a/src/cobalt/input/key_repeat_filter.h
+++ b/src/cobalt/input/key_repeat_filter.h
@@ -32,14 +32,16 @@
   explicit KeyRepeatFilter(KeyEventHandler* filter);
 
   void HandleKeyboardEvent(
-      const dom::KeyboardEvent::Data& keyboard_event) OVERRIDE;
+      base::Token type, const dom::KeyboardEventInit& keyboard_event) OVERRIDE;
 
  private:
-  void HandleKeyDown(const dom::KeyboardEvent::Data& keyboard_event);
-  void HandleKeyUp(const dom::KeyboardEvent::Data& keyboard_event);
+  void HandleKeyDown(base::Token type,
+                     const dom::KeyboardEventInit& keyboard_event);
+  void HandleKeyUp(base::Token type,
+                   const dom::KeyboardEventInit& keyboard_event);
   void FireKeyRepeatEvent();
 
-  base::optional<dom::KeyboardEvent::Data> last_event_data_;
+  base::optional<dom::KeyboardEventInit> last_event_data_;
 
   base::RepeatingTimer<KeyRepeatFilter> key_repeat_timer_;
 };
diff --git a/src/cobalt/input/keypress_generator_filter.cc b/src/cobalt/input/keypress_generator_filter.cc
index dab9a89..c34f05a 100644
--- a/src/cobalt/input/keypress_generator_filter.cc
+++ b/src/cobalt/input/keypress_generator_filter.cc
@@ -15,6 +15,7 @@
 #include "cobalt/input/keypress_generator_filter.h"
 
 #include "cobalt/base/tokens.h"
+#include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/keycode.h"
 
 namespace cobalt {
@@ -28,41 +29,36 @@
     : KeyEventHandler(filter) {}
 
 void KeypressGeneratorFilter::HandleKeyboardEvent(
-    const dom::KeyboardEvent::Data& keyboard_event) {
+    base::Token type, const dom::KeyboardEventInit& keyboard_event) {
   // Handle the original event
-  DispatchKeyboardEvent(keyboard_event);
+  DispatchKeyboardEvent(type, keyboard_event);
 
   // If the event was a keydown, we may also generate a keypress event.
-  ConditionallyGenerateKeypressEvent(keyboard_event);
+  ConditionallyGenerateKeypressEvent(type, keyboard_event);
 }
 
 bool KeypressGeneratorFilter::ConditionallyGenerateKeypressEvent(
-    const dom::KeyboardEvent::Data& orig_event) {
+    base::Token type, const dom::KeyboardEventInit& orig_event) {
   // Ignore everything but keydown events.
-  if (orig_event.type != dom::KeyboardEvent::kTypeKeyDown) {
+  if (type != base::Tokens::keydown()) {
     return false;
   }
 
   // Don't generate a keypress event if one of the modifier keys other than
   // Shift is held down.
-  if (orig_event.modifiers & dom::UIEventWithKeyState::kAltKey ||
-      orig_event.modifiers & dom::UIEventWithKeyState::kCtrlKey ||
-      orig_event.modifiers & dom::UIEventWithKeyState::kMetaKey) {
+  if (orig_event.alt_key() || orig_event.ctrl_key() || orig_event.meta_key()) {
     return false;
   }
 
   // Get the char_code corresponding to the key_code of the event.
   // Only generate the keypress event if there is a valid char_code.
-  int key_code = orig_event.key_code;
-  int char_code =
-      dom::KeyboardEvent::ComputeCharCode(key_code, orig_event.modifiers);
+  int char_code = dom::KeyboardEvent::ComputeCharCode(orig_event.key_code(),
+                                                      orig_event.shift_key());
 
   if (char_code > 0) {
-    dom::KeyboardEvent::Data keypress_event(
-        dom::KeyboardEvent::kTypeKeyPress,
-        dom::KeyboardEvent::kDomKeyLocationStandard, orig_event.modifiers,
-        key_code, char_code, orig_event.repeat);
-    DispatchKeyboardEvent(keypress_event);
+    dom::KeyboardEventInit event(orig_event);
+    event.set_char_code(char_code);
+    DispatchKeyboardEvent(base::Tokens::keypress(), event);
     return true;
   }
 
diff --git a/src/cobalt/input/keypress_generator_filter.h b/src/cobalt/input/keypress_generator_filter.h
index 9ea280d..548b98d 100644
--- a/src/cobalt/input/keypress_generator_filter.h
+++ b/src/cobalt/input/keypress_generator_filter.h
@@ -15,7 +15,8 @@
 #ifndef COBALT_INPUT_KEYPRESS_GENERATOR_FILTER_H_
 #define COBALT_INPUT_KEYPRESS_GENERATOR_FILTER_H_
 
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/base/token.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/input/key_event_handler.h"
 
 namespace cobalt {
@@ -31,14 +32,15 @@
 
   // Conditionally generates an additional keypress event.
   // Passes on the new and original events for further processing/handling.
-  void HandleKeyboardEvent(const dom::KeyboardEvent::Data& event) OVERRIDE;
+  void HandleKeyboardEvent(base::Token type,
+                           const dom::KeyboardEventInit& event) OVERRIDE;
 
  protected:
   // Generates a keypress event, if:
   // 1. The original event is a keydown.
   // 2. The keycode corresponds to a printable character, or BS/Enter.
   bool ConditionallyGenerateKeypressEvent(
-      const dom::KeyboardEvent::Data& orig_event);
+      base::Token type, const dom::KeyboardEventInit& orig_event);
 };
 
 }  // namespace input
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 269f2d1..1f12a66 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -31,7 +31,10 @@
 #include "cobalt/layout/render_tree_animations.h"
 #include "cobalt/layout/size_layout_unit.h"
 #include "cobalt/layout/used_style.h"
+#include "cobalt/math/rect_f.h"
 #include "cobalt/math/transform_2d.h"
+#include "cobalt/math/vector2d.h"
+#include "cobalt/math/vector2d_f.h"
 #include "cobalt/render_tree/border.h"
 #include "cobalt/render_tree/brush.h"
 #include "cobalt/render_tree/color_rgba.h"
@@ -64,7 +67,8 @@
     : css_computed_style_declaration_(css_computed_style_declaration),
       used_style_provider_(used_style_provider),
       layout_stat_tracker_(layout_stat_tracker),
-      parent_(NULL) {
+      parent_(NULL),
+      render_sequence_(0) {
   DCHECK(animations());
   DCHECK(used_style_provider_);
 
@@ -410,6 +414,7 @@
     if (cached_render_tree_node_info_->node_) {
       parent_content_node_builder->AddChild(
           cached_render_tree_node_info_->node_);
+      render_sequence_ = parent_content_node_builder->children().size();
     }
     return;
   }
@@ -544,9 +549,43 @@
                   new AnimateNode(animate_node_builder, border_node));
 
     parent_content_node_builder->AddChild(cached_render_tree_node_info_->node_);
+    render_sequence_ = parent_content_node_builder->children().size();
   }
 }
 
+Box::RenderSequence Box::GetRenderSequence() {
+  std::vector<size_t> render_sequence;
+  Box* ancestor_box = this;
+  Box* box = NULL;
+  while (ancestor_box && (box != ancestor_box)) {
+    box = ancestor_box;
+    if (box->cached_render_tree_node_info_) {
+      render_sequence.push_back(box->render_sequence_);
+      if (box->IsPositioned() || box->IsTransformed()) {
+        ancestor_box = box->GetStackingContext();
+      } else {
+        ancestor_box = box->GetContainingBlock();
+      }
+    }
+  }
+  return render_sequence;
+}
+
+bool Box::IsRenderedLater(RenderSequence render_sequence,
+                          RenderSequence other_render_sequence) {
+  for (size_t step = 0; step < render_sequence.size(); ++step) {
+    if (other_render_sequence.size() < 1 + step) {
+      return true;
+    }
+    size_t idx = render_sequence.size() - 1 - step;
+    size_t other_idx = other_render_sequence.size() - 1 - step;
+    if (render_sequence[idx] != other_render_sequence[other_idx]) {
+      return render_sequence[idx] > other_render_sequence[other_idx];
+    }
+  }
+  return false;
+}
+
 AnonymousBlockBox* Box::AsAnonymousBlockBox() { return NULL; }
 ContainerBox* Box::AsContainerBox() { return NULL; }
 const ContainerBox* Box::AsContainerBox() const { return NULL; }
@@ -782,6 +821,14 @@
   }
 }
 
+bool Box::IsUnderCoordinate(const Vector2dLayoutUnit& coordinate) const {
+  RectLayoutUnit rect = GetBorderBox();
+  bool res =
+      coordinate.x() >= rect.x() && coordinate.x() <= rect.x() + rect.width() &&
+      coordinate.y() >= rect.y() && coordinate.y() <= rect.y() + rect.height();
+  return res;
+}
+
 void Box::UpdateCrossReferencesOfContainerBox(
     ContainerBox* source_box, bool is_nearest_containing_block,
     bool is_nearest_absolute_containing_block,
@@ -1299,5 +1346,30 @@
   }
 }
 
+void Box::UpdateCoordinateForTransform(math::Vector2dF* coordinate) const {
+  // If the element has a transform, it needs to be applied.
+  if (!computed_style()) {
+    return;
+  }
+  const scoped_refptr<cssom::PropertyValue>& transform =
+      computed_style()->transform();
+  if (transform != cssom::KeywordValue::GetNone()) {
+    math::Vector2dF border_box_offset(
+        left().toFloat() + margin_left().toFloat(),
+        top().toFloat() + margin_top().toFloat());
+    math::RectF rect = math::RectF(PointAtOffsetFromOrigin(border_box_offset),
+                                   GetBorderBoxSize());
+    math::Matrix3F matrix =
+        GetCSSTransform(transform, computed_style()->transform_origin(), rect);
+    if (!matrix.IsIdentity()) {
+      // transform the coordinate.
+      math::PointF transformed_point =
+          matrix.Inverse() * math::PointF(coordinate->x(), coordinate->y());
+      coordinate->set_x(transformed_point.x());
+      coordinate->set_y(transformed_point.y());
+    }
+  }
+}
+
 }  // namespace layout
 }  // namespace cobalt
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 3484227..06e90ea 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -34,6 +34,8 @@
 #include "cobalt/layout/vector2d_layout_unit.h"
 #include "cobalt/math/point_f.h"
 #include "cobalt/math/rect_f.h"
+#include "cobalt/math/vector2d.h"
+#include "cobalt/math/vector2d_f.h"
 #include "cobalt/render_tree/animations/animate_node.h"
 #include "cobalt/render_tree/composition_node.h"
 #include "cobalt/web_animations/animation_set.h"
@@ -107,6 +109,14 @@
     kInlineLevel,
   };
 
+  // The RenderSequence of a box is used to compare the relative drawing order
+  // of boxes. It stores a value for the box's drawing order at each ancestor
+  // composition node up to the root of the render tree. As a result, starting
+  // from the root ancestor, the box for which the render sequence ends first,
+  // or for which the drawing order value at a composition node is lower is
+  // drawn before the other box.
+  typedef std::vector<size_t> RenderSequence;
+
   Box(const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
           css_computed_style_declaration,
       UsedStyleProvider* used_style_provider,
@@ -140,7 +150,7 @@
   // Do not confuse with the formatting context that the element may establish.
   virtual Level GetLevel() const = 0;
 
-  // Returns true if the box is positioned (e.g. position is non-static or
+  // Returns trueif the box is positioned (e.g. position is non-static or
   // transform is not None).  Intuitively, this is true if the element does
   // not follow standard layout flow rules for determining its position.
   //   https://www.w3.org/TR/CSS21/visuren.html#positioned-element.
@@ -469,6 +479,22 @@
   // layout.
   void InvalidateParent() { parent_ = NULL; }
 
+  // Returns true if the box is positioned under the passed in coordinate.
+  bool IsUnderCoordinate(const Vector2dLayoutUnit& coordinate) const;
+
+  // Returns a data structure that can be used by Box::IsRenderedLater().
+  RenderSequence GetRenderSequence();
+
+  // Returns true if the box for the given render_sequence is rendered after
+  // the box for the other_render_sequence. The boxes must be from the same
+  // layout tree.
+  static bool IsRenderedLater(RenderSequence render_sequence,
+                              RenderSequence other_render_sequence);
+
+  // Updates the passed coordinate corresponding to the transform applied to
+  // this box.
+  void UpdateCoordinateForTransform(math::Vector2dF* coordinate) const;
+
  protected:
   UsedStyleProvider* used_style_provider() const {
     return used_style_provider_;
@@ -697,6 +723,11 @@
   // recalculated during each call to RenderAndAnimateContent.
   base::optional<CachedRenderTreeNodeInfo> cached_render_tree_node_info_;
 
+  // A value that indicates the drawing order relative to boxes with the same
+  // rendering ancestor box (which is either the stacking context or the
+  // containing block). Smaller values indicate boxes that are drawn earlier.
+  size_t render_sequence_;
+
   // For write access to parent/containing_block members.
   friend class ContainerBox;
   friend class LayoutBoxes;
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc
index e1f5288..5f9c536 100644
--- a/src/cobalt/layout/box_generator.cc
+++ b/src/cobalt/layout/box_generator.cc
@@ -918,8 +918,8 @@
   AppendPseudoElementToLine(html_element, dom::kBeforePseudoElementType);
 
   // Generate child boxes.
-  for (scoped_refptr<dom::Node> child_node = html_element->first_child();
-       child_node; child_node = child_node->next_sibling()) {
+  for (dom::Node* child_node = html_element->first_child(); child_node;
+       child_node = child_node->next_sibling()) {
     BoxGenerator child_box_generator(
         html_element->css_computed_style_declaration(),
         html_element->css_computed_style_declaration()->animations(),
diff --git a/src/cobalt/layout/layout.gyp b/src/cobalt/layout/layout.gyp
index e1b9469..fa7c03c 100644
--- a/src/cobalt/layout/layout.gyp
+++ b/src/cobalt/layout/layout.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
@@ -78,6 +78,8 @@
         'size_layout_unit.h',
         'text_box.cc',
         'text_box.h',
+        'topmost_event_target.cc',
+        'topmost_event_targer.h',
         'used_style.cc',
         'used_style.h',
         'vector2d_layout_unit.cc',
@@ -94,6 +96,11 @@
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
       ],
+      # Exporting dom so that layout_test gets the transitive include paths to
+      # include generated headers.
+      'export_dependent_settings': [
+        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+      ],
     },
 
     {
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index c5be2b6..ba99c1f 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -42,9 +42,9 @@
  public:
   Impl(const std::string& name, const scoped_refptr<dom::Window>& window,
        const OnRenderTreeProducedCallback& on_render_tree_produced,
-       LayoutTrigger layout_trigger, int dom_max_element_depth,
-       float layout_refresh_rate, const std::string& language,
-       LayoutStatTracker* layout_stat_tracker);
+       const OnLayoutCallback& on_layout, LayoutTrigger layout_trigger,
+       int dom_max_element_depth, float layout_refresh_rate,
+       const std::string& language, LayoutStatTracker* layout_stat_tracker);
   ~Impl();
 
   // From dom::DocumentObserver.
@@ -72,6 +72,7 @@
   const icu::Locale locale_;
   const scoped_ptr<UsedStyleProvider> used_style_provider_;
   const OnRenderTreeProducedCallback on_render_tree_produced_callback_;
+  const OnLayoutCallback on_layout_callback_;
   const LayoutTrigger layout_trigger_;
 
   // This flag indicates whether or not we should do a re-layout.  The flag
@@ -150,15 +151,16 @@
 LayoutManager::Impl::Impl(
     const std::string& name, const scoped_refptr<dom::Window>& window,
     const OnRenderTreeProducedCallback& on_render_tree_produced,
-    LayoutTrigger layout_trigger, int dom_max_element_depth,
-    float layout_refresh_rate, const std::string& language,
-    LayoutStatTracker* layout_stat_tracker)
+    const OnLayoutCallback& on_layout, LayoutTrigger layout_trigger,
+    int dom_max_element_depth, float layout_refresh_rate,
+    const std::string& language, LayoutStatTracker* layout_stat_tracker)
     : window_(window),
       locale_(icu::Locale::createCanonical(language.c_str())),
       used_style_provider_(new UsedStyleProvider(
           window->html_element_context(), window->document()->font_cache(),
           base::Bind(&AttachCameraNodes, window))),
       on_render_tree_produced_callback_(on_render_tree_produced),
+      on_layout_callback_(on_layout),
       layout_trigger_(layout_trigger),
       layout_dirty_(StringPrintf("%s.Layout.IsDirty", name.c_str()), true,
                     "Non-zero when the layout is dirty and a new render tree "
@@ -361,17 +363,19 @@
 
     TRACE_EVENT_END0("cobalt::layout", kBenchmarkStatLayout);
   }
+
+  on_layout_callback_.Run();
 }
 
 LayoutManager::LayoutManager(
     const std::string& name, const scoped_refptr<dom::Window>& window,
     const OnRenderTreeProducedCallback& on_render_tree_produced,
-    LayoutTrigger layout_trigger, const int dom_max_element_depth,
-    const float layout_refresh_rate, const std::string& language,
-    LayoutStatTracker* layout_stat_tracker)
-    : impl_(new Impl(name, window, on_render_tree_produced, layout_trigger,
-                     dom_max_element_depth, layout_refresh_rate, language,
-                     layout_stat_tracker)) {}
+    const OnLayoutCallback& on_layout, LayoutTrigger layout_trigger,
+    const int dom_max_element_depth, const float layout_refresh_rate,
+    const std::string& language, LayoutStatTracker* layout_stat_tracker)
+    : impl_(new Impl(name, window, on_render_tree_produced, on_layout,
+                     layout_trigger, dom_max_element_depth, layout_refresh_rate,
+                     language, layout_stat_tracker)) {}
 
 LayoutManager::~LayoutManager() {}
 
diff --git a/src/cobalt/layout/layout_manager.h b/src/cobalt/layout/layout_manager.h
index e02cc0b..f2da289 100644
--- a/src/cobalt/layout/layout_manager.h
+++ b/src/cobalt/layout/layout_manager.h
@@ -49,6 +49,8 @@
   typedef base::Callback<void(const LayoutResults&)>
       OnRenderTreeProducedCallback;
 
+  typedef base::Callback<void()> OnLayoutCallback;
+
   // Specifies what event should trigger a layout, and hence what event
   // will result in a render tree being produced and passed in a call to
   // on_render_tree_produced_callback_.
@@ -62,7 +64,8 @@
   LayoutManager(const std::string& name,
                 const scoped_refptr<dom::Window>& window,
                 const OnRenderTreeProducedCallback& on_render_tree_produced,
-                LayoutTrigger layout_trigger, const int dom_max_element_depth,
+                const OnLayoutCallback& on_layout, LayoutTrigger layout_trigger,
+                const int dom_max_element_depth,
                 const float layout_refresh_rate, const std::string& language,
                 LayoutStatTracker* layout_stat_tracker);
   ~LayoutManager();
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc
new file mode 100644
index 0000000..547d103
--- /dev/null
+++ b/src/cobalt/layout/topmost_event_target.cc
@@ -0,0 +1,265 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/layout/topmost_event_target.h"
+
+#include <string>
+
+#include "cobalt/base/token.h"
+#include "cobalt/base/tokens.h"
+#include "cobalt/cssom/keyword_value.h"
+#include "cobalt/dom/document.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/dom/html_element.h"
+#include "cobalt/dom/html_html_element.h"
+#include "cobalt/dom/mouse_event.h"
+#include "cobalt/dom/mouse_event_init.h"
+#include "cobalt/dom/pointer_event.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/ui_event.h"
+#include "cobalt/dom/wheel_event.h"
+#include "cobalt/layout/container_box.h"
+#include "cobalt/layout/used_style.h"
+#include "cobalt/math/vector2d.h"
+#include "cobalt/math/vector2d_f.h"
+
+namespace cobalt {
+namespace layout {
+
+void TopmostEventTarget::FindTopmostEventTarget(
+    const scoped_refptr<dom::Document>& document,
+    const math::Vector2dF& coordinate) {
+  const scoped_refptr<dom::HTMLElement>& html_element = document->html();
+  box_ = NULL;
+  html_element_ = html_element;
+  render_sequence_.clear();
+  if (html_element) {
+    dom::LayoutBoxes* boxes = html_element->layout_boxes();
+    if (boxes && boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) {
+      LayoutBoxes* layout_boxes = base::polymorphic_downcast<LayoutBoxes*>(
+          html_element->layout_boxes());
+      if (!layout_boxes->boxes().empty()) {
+        ConsiderElement(html_element, coordinate);
+      }
+    }
+  }
+}
+
+void TopmostEventTarget::ConsiderElement(
+    const scoped_refptr<dom::HTMLElement>& html_element,
+    const math::Vector2dF& coordinate) {
+  if (!html_element) return;
+  math::Vector2dF element_coordinate(coordinate);
+  dom::LayoutBoxes* boxes = html_element->layout_boxes();
+  if (boxes && boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) {
+    SB_DCHECK(html_element->computed_style());
+    LayoutBoxes* layout_boxes = base::polymorphic_downcast<LayoutBoxes*>(boxes);
+    const Boxes& boxes = layout_boxes->boxes();
+    if (!boxes.empty()) {
+      const Box* box = boxes.front();
+      box->UpdateCoordinateForTransform(&element_coordinate);
+
+      if (box->computed_style()->position() ==
+          cssom::KeywordValue::GetAbsolute()) {
+        // The containing block for position:absolute elements is formed by the
+        // padding box instead of the content box, as described in
+        // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details.
+        element_coordinate +=
+            box->GetContainingBlock()->GetContentBoxOffsetFromPaddingBox();
+      }
+      ConsiderBoxes(html_element, layout_boxes, element_coordinate);
+    }
+  }
+
+  for (dom::Element* element = html_element->first_element_child(); element;
+       element = element->next_element_sibling()) {
+    dom::HTMLElement* child_html_element = element->AsHTMLElement();
+    if (child_html_element && child_html_element->computed_style()) {
+      ConsiderElement(child_html_element, element_coordinate);
+    }
+  }
+}
+
+void TopmostEventTarget::ConsiderBoxes(
+    const scoped_refptr<dom::HTMLElement>& html_element,
+    LayoutBoxes* layout_boxes, const math::Vector2dF& coordinate) {
+  const Boxes& boxes = layout_boxes->boxes();
+  Vector2dLayoutUnit layout_coordinate(LayoutUnit(coordinate.x()),
+                                       LayoutUnit(coordinate.y()));
+  for (Boxes::const_iterator box_iterator = boxes.begin();
+       box_iterator != boxes.end(); ++box_iterator) {
+    const scoped_refptr<Box>& box = *box_iterator;
+    if (box->IsUnderCoordinate(layout_coordinate)) {
+      Box::RenderSequence render_sequence = box->GetRenderSequence();
+      if (Box::IsRenderedLater(render_sequence, render_sequence_)) {
+        html_element_ = html_element;
+        box_ = box;
+        render_sequence_.swap(render_sequence);
+      }
+    }
+  }
+}
+
+void TopmostEventTarget::MaybeSendPointerEvents(
+    const scoped_refptr<dom::Event>& event,
+    const scoped_refptr<dom::Window>& window) {
+  const dom::MouseEvent* const mouse_event =
+      base::polymorphic_downcast<const dom::MouseEvent* const>(event.get());
+  SB_DCHECK(mouse_event);
+  const scoped_refptr<dom::Document>& document =
+      mouse_event->view()->document();
+
+  math::Vector2dF coordinate(mouse_event->client_x(), mouse_event->client_y());
+  FindTopmostEventTarget(document, coordinate);
+
+  if (html_element_) {
+    html_element_->DispatchEvent(event);
+  }
+  if (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) {
+    const dom::PointerEvent* const pointer_event =
+        base::polymorphic_downcast<const dom::PointerEvent* const>(event.get());
+
+    // Send compatibility mapping mouse events if needed.
+    //  https://www.w3.org/TR/2015/REC-pointerevents-20150224/#compatibility-mapping-with-mouse-events
+    if (html_element_) {
+      bool has_compatibility_mouse_event = false;
+      base::Token type;
+      if (pointer_event->type() == base::Tokens::pointerdown()) {
+        type = base::Tokens::mousedown();
+        has_compatibility_mouse_event = true;
+      } else if (pointer_event->type() == base::Tokens::pointerup()) {
+        type = base::Tokens::mouseup();
+        has_compatibility_mouse_event = true;
+      } else if (pointer_event->type() == base::Tokens::pointermove()) {
+        type = base::Tokens::mousemove();
+        has_compatibility_mouse_event = true;
+      }
+      if (has_compatibility_mouse_event) {
+        dom::MouseEventInit mouse_event_init;
+        mouse_event_init.set_screen_x(pointer_event->screen_x());
+        mouse_event_init.set_screen_y(pointer_event->screen_x());
+        mouse_event_init.set_client_x(pointer_event->screen_x());
+        mouse_event_init.set_client_y(pointer_event->screen_x());
+        mouse_event_init.set_button(pointer_event->button());
+        mouse_event_init.set_buttons(pointer_event->buttons());
+        html_element_->DispatchEvent(
+            new dom::MouseEvent(type, window, mouse_event_init));
+        if (pointer_event->type() == base::Tokens::pointerup()) {
+          type = base::Tokens::click();
+          html_element_->DispatchEvent(
+              new dom::MouseEvent(type, window, mouse_event_init));
+        }
+      }
+    }
+
+    // Send enter/leave/over/out (status change) events when needed.
+    if (previous_html_element_ != html_element_) {
+      // Store the data for the status change event(s).
+      dom::PointerEventInit event_init;
+      event_init.set_related_target(previous_html_element_);
+      const dom::MouseEvent* const pointer_event =
+          base::polymorphic_downcast<const dom::PointerEvent* const>(
+              event.get());
+      event_init.set_screen_x(pointer_event->screen_x());
+      event_init.set_screen_y(pointer_event->screen_x());
+      event_init.set_client_x(pointer_event->screen_x());
+      event_init.set_client_y(pointer_event->screen_x());
+      if (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) {
+        const dom::PointerEvent* const pointer_event =
+            base::polymorphic_downcast<const dom::PointerEvent* const>(
+                event.get());
+        event_init.set_pointer_id(pointer_event->pointer_id());
+        event_init.set_width(pointer_event->width());
+        event_init.set_height(pointer_event->height());
+        event_init.set_pressure(pointer_event->pressure());
+        event_init.set_tilt_x(pointer_event->tilt_x());
+        event_init.set_tilt_y(pointer_event->tilt_y());
+        event_init.set_pointer_type(pointer_event->pointer_type());
+        event_init.set_is_primary(pointer_event->is_primary());
+      }
+
+      // The enter/leave status change events apply to all ancestors up to the
+      // nearest common ancestor between the previous and current element.
+      scoped_refptr<dom::Element> nearest_common_ancestor;
+
+      if (previous_html_element_) {
+        previous_html_element_->DispatchEvent(new dom::PointerEvent(
+            base::Tokens::pointerout(), window, event_init));
+        previous_html_element_->DispatchEvent(
+            new dom::MouseEvent(base::Tokens::mouseout(), window, event_init));
+
+        // Find the nearest common ancestor, if there is any.
+        dom::Document* previous_document =
+            previous_html_element_->node_document();
+        if (previous_document) {
+          if (html_element_ &&
+              previous_document == html_element_->node_document()) {
+            // The nearest ancestor of the current element that is already
+            // designated is the nearest common ancestor of it and the previous
+            // element.
+            nearest_common_ancestor = html_element_;
+            while (nearest_common_ancestor &&
+                   nearest_common_ancestor->AsHTMLElement() &&
+                   !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) {
+              nearest_common_ancestor =
+                  nearest_common_ancestor->parent_element();
+            }
+          }
+
+          for (scoped_refptr<dom::Element> element = previous_html_element_;
+               element != nearest_common_ancestor;
+               element = element->parent_element()) {
+            element->DispatchEvent(new dom::PointerEvent(
+                base::Tokens::pointerleave(), dom::Event::kNotBubbles,
+                dom::Event::kNotCancelable, window, event_init));
+            element->DispatchEvent(new dom::MouseEvent(
+                base::Tokens::mouseleave(), dom::Event::kNotBubbles,
+                dom::Event::kNotCancelable, window, event_init));
+          }
+
+          if (!html_element_ ||
+              previous_document != html_element_->node_document()) {
+            previous_document->SetIndicatedElement(NULL);
+          }
+        }
+      }
+      if (html_element_) {
+        html_element_->DispatchEvent(new dom::PointerEvent(
+            base::Tokens::pointerover(), window, event_init));
+        html_element_->DispatchEvent(
+            new dom::MouseEvent(base::Tokens::mouseover(), window, event_init));
+
+        for (scoped_refptr<dom::Element> element = html_element_;
+             element != nearest_common_ancestor;
+             element = element->parent_element()) {
+          element->DispatchEvent(new dom::PointerEvent(
+              base::Tokens::pointerenter(), dom::Event::kNotBubbles,
+              dom::Event::kNotCancelable, window, event_init));
+          element->DispatchEvent(new dom::MouseEvent(
+              base::Tokens::mouseenter(), dom::Event::kNotBubbles,
+              dom::Event::kNotCancelable, window, event_init));
+        }
+
+        dom::Document* document = html_element_->node_document();
+        if (document) {
+          document->SetIndicatedElement(html_element_);
+        }
+      }
+      previous_html_element_ = html_element_;
+    }
+  }
+}
+
+}  // namespace layout
+}  // namespace cobalt
diff --git a/src/cobalt/layout/topmost_event_target.h b/src/cobalt/layout/topmost_event_target.h
new file mode 100644
index 0000000..71e8166
--- /dev/null
+++ b/src/cobalt/layout/topmost_event_target.h
@@ -0,0 +1,55 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_LAYOUT_TOPMOST_EVENT_TARGET_H_
+#define COBALT_LAYOUT_TOPMOST_EVENT_TARGET_H_
+
+#include "cobalt/dom/document.h"
+#include "cobalt/dom/html_element.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/layout/box.h"
+#include "cobalt/layout/layout_boxes.h"
+#include "cobalt/math/vector2d.h"
+#include "cobalt/math/vector2d_f.h"
+
+namespace cobalt {
+namespace layout {
+
+class TopmostEventTarget {
+ public:
+  explicit TopmostEventTarget() {}
+
+  void MaybeSendPointerEvents(const scoped_refptr<dom::Event>& event,
+                              const scoped_refptr<dom::Window>& window);
+
+  scoped_refptr<dom::HTMLElement> previous_html_element_;
+  scoped_refptr<dom::HTMLElement> html_element_;
+  scoped_refptr<Box> box_;
+  Box::RenderSequence render_sequence_;
+
+ private:
+  void FindTopmostEventTarget(const scoped_refptr<dom::Document>& document,
+                              const math::Vector2dF& coordinate);
+
+  void ConsiderElement(const scoped_refptr<dom::HTMLElement>& html_element,
+                       const math::Vector2dF& coordinate);
+  void ConsiderBoxes(const scoped_refptr<dom::HTMLElement>& html_element,
+                     LayoutBoxes* layout_boxes,
+                     const math::Vector2dF& coordinate);
+};
+
+}  // namespace layout
+}  // namespace cobalt
+
+#endif  // COBALT_LAYOUT_TOPMOST_EVENT_TARGET_H_
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc
index 91e64d4..5c9dab7 100644
--- a/src/cobalt/layout/used_style.cc
+++ b/src/cobalt/layout/used_style.cc
@@ -710,69 +710,11 @@
   }
 }
 
-// Returns the corner points that should be used to calculate the source and
-// destination gradient points.  This is determined by which quadrant the
-// gradient direction vector lies within.
-std::pair<math::PointF, math::PointF>
-GetSourceAndDestinationPointsFromGradientVector(
-    const math::Vector2dF& gradient_vector, const math::SizeF& frame_size) {
-  std::pair<math::PointF, math::PointF> ret;
-  if (gradient_vector.x() >= 0 && gradient_vector.y() >= 0) {
-    ret.first = math::PointF(0, 0);
-    ret.second = math::PointF(frame_size.width(), frame_size.height());
-  } else if (gradient_vector.x() < 0 && gradient_vector.y() >= 0) {
-    ret.first = math::PointF(frame_size.width(), 0);
-    ret.second = math::PointF(0, frame_size.height());
-  } else if (gradient_vector.x() < 0 && gradient_vector.y() < 0) {
-    ret.first = math::PointF(frame_size.width(), frame_size.height());
-    ret.second = math::PointF(0, 0);
-  } else if (gradient_vector.x() >= 0 && gradient_vector.y() < 0) {
-    ret.first = math::PointF(0, frame_size.height());
-    ret.second = math::PointF(frame_size.width(), 0);
-  } else {
-    NOTREACHED();
-  }
-
-  return ret;
-}
-
-math::PointF IntersectLines(math::PointF point_a, math::Vector2dF dir_a,
-                            math::PointF point_b, math::Vector2dF dir_b) {
-  DCHECK(dir_a.y() != 0 || dir_b.y() != 0);
-
-  if (dir_a.x() == 0) {
-    // Swap a and b so that we are guaranteed not to divide by 0.
-    std::swap(point_a, point_b);
-    std::swap(dir_a, dir_b);
-  }
-
-  float slope_a = dir_a.y() / dir_a.x();
-
-  // Calculate how far from |point_b| we should travel in units of |dir_b|
-  // in order to reach the point of intersection.
-  float distance_from_point_b =
-      (point_a.y() - point_b.y() + slope_a * (point_b.x() - point_a.x())) /
-      (dir_b.y() - slope_a * dir_b.x());
-
-  dir_b.Scale(distance_from_point_b);
-  return point_b + dir_b;
-}
-
 std::pair<math::PointF, math::PointF> LinearGradientPointsFromAngle(
     float angle_in_radians, const math::SizeF& frame_size) {
   // The method of defining the source and destination points for the linear
   // gradient are defined here:
   //   https://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradients
-  //
-  // "Starting from the center of the gradient box, extend a line at the
-  //  specified angle in both directions. The ending point is the point on the
-  //  gradient line where a line drawn perpendicular to the gradient line would
-  //  intersect the corner of the gradient box in the specified direction. The
-  //  starting point is determined identically, but in the opposite direction."
-
-  // First determine the line parallel to the gradient angle.
-  math::PointF gradient_line_point(frame_size.width() / 2.0f,
-                                   frame_size.height() / 2.0f);
 
   // The angle specified by linear gradient has "up" as its origin direction
   // and rotates clockwise as the angle increases.  We must convert this to
@@ -780,29 +722,8 @@
   // we can pass it into the trigonometric functions cos() and sin().
   float ccw_angle_from_right = -angle_in_radians + static_cast<float>(M_PI / 2);
 
-  // Note that we flip the y value here since we move down in our screen space
-  // as y increases.
-  math::Vector2dF gradient_vector(
-      static_cast<float>(cos(ccw_angle_from_right)),
-      static_cast<float>(-sin(ccw_angle_from_right)));
-
-  // Determine the line direction that is perpendicular to the gradient line.
-  math::Vector2dF perpendicular_vector(-gradient_vector.y(),
-                                       gradient_vector.x());
-
-  // Determine the corner points that should be used to calculate the source
-  // and destination points, based on which quadrant the gradient direction
-  // vector lies within.
-  std::pair<math::PointF, math::PointF> corners =
-      GetSourceAndDestinationPointsFromGradientVector(gradient_vector,
-                                                      frame_size);
-
-  // Intersect the perpendicular line running through the source corner with
-  // the gradient line to get our source point.
-  return std::make_pair(IntersectLines(gradient_line_point, gradient_vector,
-                                       corners.first, perpendicular_vector),
-                        IntersectLines(gradient_line_point, gradient_vector,
-                                       corners.second, perpendicular_vector));
+  return render_tree::LinearGradientPointsFromAngle(
+      ccw_angle_from_right, frame_size);
 }
 
 // The specifications indicate that if positions are not specified for color
diff --git a/src/cobalt/layout_tests/layout_snapshot.cc b/src/cobalt/layout_tests/layout_snapshot.cc
index cb375a7..f57e2e5 100644
--- a/src/cobalt/layout_tests/layout_snapshot.cc
+++ b/src/cobalt/layout_tests/layout_snapshot.cc
@@ -82,14 +82,14 @@
 
   // Create the WebModule and wait for a layout to occur.
   browser::WebModule web_module(
-      url, base::Bind(&WebModuleOnRenderTreeProducedCallback, &results,
-                      &run_loop, MessageLoop::current()),
+      url, base::kApplicationStateStarted,
+      base::Bind(&WebModuleOnRenderTreeProducedCallback, &results, &run_loop,
+                 MessageLoop::current()),
       base::Bind(&WebModuleErrorCallback, &run_loop, MessageLoop::current()),
       base::Closure() /* window_close_callback */,
-      base::Closure() /* window_minimize_callback */,
-      stub_media_module.get(), &network_module, viewport_size,
-      resource_provider, stub_media_module->system_window(), 60.0f,
-      web_module_options);
+      base::Closure() /* window_minimize_callback */, stub_media_module.get(),
+      &network_module, viewport_size, resource_provider,
+      stub_media_module->system_window(), 60.0f, web_module_options);
 
   run_loop.Run();
 
diff --git a/src/cobalt/layout_tests/layout_tests.gyp b/src/cobalt/layout_tests/layout_tests.gyp
index 179732c..e0b1ce6 100644
--- a/src/cobalt/layout_tests/layout_tests.gyp
+++ b/src/cobalt/layout_tests/layout_tests.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
 
   # A library to support code that wishes to load a URL and take a snapshot
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
index 7532609..3fd8ec9 100644
--- a/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
+++ b/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
@@ -5,12 +5,17 @@
 #   json(), and arrayBuffer(). Blob, URLSearchParams, and FormData are not
 #   well-supported by the javascript engine. Certain tests may be substitued
 #   with *.cobalt.html versions to accommodate this limitation.
+
+# Failure is not specific to fetch.
 api/basic/block-mime-as-script.html,DISABLE
+# Caching responses is not supported.
 api/basic/conditional-get.html,DISABLE
 api/basic/error-after-response.html,PASS
+# Integrity validation is not supported.
 api/basic/integrity.html,DISABLE
 api/basic/integrity-sharedworker.html,DISABLE
 api/basic/integrity-worker.html,DISABLE
+# Mode is not supported.
 api/basic/mode-no-cors.html,DISABLE
 api/basic/mode-no-cors-worker.html,DISABLE
 api/basic/request-referrer-redirected-worker.html,DISABLE
@@ -22,7 +27,9 @@
 api/basic/scheme-blob-worker.html,DISABLE
 api/basic/scheme-others.html,PASS
 api/basic/scheme-others-worker.html,DISABLE
+# Only UTF-8 is supported. UTF-16 is not supported.
 api/basic/text-utf8.html,DISABLE
+# CORS is not supported.
 api/cors/cors-expose-star.html,DISABLE
 api/cors/cors-expose-star-worker.html,DISABLE
 api/cors/cors-filtering.html,DISABLE
@@ -44,8 +51,12 @@
 api/headers/header-values.html,DISABLE
 # Invalid: this tests XHR
 api/headers/header-values-normalize.html,DISABLE
+# Fails because a SecurityError is thrown instead of the expected TypeError.
+# The fetch spec does not explicitly say a TypeError should be thrown, so the
+# test may be wrong.
 api/policies/csp-blocked.html,DISABLE
 api/policies/csp-blocked-worker.html,DISABLE
+# Referrer is intentionally not supported due to privacy concerns.
 api/policies/referrer-no-referrer.html,DISABLE
 api/policies/referrer-no-referrer-service-worker.https.html,DISABLE
 api/policies/referrer-no-referrer-worker.html,DISABLE
@@ -58,6 +69,7 @@
 api/policies/referrer-unsafe-url.html,DISABLE
 api/policies/referrer-unsafe-url-service-worker.https.html,DISABLE
 api/policies/referrer-unsafe-url-worker.html,DISABLE
+# Redirect is not fully supported.
 api/redirect/redirect-count.html,DISABLE
 api/redirect/redirect-count-worker.html,DISABLE
 api/redirect/redirect-location.html,DISABLE
@@ -73,8 +85,10 @@
 api/redirect/redirect-schemes.html,DISABLE
 api/redirect/redirect-to-dataurl.html,DISABLE
 api/redirect/redirect-to-dataurl-worker.html,DISABLE
+# Test fails to run because 'iframe' is not supported.
 api/request/multi-globals/url-parsing.html,DISABLE
 api/request/request-bad-port.html,PASS
+# Fails because Cobalt does not support caching responses.
 api/request/request-cache-default.html,DISABLE
 api/request/request-cache-default-conditional.html,DISABLE
 api/request/request-cache-force-cache.html,DISABLE
@@ -82,17 +96,25 @@
 api/request/request-cache-no-store.html,PASS
 api/request/request-cache-only-if-cached.html,DISABLE
 api/request/request-cache-reload.html,DISABLE
-api/request/request-clone.sub.html,DISABLE
+api/request/request-clone.sub.html,PASS
+# Fails because Body only supports text, json, and arrayBuffer. See the
+# corresponding tests for Response which have been customized to test only
+# those types.
 api/request/request-consume.html,DISABLE
+# Fails because blob and formData are not supported.
 api/request/request-consume-empty.html,DISABLE
-api/request/request-disturbed.html,DISABLE
-api/request/request-error.html,DISABLE
-api/request/request-headers.html,DISABLE
+api/request/request-disturbed.html,PASS
+api/request/request-error.html,PASS
+api/request/request-headers.html,PASS
+# Fails because implementation is a polyfill.
 api/request/request-idl.html,DISABLE
-api/request/request-init-001.sub.html,DISABLE
+api/request/request-init-001.sub.html,PASS
+# Fails because Blob, FormData, and URLSearchParams are not supported.
 api/request/request-init-002.html,DISABLE
-api/request/request-init-003.sub.html,DISABLE
+api/request/request-init-003.sub.html,PASS
 api/request/request-keepalive-quota.html,DISABLE
+# Fails because: 1) Body does not support blob nor formData, and 2) Request
+# does not support destination nor type.
 api/request/request-structure.html,DISABLE
 api/response/multi-globals/url-parsing.html,DISABLE
 # Fails because Blob is not fully supported.
@@ -116,5 +138,5 @@
 api/response/response-stream-disturbed-3.html,PASS
 api/response/response-stream-disturbed-4.html,PASS
 api/response/response-stream-disturbed-5.html,PASS
-# Not implemented.
+# Fails because trailer is not implemented.
 api/response/response-trailer.html,DISABLE
diff --git a/src/cobalt/layout_tests/web_platform_tests.cc b/src/cobalt/layout_tests/web_platform_tests.cc
index 12e70e2..f990066 100644
--- a/src/cobalt/layout_tests/web_platform_tests.cc
+++ b/src/cobalt/layout_tests/web_platform_tests.cc
@@ -172,14 +172,14 @@
 
   // Create the WebModule and wait for a layout to occur.
   browser::WebModule web_module(
-      url, base::Bind(&WebModuleOnRenderTreeProducedCallback, &results,
-                      &run_loop, MessageLoop::current()),
+      url, base::kApplicationStateStarted,
+      base::Bind(&WebModuleOnRenderTreeProducedCallback, &results, &run_loop,
+                 MessageLoop::current()),
       base::Bind(&WebModuleErrorCallback, &run_loop, MessageLoop::current()),
       base::Closure() /* window_close_callback */,
-      base::Closure() /* window_minimize_callback */,
-      media_module.get(), &network_module, kDefaultViewportSize,
-      &resource_provider, media_module->system_window(), 60.0f,
-      web_module_options);
+      base::Closure() /* window_minimize_callback */, media_module.get(),
+      &network_module, kDefaultViewportSize, &resource_provider,
+      media_module->system_window(), 60.0f, web_module_options);
   run_loop.Run();
   const std::string extract_results =
       "document.getElementById(\"__testharness__results__\").textContent;";
diff --git a/src/cobalt/loader/image/image_data_decoder.h b/src/cobalt/loader/image/image_data_decoder.h
index 1dd5a52..2d648b5 100644
--- a/src/cobalt/loader/image/image_data_decoder.h
+++ b/src/cobalt/loader/image/image_data_decoder.h
@@ -46,7 +46,7 @@
   }
 
 #if defined(STARBOARD)
-#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+#if SB_API_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
@@ -59,7 +59,7 @@
   virtual SbDecodeTarget RetrieveSbDecodeTarget() {
     return kSbDecodeTargetInvalid;
   }
-#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 #endif  // defined(STARBOARD)
 
   void DecodeChunk(const uint8* data, size_t size);
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index a44f373..739c523 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -162,7 +162,7 @@
       if (decoder_->FinishWithSuccess()) {
         if (!decoder_->has_animation()) {
 #if defined(STARBOARD)
-#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
           SbDecodeTarget target = decoder_->RetrieveSbDecodeTarget();
           if (SbDecodeTargetIsValid(target)) {
             success_callback_.Run(new StaticImage(
@@ -272,7 +272,7 @@
 
 namespace {
 #if defined(STARBOARD)
-#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 const char* GetMimeTypeFromImageType(ImageDecoder::ImageType image_type) {
   switch (image_type) {
     case ImageDecoder::kImageTypeJPEG:
@@ -331,7 +331,7 @@
   }
   return scoped_ptr<ImageDataDecoder>();
 }
-#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 #endif  // defined(STARBOARD)
 
 scoped_ptr<ImageDataDecoder> CreateImageDecoderFromImageType(
@@ -384,10 +384,10 @@
   }
 
 #if defined(STARBOARD)
-#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
   decoder_ =
       MaybeCreateStarboardDecoder(mime_type_, image_type_, resource_provider_);
-#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 #endif  // defined(STARBOARD)
 
   if (!decoder_) {
diff --git a/src/cobalt/loader/image/image_decoder_starboard.h b/src/cobalt/loader/image/image_decoder_starboard.h
index 2cc98ef..39b6cbe 100644
--- a/src/cobalt/loader/image/image_decoder_starboard.h
+++ b/src/cobalt/loader/image/image_decoder_starboard.h
@@ -25,7 +25,7 @@
 #include "cobalt/loader/image/image_data_decoder.h"
 #include "starboard/decode_target.h"
 
-#if SB_VERSION(3) && SB_HAS(GRAPHICS)
+#if SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 
 namespace cobalt {
 namespace loader {
@@ -63,7 +63,7 @@
 }  // namespace loader
 }  // namespace cobalt
 
-#endif  // SB_VERSION(3) && SB_HAS(GRAPHICS)
+#endif  // SB_API_VERSION >= 3 && SB_HAS(GRAPHICS)
 
 #endif  // defined(STARBOARD)
 
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 0c9edff..b9bdc78 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/loader/loader_factory.cc b/src/cobalt/loader/loader_factory.cc
index b2794a5..b2e68eb 100644
--- a/src/cobalt/loader/loader_factory.cc
+++ b/src/cobalt/loader/loader_factory.cc
@@ -14,7 +14,6 @@
 
 #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"
 
@@ -117,11 +116,7 @@
   }
 
   // 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();
+  load_thread_.message_loop()->WaitForFence();
 }
 
 void LoaderFactory::Resume(render_tree::ResourceProvider* resource_provider) {
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 2134423..336d137 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -512,6 +512,7 @@
 
   base::CVal<base::cval::SizeInBytes, base::CValPublic> size_in_bytes_;
   base::CVal<base::cval::SizeInBytes, base::CValPublic> capacity_in_bytes_;
+  base::CVal<int> count_requested_resources_;
   base::CVal<int> count_loading_resources_;
   base::CVal<int> count_pending_callbacks_;
 
@@ -539,6 +540,9 @@
           "The capacity, in bytes, of the resource cache.  "
           "Exceeding this results in *unused* resources being "
           "purged."),
+      count_requested_resources_(
+          base::StringPrintf("Count.%s.RequestedResources", name_.c_str()), 0,
+          "The total number of resources that have been requested."),
       count_loading_resources_(
           base::StringPrintf("Count.%s.LoadingResources", name_.c_str()), 0,
           "The number of loading resources that are still outstanding."),
@@ -576,6 +580,7 @@
   }
 
   // If we reach this point, then the resource doesn't exist yet.
+  ++count_requested_resources_;
 
   // Add the resource to a loading set. If no current resources have pending
   // callbacks, then this resource will block callbacks until it is decoded.
diff --git a/src/cobalt/media/base/pipeline.h b/src/cobalt/media/base/pipeline.h
index 82863ce..1853ae5 100644
--- a/src/cobalt/media/base/pipeline.h
+++ b/src/cobalt/media/base/pipeline.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_MEDIA_BASE_PIPELINE_H_
 #define COBALT_MEDIA_BASE_PIPELINE_H_
 
+#include <vector>
+
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop_proxy.h"
@@ -38,6 +40,8 @@
 typedef void* PipelineWindow;
 #endif  // defined(COBALT_USE_SBPLAYER_PIPELINE)
 
+// #define COBALT_MEDIA_ENABLE_VIDEO_DUMPER 1
+
 namespace cobalt {
 namespace media {
 
@@ -46,9 +50,17 @@
 // Callback to notify that a DRM system is ready.
 typedef base::Callback<void(SbDrmSystem)> DrmSystemReadyCB;
 
-// Callback to set a DrmSystemReadyCB.
+// Callback to set an DrmSystemReadyCB.
 typedef base::Callback<void(const DrmSystemReadyCB&)> SetDrmSystemReadyCB;
 
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+// Callback to notify that EME init data is ready.
+typedef base::Callback<void(const std::vector<uint8_t>&)> EMEInitDataReadyCB;
+
+// Callback to set an EMEInitDataReadyCB.
+typedef base::Callback<void(const EMEInitDataReadyCB&)> SetEMEInitDataReadyCB;
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
 // Pipeline contains the common interface for media pipelines.  It provides
 // functions to perform asynchronous initialization, pausing, seeking and
 // playing.
@@ -104,11 +116,15 @@
   // It is an error to call this method after the pipeline has already started.
   virtual void Start(Demuxer* demuxer,
                      const SetDrmSystemReadyCB& set_drm_system_ready_cb,
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+                     const SetEMEInitDataReadyCB& set_eme_init_data_ready_cb,
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
                      const PipelineStatusCB& ended_cb,
                      const PipelineStatusCB& error_cb,
                      const PipelineStatusCB& seek_cb,
                      const BufferingStateCB& buffering_state_cb,
-                     const base::Closure& duration_change_cb) = 0;
+                     const base::Closure& duration_change_cb,
+                     const base::Closure& output_mode_change_cb) = 0;
 
   // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline
   // teardown has completed.
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index b773589..13db021 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -39,6 +39,7 @@
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
 #include "cobalt/media/base/starboard_player.h"
 #include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/base/video_dumper.h"
 #include "ui/gfx/size.h"
 
 namespace cobalt {
@@ -51,6 +52,8 @@
 
 namespace {
 
+const char kVideoDumpFileName[] = "video_content.dmp";
+
 // Used to post parameters to SbPlayerPipeline::StartTask() as the number of
 // parameters exceed what base::Bind() can support.
 struct StartTaskParameters {
@@ -61,6 +64,7 @@
   PipelineStatusCB seek_cb;
   Pipeline::BufferingStateCB buffering_state_cb;
   base::Closure duration_change_cb;
+  base::Closure output_mode_change_cb;
 };
 
 // SbPlayerPipeline is a PipelineBase implementation that uses the SbPlayer
@@ -79,10 +83,14 @@
   void Resume() OVERRIDE;
   void Start(Demuxer* demuxer,
              const SetDrmSystemReadyCB& set_drm_system_ready_cb,
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+             const SetEMEInitDataReadyCB& set_eme_init_data_ready_cb,
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
              const PipelineStatusCB& ended_cb, const PipelineStatusCB& error_cb,
              const PipelineStatusCB& seek_cb,
              const BufferingStateCB& buffering_state_cb,
-             const base::Closure& duration_change_cb) OVERRIDE;
+             const base::Closure& duration_change_cb,
+             const base::Closure& output_mode_change_cb) OVERRIDE;
 
   void Stop(const base::Closure& stop_cb) OVERRIDE;
   void Seek(TimeDelta time, const PipelineStatusCB& seek_cb);
@@ -136,6 +144,15 @@
   void SuspendTask(base::WaitableEvent* done_event);
   void ResumeTask(base::WaitableEvent* done_event);
 
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  void OnEMEInitDataReady(const std::vector<uint8_t>& eme_init_data) {
+    DCHECK(!eme_init_data.empty());
+    eme_init_datas_.push_back(eme_init_data);
+  }
+
+  std::vector<std::vector<uint8_t> > eme_init_datas_;
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
   // Message loop used to execute pipeline tasks.  It is thread-safe.
   scoped_refptr<base::MessageLoopProxy> message_loop_;
 
@@ -187,6 +204,7 @@
   PipelineStatusCB error_cb_;
   BufferingStateCB buffering_state_cb_;
   base::Closure duration_change_cb_;
+  base::Closure output_mode_change_cb_;
   base::optional<bool> decode_to_texture_output_mode_;
 
   // Demuxer reference used for setting the preload value.
@@ -210,6 +228,8 @@
   bool suspended_;
   bool stopped_;
 
+  VideoDumper video_dumper_;
+
   DISALLOW_COPY_AND_ASSIGN(SbPlayerPipeline);
 };
 
@@ -229,7 +249,8 @@
       video_read_in_progress_(false),
       set_bounds_helper_(new SbPlayerSetBoundsHelper),
       suspended_(false),
-      stopped_(false) {}
+      stopped_(false),
+      video_dumper_(kVideoDumpFileName) {}
 
 SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_); }
 
@@ -255,13 +276,15 @@
   waitable_event.Wait();
 }
 
-void SbPlayerPipeline::Start(Demuxer* demuxer,
-                             const SetDrmSystemReadyCB& set_drm_system_ready_cb,
-                             const PipelineStatusCB& ended_cb,
-                             const PipelineStatusCB& error_cb,
-                             const PipelineStatusCB& seek_cb,
-                             const BufferingStateCB& buffering_state_cb,
-                             const base::Closure& duration_change_cb) {
+void SbPlayerPipeline::Start(
+    Demuxer* demuxer, const SetDrmSystemReadyCB& set_drm_system_ready_cb,
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+    const SetEMEInitDataReadyCB& set_eme_init_data_ready_cb,
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+    const PipelineStatusCB& ended_cb, const PipelineStatusCB& error_cb,
+    const PipelineStatusCB& seek_cb, const BufferingStateCB& buffering_state_cb,
+    const base::Closure& duration_change_cb,
+    const base::Closure& output_mode_change_cb) {
   TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::Start");
 
   DCHECK(demuxer);
@@ -270,6 +293,7 @@
   DCHECK(!seek_cb.is_null());
   DCHECK(!buffering_state_cb.is_null());
   DCHECK(!duration_change_cb.is_null());
+  DCHECK(!output_mode_change_cb.is_null());
 
   StartTaskParameters parameters;
   parameters.demuxer = demuxer;
@@ -279,6 +303,12 @@
   parameters.seek_cb = seek_cb;
   parameters.buffering_state_cb = buffering_state_cb;
   parameters.duration_change_cb = duration_change_cb;
+  parameters.output_mode_change_cb = output_mode_change_cb;
+
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  set_eme_init_data_ready_cb.Run(base::Bind(
+      &SbPlayerPipeline::OnEMEInitDataReady, base::Unretained(this)));
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
 
   message_loop_->PostTask(
       FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
@@ -489,6 +519,7 @@
   }
   buffering_state_cb_ = parameters.buffering_state_cb;
   duration_change_cb_ = parameters.duration_change_cb;
+  output_mode_change_cb_ = parameters.output_mode_change_cb;
 
   const bool kEnableTextTracks = false;
   demuxer_->Initialize(this,
@@ -578,6 +609,10 @@
   const VideoDecoderConfig& video_config =
       video_stream_->video_decoder_config();
 
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  video_dumper_.DumpEmeInitData(eme_init_datas_);
+  video_dumper_.DumpConfigs(audio_config, video_config);
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
   {
     base::AutoLock auto_lock(lock_);
     player_.reset(new StarboardPlayer(
@@ -588,6 +623,14 @@
   }
 
   if (player_->IsValid()) {
+    base::Closure output_mode_change_cb;
+    {
+      base::AutoLock auto_lock(lock_);
+      DCHECK(!output_mode_change_cb_.is_null());
+      output_mode_change_cb = base::ResetAndReturn(&output_mode_change_cb_);
+    }
+    output_mode_change_cb.Run();
+
     return;
   }
 
@@ -724,6 +767,7 @@
     video_read_in_progress_ = false;
   }
 
+  video_dumper_.DumpAccessUnit(buffer);
   player_->WriteBuffer(type, buffer);
 }
 
diff --git a/src/cobalt/media/base/shell_video_frame_provider.h b/src/cobalt/media/base/shell_video_frame_provider.h
index eb98efb..a5bd969 100644
--- a/src/cobalt/media/base/shell_video_frame_provider.h
+++ b/src/cobalt/media/base/shell_video_frame_provider.h
@@ -28,7 +28,7 @@
 //       We should consider remove VideoFrame as it is no longer useful.
 class VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
  public:
-  int texture_id() const { return 0; }
+  uintptr_t texture_id() const { return 0; }
   base::TimeDelta GetTimestamp() const { return base::TimeDelta(); }
 };
 
diff --git a/src/cobalt/media/base/video_dmp_reader.h b/src/cobalt/media/base/video_dmp_reader.h
new file mode 100644
index 0000000..51c9f14
--- /dev/null
+++ b/src/cobalt/media/base/video_dmp_reader.h
@@ -0,0 +1,219 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_BASE_VIDEO_DMP_READER_H_
+#define COBALT_MEDIA_BASE_VIDEO_DMP_READER_H_
+
+// This file is deliberately not using any Cobalt/Starboard specific API so it
+// can be used in an independent application.
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+// File: <BOM> <Record>*
+//   BOM: 0x76543210
+//   Record: <4 bytes type> + <4 bytes size> + <|size| bytes binary data>
+//     type: 0: audio config, 1: video config, 2: eme init data,
+//           3: audio access unit, 4: video access unit.
+//     audio/video access unit;
+//       <8 bytes time stamp in microseconds>
+//       <4 bytes size of key_id> + |size| bytes of key id
+//       <4 bytes size of iv> + |size| bytes of iv
+//       <4 bytes count> (0 for non-encrypted AU/frame)
+//         (subsample: 4 bytes clear size, 4 bytes encrypted size) * |count|
+//       <4 bytes size>
+//         |size| bytes encoded binary data
+class VideoDmpReader {
+ public:
+  enum {
+    kRecordTypeAudioConfig,
+    kRecordTypeVideoConfig,
+    kRecordTypeEmeInitData,
+    kRecordTypeAudioAccessUnit,
+    kRecordTypeVideoAccessUnit,
+  };
+
+  enum AccessUnitType { kAccessUnitTypeAudio, kAccessUnitTypeVideo };
+
+  struct Subsample {
+    uint32_t clear_bytes;
+    uint32_t encrypted_bytes;
+  };
+
+  typedef std::vector<Subsample> Subsamples;
+  typedef std::vector<uint8_t> EmeInitData;
+
+  class AccessUnit {
+   public:
+    AccessUnit(AccessUnitType access_unit_type, int64_t timestamp,
+               const std::vector<uint8_t>& key_id,
+               const std::vector<uint8_t>& iv, const Subsamples& subsamples,
+               std::vector<uint8_t> data)
+        : access_unit_type_(access_unit_type),
+          timestamp_(timestamp),
+          key_id_(key_id),
+          iv_(iv),
+          subsamples_(subsamples),
+          data_(std::move(data)) {}
+    AccessUnitType access_unit_type() const { return access_unit_type_; }
+    int64_t timestamp() const { return timestamp_; }
+    // Returns empty vector when the AU is not encrypted.
+    const std::vector<uint8_t>& key_id() const { return key_id_; }
+    const std::vector<uint8_t>& iv() const { return iv_; }
+    const Subsamples& subsamples() const { return subsamples_; }
+    const std::vector<uint8_t> data() const { return data_; }
+
+   private:
+    AccessUnitType access_unit_type_;
+    int64_t timestamp_;
+    std::vector<uint8_t> key_id_;
+    std::vector<uint8_t> iv_;
+    Subsamples subsamples_;
+    std::vector<uint8_t> data_;
+  };
+
+  static const uint32_t kBOM = 0x76543210;
+
+  VideoDmpReader() : reverse_byte_order_(false) {}
+
+  // Abstract function implemented by the derived class to read data.  When
+  // the return value is less than |bytes_to_read|, the stream reaches the end.
+  virtual size_t Read(void* buffer, size_t bytes_to_read) = 0;
+  virtual void ReportFatalError() = 0;
+
+  const std::vector<EmeInitData> eme_init_datas() const {
+    return eme_init_datas_;
+  }
+  const std::vector<AccessUnit>& access_units() const { return access_units_; }
+
+  void Parse() {
+    uint32_t bom;
+    ReadChecked(&bom);
+    if (bom != kBOM) {
+      std::reverse(reinterpret_cast<uint8_t*>(&bom),
+                   reinterpret_cast<uint8_t*>(&bom + 1));
+      if (bom != kBOM) {
+        ReportFatalError();
+        return;
+      }
+      reverse_byte_order_ = true;
+    }
+    uint32_t type;
+    EmeInitData eme_init_data;
+    while (ReadUnchecked(&type)) {
+      uint32_t size;
+      switch (type) {
+        case kRecordTypeAudioConfig:
+          ReadChecked(&size);
+          if (size != 0) {
+            ReportFatalError();
+          }
+          break;
+        case kRecordTypeVideoConfig:
+          ReadChecked(&size);
+          if (size != 0) {
+            ReportFatalError();
+          }
+          break;
+        case kRecordTypeEmeInitData:
+          ReadChecked(&eme_init_data);
+          eme_init_datas_.push_back(eme_init_data);
+          break;
+        case kRecordTypeAudioAccessUnit:
+          ReadChecked(&size);
+          ReadAndAppendAccessUnitChecked(kAccessUnitTypeAudio);
+          break;
+        case kRecordTypeVideoAccessUnit:
+          ReadChecked(&size);
+          ReadAndAppendAccessUnitChecked(kAccessUnitTypeVideo);
+          break;
+      }
+    }
+  }
+
+ private:
+  void ReadAndAppendAccessUnitChecked(AccessUnitType access_unit_type) {
+    int64_t timestamp;
+    ReadChecked(&timestamp);
+
+    std::vector<uint8_t> key_id, iv;
+    ReadChecked(&key_id);
+    ReadChecked(&iv);
+
+    uint32_t subsample_count;
+    ReadChecked(&subsample_count);
+
+    Subsamples subsamples(subsample_count);
+    for (auto& subsample : subsamples) {
+      ReadChecked(&subsample.clear_bytes);
+      ReadChecked(&subsample.encrypted_bytes);
+    }
+
+    std::vector<uint8_t> data;
+    ReadChecked(&data);
+
+    access_units_.emplace_back(access_unit_type, timestamp, key_id, iv,
+                               subsamples, std::move(data));
+  }
+
+  template <typename T>
+  bool ReadUnchecked(T* value) {
+    if (!value) {
+      ReportFatalError();
+      return false;
+    }
+
+    int bytes_to_read = static_cast<int>(sizeof(*value));
+    int bytes_read = Read(value, bytes_to_read);
+
+    if (reverse_byte_order_) {
+      std::reverse(reinterpret_cast<uint8_t*>(value),
+                   reinterpret_cast<uint8_t*>(value + 1));
+    }
+
+    return bytes_to_read == bytes_read;
+  }
+  template <typename T>
+  void ReadChecked(T* value) {
+    if (!ReadUnchecked(value)) {
+      ReportFatalError();
+    }
+  }
+  void ReadChecked(std::vector<uint8_t>* value) {
+    if (!value) {
+      ReportFatalError();
+    }
+
+    uint32_t size;
+    ReadChecked(&size);
+
+    value->resize(size);
+
+    if (size == 0) {
+      return;
+    }
+
+    int bytes_read = Read(value->data(), size);
+    if (bytes_read != size) {
+      ReportFatalError();
+    }
+  }
+
+  bool reverse_byte_order_;
+  std::vector<EmeInitData> eme_init_datas_;
+  std::vector<AccessUnit> access_units_;
+};
+
+#endif  // COBALT_MEDIA_BASE_VIDEO_DMP_READER_H_
diff --git a/src/cobalt/media/base/video_dumper.h b/src/cobalt/media/base/video_dumper.h
new file mode 100644
index 0000000..c547989
--- /dev/null
+++ b/src/cobalt/media/base/video_dumper.h
@@ -0,0 +1,169 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_BASE_VIDEO_DUMPER_H_
+#define COBALT_MEDIA_BASE_VIDEO_DUMPER_H_
+
+#include <string>
+#include <vector>
+
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/decoder_buffer.h"
+#include "cobalt/media/base/demuxer_stream.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/base/video_dmp_reader.h"
+#include "starboard/file.h"
+
+namespace cobalt {
+namespace media {
+
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
+// This class saves video data according to the format specified inside
+// video_dmp_reader.h.
+class VideoDumper {
+ public:
+  typedef VideoDmpReader::EmeInitData EmeInitData;
+
+  explicit VideoDumper(const char* file_name) {
+    bool created = false;
+    file_ = SbFileOpen(file_name, kSbFileCreateAlways | kSbFileWrite, &created,
+                       NULL);
+    DCHECK(created);
+    DCHECK(SbFileIsValid(file_));
+
+    // Using a local variable to avoid addressing a constant which may cause
+    // link error.
+    uint32 bom = VideoDmpReader::kBOM;
+    Write<uint32>(bom);
+  }
+
+  ~VideoDumper() { SbFileClose(file_); }
+
+  void DumpEmeInitData(const std::vector<EmeInitData>& eme_init_datas) {
+    for (auto& eme_init_data : eme_init_datas) {
+      if (eme_init_data.empty()) {
+        continue;
+      }
+      Write<uint32>(VideoDmpReader::kRecordTypeEmeInitData);
+      // EME init data size
+      Write<uint32>(static_cast<uint32>(eme_init_data.size()));
+      Write(eme_init_data.data(), eme_init_data.size());
+    }
+  }
+
+  void DumpConfigs(const AudioDecoderConfig& audio_config,
+                   const VideoDecoderConfig& video_config) {
+    // Temporarily write empty audio/video configs
+    Write<uint32>(VideoDmpReader::kRecordTypeAudioConfig);
+    Write<uint32>(0);
+    Write<uint32>(VideoDmpReader::kRecordTypeVideoConfig);
+    Write<uint32>(0);
+  }
+
+  void DumpAccessUnit(const scoped_refptr<DecoderBuffer>& buffer) {
+    uint32 dump_type;
+    if (buffer->type() == DemuxerStream::AUDIO) {
+      dump_type = VideoDmpReader::kRecordTypeAudioAccessUnit;
+    } else if (buffer->type() == DemuxerStream::VIDEO) {
+      dump_type = VideoDmpReader::kRecordTypeVideoAccessUnit;
+    } else {
+      NOTREACHED() << buffer->type();
+    }
+    Write(dump_type);
+
+    const DecryptConfig* decrypt_config = buffer->decrypt_config();
+    if (decrypt_config && decrypt_config->key_id().size() == 16 &&
+        decrypt_config->iv().size() == 16) {
+      uint32 record_size =
+          sizeof(int64_t)                                       // timestamp
+          + sizeof(uint32_t)                                    // key_id size
+          + decrypt_config->key_id().size() + sizeof(uint32_t)  // iv size
+          + decrypt_config->iv().size() + sizeof(uint32_t)  // subsample count
+          +
+          sizeof(uint32_t) * 2 *
+              decrypt_config->subsamples().size()  // subsample count
+          + sizeof(uint32_t)                       // size of encoded data
+          + buffer->data_size();
+      Write(static_cast<uint32>(record_size));
+      Write(buffer->timestamp().InMicroseconds());
+      Write<uint32>(decrypt_config->key_id().size());  // key_id size
+      Write(&decrypt_config->key_id()[0], decrypt_config->key_id().size());
+      Write<uint32>(decrypt_config->iv().size());  // iv size
+      Write(&decrypt_config->iv()[0], decrypt_config->iv().size());
+      // subsample count
+      Write<uint32>(decrypt_config->subsamples().size());
+      for (size_t i = 0; i < decrypt_config->subsamples().size(); ++i) {
+        Write<uint32>(decrypt_config->subsamples()[i].clear_bytes);
+        Write<uint32>(decrypt_config->subsamples()[i].cypher_bytes);
+      }
+      Write<uint32>(static_cast<uint32>(buffer->data_size()));
+      Write(buffer->data(), buffer->data_size());
+    } else {
+      size_t record_size = sizeof(int64_t)     // timestamp
+                           + sizeof(uint32_t)  // key_id size
+                           + sizeof(uint32_t)  // iv size
+                           + sizeof(uint32_t)  // subsample count
+                           + sizeof(uint32_t)  // size of encoded data
+                           + buffer->data_size();
+      Write(static_cast<uint32>(record_size));
+      Write(buffer->timestamp().InMicroseconds());
+      Write<uint32>(0);  // key_id size
+      Write<uint32>(0);  // iv size
+      Write<uint32>(0);  // subsample count
+      Write<uint32>(static_cast<uint32>(buffer->data_size()));
+      Write(buffer->data(), buffer->data_size());
+    }
+  }
+
+ private:
+  template <typename T>
+  void Write(const T& value) {
+    int bytes_to_write = static_cast<int>(sizeof(value));
+    int bytes_written = SbFileWrite(
+        file_, reinterpret_cast<const char*>(&value), bytes_to_write);
+    DCHECK_EQ(bytes_to_write, bytes_written);
+  }
+
+  void Write(const char* buffer, size_t size) {
+    if (size == 0) {
+      return;
+    }
+    int bytes_written = SbFileWrite(file_, buffer, static_cast<int>(size));
+    DCHECK_EQ(static_cast<int>(size), bytes_written);
+  }
+
+  void Write(const uint8_t* buffer, size_t size) {
+    Write(reinterpret_cast<const char*>(buffer), size);
+  }
+
+  SbFile file_;
+};
+
+#else  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
+class VideoDumper {
+ public:
+  explicit VideoDumper(const char*) {}
+  void StartDump(const std::vector<std::vector<uint8_t> >&) {}
+  void DumpConfigs(const AudioDecoderConfig&, const VideoDecoderConfig&) {}
+  void DumpAccessUnit(const scoped_refptr<DecoderBuffer>&) {}
+};
+
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_BASE_VIDEO_DUMPER_H_
diff --git a/src/cobalt/media/decoder_buffer_allocator.cc b/src/cobalt/media/decoder_buffer_allocator.cc
index bd53da4..7097815 100644
--- a/src/cobalt/media/decoder_buffer_allocator.cc
+++ b/src/cobalt/media/decoder_buffer_allocator.cc
@@ -22,10 +22,6 @@
 namespace cobalt {
 namespace media {
 
-namespace {
-const bool kPreAllocateAllMemory = true;
-}  // namespace
-
 DecoderBufferAllocator::DecoderBufferAllocator() : memory_block_(NULL) {
   TRACK_MEMORY_SCOPE("Media");
   if (COBALT_MEDIA_BUFFER_INITIAL_CAPACITY > 0) {
@@ -36,9 +32,8 @@
   if (COBALT_MEDIA_BUFFER_INITIAL_CAPACITY > 0 ||
       COBALT_MEDIA_BUFFER_ALLOCATION_UNIT > 0) {
     // TODO: Support COBALT_MEDIA_BUFFER_ALLOCATION_UNIT > 0.
-    memory_pool_.set(starboard::make_scoped_ptr(
-        new nb::MemoryPool(memory_block_, COBALT_MEDIA_BUFFER_INITIAL_CAPACITY,
-                           kPreAllocateAllMemory)));
+    memory_pool_.set(starboard::make_scoped_ptr(new nb::FirstFitMemoryPool(
+        memory_block_, COBALT_MEDIA_BUFFER_INITIAL_CAPACITY)));
   }
 }
 
diff --git a/src/cobalt/media/decoder_buffer_allocator.h b/src/cobalt/media/decoder_buffer_allocator.h
index b83426b..8fa87f2 100644
--- a/src/cobalt/media/decoder_buffer_allocator.h
+++ b/src/cobalt/media/decoder_buffer_allocator.h
@@ -33,7 +33,7 @@
 
  private:
   void* memory_block_;
-  starboard::LockedPtr<nb::MemoryPool> memory_pool_;
+  starboard::LockedPtr<nb::FirstFitMemoryPool> memory_pool_;
 };
 
 }  // namespace media
diff --git a/src/cobalt/media/filters/source_buffer_range.cc b/src/cobalt/media/filters/source_buffer_range.cc
index aab6b44..c708c57 100644
--- a/src/cobalt/media/filters/source_buffer_range.cc
+++ b/src/cobalt/media/filters/source_buffer_range.cc
@@ -62,10 +62,13 @@
     buffers_.push_back(*itr);
     size_in_bytes_ += (*itr)->data_size();
 
+    DCHECK_LE(buffers_.size(), kint32max);
     if ((*itr)->is_key_frame()) {
+      int offset =
+          static_cast<int>(buffers_.size()) - 1 + keyframe_map_index_base_;
+      DCHECK_GE(offset, 0);
       keyframe_map_.insert(
-          std::make_pair((*itr)->GetDecodeTimestamp(),
-                         buffers_.size() - 1 + keyframe_map_index_base_));
+          std::make_pair((*itr)->GetDecodeTimestamp(), offset));
     }
   }
 }
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 383f25f..c287bdb 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/media/media_buffer_allocator.h b/src/cobalt/media/media_buffer_allocator.h
deleted file mode 100644
index b14616e..0000000
--- a/src/cobalt/media/media_buffer_allocator.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 COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
-#define COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
-
-#include "nb/memory_pool.h"
-#include "starboard/common/scoped_ptr.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_(starboard::make_scoped_ptr(new nb::MemoryPool(
-            pool_, main_pool_size_, true /* verify_full_capacity */))) {
-    if (small_allocation_pool_size_ > 0u) {
-      DCHECK_GT(small_allocation_threshold_, 0u);
-      small_allocation_pool_.set(starboard::make_scoped_ptr(new nb::MemoryPool(
-          pool_ + main_pool_size_, small_allocation_pool_size_,
-          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_->is_valid()) {
-      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_->is_valid()) {
-      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_;
-
-  starboard::LockedPtr<nb::MemoryPool> main_pool_;
-  starboard::LockedPtr<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/player/web_media_player.h b/src/cobalt/media/player/web_media_player.h
index 94e372d..b3b5cb6 100644
--- a/src/cobalt/media/player/web_media_player.h
+++ b/src/cobalt/media/player/web_media_player.h
@@ -196,6 +196,7 @@
   virtual void ReadyStateChanged() = 0;
   virtual void TimeChanged() = 0;
   virtual void DurationChanged() = 0;
+  virtual void OutputModeChanged() = 0;
   virtual void PlaybackStateChanged() = 0;
   // TODO: Revisit the necessity of the following function.
   virtual void SetOpaque(bool /* opaque */) {}
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index 623c892..bd0d14b 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -564,6 +564,14 @@
   }
 }
 
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+void WebMediaPlayerImpl::SetEMEInitDataReadyCB(
+    const EMEInitDataReadyCB& eme_init_data_ready_cb) {
+  DCHECK(!eme_init_data_ready_cb.is_null());
+  eme_init_data_ready_cb_ = eme_init_data_ready_cb;
+}
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
 void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
   DCHECK_EQ(main_loop_, MessageLoop::current());
   state_.starting = false;
@@ -691,11 +699,15 @@
   pipeline_->SetDecodeToTextureOutputMode(client_->PreferDecodeToTexture());
   pipeline_->Start(
       demuxer, BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::SetDrmSystemReadyCB),
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::SetEMEInitDataReadyCB),
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded),
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek),
       BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingState),
-      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged));
+      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged),
+      BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnOutputModeChanged));
 }
 
 void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
@@ -777,6 +789,11 @@
 
   GetClient()->EncryptedMediaInitDataEncountered(init_data_type, &init_data[0],
                                                  init_data.size());
+
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  DCHECK(!eme_init_data_ready_cb_.is_null());
+  eme_init_data_ready_cb_.Run(init_data);
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
 }
 
 WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
@@ -791,5 +808,9 @@
   GetClient()->DurationChanged();
 }
 
+void WebMediaPlayerImpl::OnOutputModeChanged() {
+  GetClient()->OutputModeChanged();
+}
+
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/player/web_media_player_impl.h b/src/cobalt/media/player/web_media_player_impl.h
index 5f3477b..c3804ac 100644
--- a/src/cobalt/media/player/web_media_player_impl.h
+++ b/src/cobalt/media/player/web_media_player_impl.h
@@ -184,6 +184,9 @@
 
   void SetDrmSystem(DrmSystem* drm_system) OVERRIDE;
   void SetDrmSystemReadyCB(const DrmSystemReadyCB& drm_system_ready_cb);
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  void SetEMEInitDataReadyCB(const EMEInitDataReadyCB& eme_init_data_ready_cb);
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
 
   void OnPipelineSeek(PipelineStatus status);
   void OnPipelineEnded(PipelineStatus status);
@@ -218,6 +221,7 @@
  private:
   // Callbacks that forward duration change from |pipeline_| to |client_|.
   void OnDurationChanged();
+  void OnOutputModeChanged();
 
   base::Thread pipeline_thread_;
 
@@ -309,6 +313,11 @@
       media_time_and_seeking_state_cb_;
 
   DrmSystemReadyCB drm_system_ready_cb_;
+
+#if COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+  EMEInitDataReadyCB eme_init_data_ready_cb_;
+#endif  // COBALT_MEDIA_ENABLE_VIDEO_DUMPER
+
   DrmSystem* drm_system_;
 
   DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl);
diff --git a/src/cobalt/media/sandbox/web_media_player_helper.cc b/src/cobalt/media/sandbox/web_media_player_helper.cc
index adb3afe..58618a2 100644
--- a/src/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/src/cobalt/media/sandbox/web_media_player_helper.cc
@@ -37,6 +37,7 @@
   void ReadyStateChanged() OVERRIDE {}
   void TimeChanged() OVERRIDE {}
   void DurationChanged() OVERRIDE {}
+  void OutputModeChanged() OVERRIDE {}
   void PlaybackStateChanged() OVERRIDE {}
   void SawUnsupportedTracks() OVERRIDE {}
   float Volume() const OVERRIDE { return 1.f; }
diff --git a/src/cobalt/media/shell_media_platform_starboard.cc b/src/cobalt/media/shell_media_platform_starboard.cc
deleted file mode 100644
index 92c9194..0000000
--- a/src/cobalt/media/shell_media_platform_starboard.cc
+++ /dev/null
@@ -1,120 +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/media/shell_media_platform_starboard.h"
-
-#include <limits>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
-#include "media/audio/shell_audio_streamer.h"
-#include "media/base/shell_buffer_factory.h"
-#include "media/base/shell_cached_decoder_buffer.h"
-#include "starboard/common/scoped_ptr.h"
-#include "starboard/configuration.h"
-#include "starboard/media.h"
-
-namespace media {
-
-namespace {
-
-const size_t kMediaBufferAlignment = SB_MEDIA_BUFFER_ALIGNMENT;
-const size_t kVideoFrameAlignment = SB_MEDIA_VIDEO_FRAME_ALIGNMENT;
-
-// This may be zero, which should disable GPU memory buffer allocations.
-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(
-    cobalt::render_tree::ResourceProvider* resource_provider)
-    : resource_provider_(resource_provider),
-      video_data_allocator_(resource_provider_, 0,
-                            std::numeric_limits<size_t>::max(),
-                            kVideoFrameAlignment),
-      video_frame_provider_(new ShellVideoFrameProvider) {
-  DCHECK(!Instance());
-
-  if (kGPUMemoryBufferBudget > 0) {
-    gpu_memory_buffer_space_ = resource_provider->AllocateRawImageMemory(
-        kGPUMemoryBufferBudget, kMediaBufferAlignment);
-    DCHECK(gpu_memory_buffer_space_);
-    DCHECK(gpu_memory_buffer_space_->GetMemory());
-    DCHECK_GE(gpu_memory_buffer_space_->GetSizeInBytes(),
-              kGPUMemoryBufferBudget);
-    gpu_memory_pool_.set(starboard::make_scoped_ptr(new nb::MemoryPool(
-        gpu_memory_buffer_space_->GetMemory(),
-        gpu_memory_buffer_space_->GetSizeInBytes(),
-        true /* verify_full_capacity */, kSmallAllocationThreshold)));
-  }
-
-  DCHECK_LE(0, kMainMemoryBufferBudget > 0);
-  main_memory_buffer_space_.reset(static_cast<uint8*>(
-      base::AlignedAlloc(kMainMemoryBufferBudget, kMediaBufferAlignment)));
-  DCHECK(main_memory_buffer_space_);
-  main_memory_pool_.set(starboard::make_scoped_ptr(new nb::MemoryPool(
-      main_memory_buffer_space_.get(), kMainMemoryBufferBudget,
-      true, /* verify_full_capacity */
-      kSmallAllocationThreshold)));
-
-  ShellBufferFactory::Initialize();
-  ShellAudioStreamer::Initialize();
-
-  SetInstance(this);
-}
-
-ShellMediaPlatformStarboard::~ShellMediaPlatformStarboard() {
-  DCHECK_EQ(Instance(), this);
-  SetInstance(NULL);
-
-  ShellAudioStreamer::Terminate();
-  ShellBufferFactory::Terminate();
-}
-
-void* ShellMediaPlatformStarboard::AllocateBuffer(size_t size) {
-  if (kGPUMemoryBufferBudget) {
-    return gpu_memory_pool_->Allocate(size, kMediaBufferAlignment);
-  }
-
-  return main_memory_pool_->Allocate(size, kMediaBufferAlignment);
-}
-
-void ShellMediaPlatformStarboard::FreeBuffer(void* ptr) {
-  if (kGPUMemoryBufferBudget) {
-    return gpu_memory_pool_->Free(ptr);
-  }
-
-  return main_memory_pool_->Free(ptr);
-}
-
-scoped_refptr<DecoderBuffer>
-ShellMediaPlatformStarboard::ProcessBeforeLeavingDemuxer(
-    const scoped_refptr<DecoderBuffer>& buffer) {
-  // TODO: Completely remove GPU buffer for the new DecoderBuffer
-  //       implementation.
-  return buffer;
-}
-
-bool ShellMediaPlatformStarboard::IsOutputProtected() {
-  if (SbMediaIsOutputProtected()) {
-    return true;
-  }
-  return SbMediaSetOutputProtection(true);
-}
-
-}  // namespace media
diff --git a/src/cobalt/media_session/media_session.gyp b/src/cobalt/media_session/media_session.gyp
index 4de3b9d..f9083ee 100644
--- a/src/cobalt/media_session/media_session.gyp
+++ b/src/cobalt/media_session/media_session.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/network/network.gyp b/src/cobalt/network/network.gyp
index d6296e3..be0258f 100644
--- a/src/cobalt/network/network.gyp
+++ b/src/cobalt/network/network.gyp
@@ -16,7 +16,7 @@
   'includes': [ '../build/contents_dir.gypi' ],
 
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc b/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
index 7a2ddd2..6113bac 100644
--- a/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
+++ b/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
@@ -81,13 +81,13 @@
       youtube_tv_info_->network_operator = value;
     }
 
-#if SB_API_VERSION >= SB_USER_AGENT_AUX_SYSTEM_PROPERTY_API_VERSION
+#if SB_API_VERSION >= 5
     result = SbSystemGetProperty(kSbSystemPropertyUserAgentAuxField, value,
                                  kSystemPropertyMaxLength);
     if (result) {
       aux_field_ = value;
     }
-#endif  // SB_API_VERSION >= SB_USER_AGENT_AUX_SYSTEM_PROPERTY_API_VERSION
+#endif  // SB_API_VERSION >= 5
 
     // Device Type
     switch (device_type) {
diff --git a/src/cobalt/network_bridge/network_bridge.gyp b/src/cobalt/network_bridge/network_bridge.gyp
index 0daab78..fbdc257 100644
--- a/src/cobalt/network_bridge/network_bridge.gyp
+++ b/src/cobalt/network_bridge/network_bridge.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/page_visibility/document.idl b/src/cobalt/page_visibility/document.idl
new file mode 100644
index 0000000..b6d39a0
--- /dev/null
+++ b/src/cobalt/page_visibility/document.idl
@@ -0,0 +1,22 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Part of Page Visibility Level 2
+// https://www.w3.org/TR/page-visibility-2/#extensions-to-the-document-interface
+
+partial interface Document {
+  readonly attribute boolean hidden;
+  readonly attribute VisibilityState visibilityState;
+  attribute EventHandler onvisibilitychange;
+};
diff --git a/src/cobalt/page_visibility/page_visibility.gyp b/src/cobalt/page_visibility/page_visibility.gyp
new file mode 100644
index 0000000..e943727
--- /dev/null
+++ b/src/cobalt/page_visibility/page_visibility.gyp
@@ -0,0 +1,68 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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': {
+    'cobalt_code': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'page_visibility',
+      'type': 'static_library',
+      'sources': [
+        'page_visibility_state.cc',
+        'page_visibility_state.h',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+      ],
+      # This target doesn't generate any headers, but it exposes generated
+      # header files through this module's public header files. So mark this
+      # target as a hard dependency to ensure that any dependent targets wait
+      # until this target (and its hard dependencies) are built.
+      'hard_dependency': 1,
+      'export_dependent_settings': [
+        # Additionally, ensure that the include directories for generated
+        # headers are put on the include directories for targets that depend
+        # on this one.
+        '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+      ]
+    },
+    {
+      'target_name': 'page_visibility_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'page_visibility_state_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        'page_visibility',
+      ],
+    },
+    {
+      'target_name': 'page_visibility_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'page_visibility_test',
+      ],
+      'variables': {
+        'executable_name': 'page_visibility_test',
+      },
+      'includes': [ '../../starboard/build/deploy.gypi' ],
+    },
+  ],
+}
diff --git a/src/cobalt/page_visibility/page_visibility_state.cc b/src/cobalt/page_visibility/page_visibility_state.cc
new file mode 100644
index 0000000..d8ac3ee
--- /dev/null
+++ b/src/cobalt/page_visibility/page_visibility_state.cc
@@ -0,0 +1,148 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/page_visibility/page_visibility_state.h"
+
+namespace cobalt {
+namespace page_visibility {
+
+namespace {
+// Converts an ApplicationState to a VisibilityState.
+VisibilityState ToVisibilityState(base::ApplicationState state) {
+  switch (state) {
+    case base::kApplicationStatePreloading:
+      return kVisibilityStatePrerender;
+    case base::kApplicationStateStarted:
+    case base::kApplicationStatePaused:
+      return kVisibilityStateVisible;
+    case base::kApplicationStateSuspended:
+    case base::kApplicationStateStopped:
+      return kVisibilityStateHidden;
+    default:
+      NOTREACHED() << "Invalid Application State: " << state;
+      return kVisibilityStateHidden;
+  }
+}
+
+bool HasFocus(base::ApplicationState state) {
+  switch (state) {
+    case base::kApplicationStateStarted:
+      return true;
+    case base::kApplicationStatePreloading:
+    case base::kApplicationStatePaused:
+    case base::kApplicationStateSuspended:
+    case base::kApplicationStateStopped:
+      return false;
+    default:
+      NOTREACHED() << "Invalid Application State: " << state;
+      return false;
+  }
+}
+}  // namespace
+
+PageVisibilityState::PageVisibilityState()
+    : application_state_(base::kApplicationStateStarted) {}
+
+PageVisibilityState::PageVisibilityState(
+    base::ApplicationState initial_application_state)
+    : application_state_(initial_application_state) {
+  DCHECK((application_state_ == base::kApplicationStateStarted) ||
+         (application_state_ == base::kApplicationStatePreloading));
+
+  // TODO: Support Preloading
+  DCHECK_NE(base::kApplicationStatePreloading, application_state_);
+}
+
+bool PageVisibilityState::HasWindowFocus() const {
+  return HasFocus(application_state());
+}
+
+VisibilityState PageVisibilityState::GetVisibilityState() const {
+  return ToVisibilityState(application_state());
+}
+
+void PageVisibilityState::SetApplicationState(base::ApplicationState state) {
+  if (application_state_ == state) {
+    DLOG(WARNING) << __FUNCTION__ << ": Attempt to re-enter "
+                  << application_state_;
+    return;
+  }
+
+  // Audit that the transitions are correct.
+  if (DLOG_IS_ON(FATAL)) {
+    switch (application_state_) {
+      case base::kApplicationStatePaused:
+        DCHECK(state == base::kApplicationStateSuspended ||
+               state == base::kApplicationStateStarted);
+        break;
+      case base::kApplicationStatePreloading:
+        DCHECK(state == base::kApplicationStateSuspended ||
+               state == base::kApplicationStateStarted);
+      case base::kApplicationStateStarted:
+        DCHECK(state == base::kApplicationStatePaused);
+        break;
+      case base::kApplicationStateStopped:
+        DCHECK(state == base::kApplicationStatePreloading ||
+               state == base::kApplicationStateStarted);
+        break;
+      case base::kApplicationStateSuspended:
+        DCHECK(state == base::kApplicationStatePaused ||
+               state == base::kApplicationStateStopped);
+        break;
+      default:
+        NOTREACHED() << application_state_;
+        break;
+    }
+  }
+
+  bool old_has_focus = HasFocus(application_state_);
+  VisibilityState old_visibility_state = ToVisibilityState(application_state_);
+  application_state_ = state;
+  bool has_focus = HasFocus(application_state_);
+  VisibilityState visibility_state = ToVisibilityState(application_state_);
+  bool focus_changed = has_focus != old_has_focus;
+  bool visibility_state_changed = visibility_state != old_visibility_state;
+
+  if (focus_changed && has_focus) {
+    // If going to a focused state, dispatch the visibility state first.
+    if (visibility_state_changed) {
+      DispatchVisibilityStateChanged(visibility_state);
+    }
+
+    DispatchWindowFocusChanged(has_focus);
+    return;
+  }
+
+  // Otherwise, we should dispatch the focus state first.
+  if (focus_changed) {
+    DispatchWindowFocusChanged(has_focus);
+  }
+
+  if (visibility_state_changed) {
+    DispatchVisibilityStateChanged(visibility_state);
+  }
+}
+
+void PageVisibilityState::DispatchWindowFocusChanged(bool has_focus) {
+  FOR_EACH_OBSERVER(Observer, observer_list_, OnWindowFocusChanged(has_focus));
+}
+
+void PageVisibilityState::DispatchVisibilityStateChanged(
+    VisibilityState visibility_state) {
+  FOR_EACH_OBSERVER(Observer, observer_list_,
+                    OnVisibilityStateChanged(visibility_state));
+}
+
+}  // namespace page_visibility
+}  // namespace cobalt
diff --git a/src/cobalt/page_visibility/page_visibility_state.h b/src/cobalt/page_visibility/page_visibility_state.h
new file mode 100644
index 0000000..9ab3a87
--- /dev/null
+++ b/src/cobalt/page_visibility/page_visibility_state.h
@@ -0,0 +1,94 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PAGE_VISIBILITY_PAGE_VISIBILITY_STATE_H_
+#define COBALT_PAGE_VISIBILITY_PAGE_VISIBILITY_STATE_H_
+
+#include "base/observer_list.h"
+#include "cobalt/base/application_state.h"
+#include "cobalt/page_visibility/visibility_state.h"
+
+namespace cobalt {
+namespace page_visibility {
+
+// The visibility state of the Window and Document as controlled by the current
+// application state.
+class PageVisibilityState {
+ public:
+  // Pure virtual interface for classes that want to observe page visibility
+  // state changes.
+  class Observer {
+   public:
+    // Called when the window focus state changes.
+    virtual void OnWindowFocusChanged(bool has_focus) = 0;
+    // Called when the VisibilityState changes.
+    virtual void OnVisibilityStateChanged(VisibilityState visibility_state) = 0;
+
+   protected:
+    virtual ~Observer() {}
+  };
+
+  PageVisibilityState();
+  explicit PageVisibilityState(
+      base::ApplicationState initial_application_state);
+
+  base::ApplicationState application_state() const {
+    return application_state_;
+  }
+
+  // Whether the current window has focus based on the current application
+  // state.
+  bool HasWindowFocus() const;
+
+  // Whether the current window has focus based on the current application
+  // state.
+  VisibilityState GetVisibilityState() const;
+
+  // Sets the current application state, and dispatches appropriate observation
+  // events.
+  void SetApplicationState(base::ApplicationState state);
+
+  // Adds a PageVisibiltyState::Observer to this PageVisibilityState.
+  void AddObserver(Observer* observer) { observer_list_.AddObserver(observer); }
+
+  // Removes a PageVisibiltyState::Observer from this PageVisibilityState, if it
+  // is registered.
+  void RemoveObserver(Observer* observer) {
+    observer_list_.RemoveObserver(observer);
+  }
+
+  // Returns whether a PageVisibiltyState::Observer is registered on this
+  // PageVisibilityState.
+  bool HasObserver(Observer* observer) const {
+    return observer_list_.HasObserver(observer);
+  }
+
+  // Clears all registered PageVisibiltyState::Observers, if any.
+  void ClearObservers() { observer_list_.Clear(); }
+
+ private:
+  void DispatchWindowFocusChanged(bool has_focus);
+  void DispatchVisibilityStateChanged(VisibilityState visibility_state);
+
+  // The current application state.
+  base::ApplicationState application_state_;
+
+  // The list of registered PageVisibiltyState::Observers;
+  ObserverList<Observer> observer_list_;
+};
+
+}  // namespace page_visibility
+}  // namespace cobalt
+
+#endif  // COBALT_PAGE_VISIBILITY_PAGE_VISIBILITY_STATE_H_
diff --git a/src/cobalt/page_visibility/page_visibility_state_test.cc b/src/cobalt/page_visibility/page_visibility_state_test.cc
new file mode 100644
index 0000000..3a021e5
--- /dev/null
+++ b/src/cobalt/page_visibility/page_visibility_state_test.cc
@@ -0,0 +1,133 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/page_visibility/page_visibility_state.h"
+
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cobalt/base/application_state.h"
+#include "cobalt/page_visibility/visibility_state.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace page_visibility {
+namespace {
+
+using ::testing::_;
+
+class MockPageVisibilityStateObserver : public PageVisibilityState::Observer {
+ public:
+  static scoped_ptr<MockPageVisibilityStateObserver> Create() {
+    return make_scoped_ptr<MockPageVisibilityStateObserver>(
+        new ::testing::StrictMock<MockPageVisibilityStateObserver>());
+  }
+
+  MOCK_METHOD1(OnWindowFocusChanged, void(bool has_focus));
+  MOCK_METHOD1(OnVisibilityStateChanged,
+               void(VisibilityState visibility_state));
+
+ protected:
+  MockPageVisibilityStateObserver() {}
+};
+
+TEST(PageVisibilityStateTest, DefaultConstructor) {
+  PageVisibilityState state;
+  EXPECT_TRUE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+}
+
+TEST(PageVisibilityStateTest, InitialStateConstructorStarted) {
+  PageVisibilityState state(base::kApplicationStateStarted);
+  EXPECT_TRUE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+}
+
+TEST(PageVisibilityStateTest, TransitionsAndObservations) {
+  PageVisibilityState state(base::kApplicationStateStarted);
+  scoped_ptr<MockPageVisibilityStateObserver> observer =
+      MockPageVisibilityStateObserver::Create();
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(_)).Times(0);
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  EXPECT_TRUE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  state.AddObserver(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(false));
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStatePaused);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(true));
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStateStarted);
+  EXPECT_TRUE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(false));
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStatePaused);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(kVisibilityStateHidden));
+  EXPECT_CALL(*observer, OnWindowFocusChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStateSuspended);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateHidden, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(kVisibilityStateVisible));
+  EXPECT_CALL(*observer, OnWindowFocusChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStatePaused);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(true));
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStateStarted);
+  EXPECT_TRUE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  state.RemoveObserver(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(_)).Times(0);
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStatePaused);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateVisible, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+
+  EXPECT_CALL(*observer, OnWindowFocusChanged(_)).Times(0);
+  EXPECT_CALL(*observer, OnVisibilityStateChanged(_)).Times(0);
+  state.SetApplicationState(base::kApplicationStateSuspended);
+  EXPECT_FALSE(state.HasWindowFocus());
+  EXPECT_EQ(kVisibilityStateHidden, state.GetVisibilityState());
+  ::testing::Mock::VerifyAndClearExpectations(observer.get());
+}
+
+}  // namespace
+}  // namespace page_visibility
+}  // namespace cobalt
diff --git a/src/cobalt/page_visibility/visibility_state.idl b/src/cobalt/page_visibility/visibility_state.idl
new file mode 100644
index 0000000..829cc5d
--- /dev/null
+++ b/src/cobalt/page_visibility/visibility_state.idl
@@ -0,0 +1,22 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Part of Page Visibility Level 2
+// https://www.w3.org/TR/page-visibility-2/#visibility-states-and-the-visibilitystate-enum
+
+enum VisibilityState {
+  "hidden",
+  "visible",
+  "prerender",
+};
diff --git a/src/cobalt/render_tree/brush.cc b/src/cobalt/render_tree/brush.cc
index 8ba6694..2edf8ae 100644
--- a/src/cobalt/render_tree/brush.cc
+++ b/src/cobalt/render_tree/brush.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/render_tree/brush.h"
 
+#include <algorithm>
+
 #include "cobalt/render_tree/brush_visitor.h"
 
 namespace cobalt {
@@ -155,5 +157,96 @@
   return true;
 }
 
+namespace {
+// Returns the corner points that should be used to calculate the source and
+// destination gradient points.  This is determined by which quadrant the
+// gradient direction vector lies within.
+std::pair<math::PointF, math::PointF>
+GetSourceAndDestinationPointsFromGradientVector(
+    const math::Vector2dF& gradient_vector, const math::SizeF& frame_size) {
+  std::pair<math::PointF, math::PointF> ret;
+  if (gradient_vector.x() >= 0 && gradient_vector.y() >= 0) {
+    ret.first = math::PointF(0, 0);
+    ret.second = math::PointF(frame_size.width(), frame_size.height());
+  } else if (gradient_vector.x() < 0 && gradient_vector.y() >= 0) {
+    ret.first = math::PointF(frame_size.width(), 0);
+    ret.second = math::PointF(0, frame_size.height());
+  } else if (gradient_vector.x() < 0 && gradient_vector.y() < 0) {
+    ret.first = math::PointF(frame_size.width(), frame_size.height());
+    ret.second = math::PointF(0, 0);
+  } else if (gradient_vector.x() >= 0 && gradient_vector.y() < 0) {
+    ret.first = math::PointF(0, frame_size.height());
+    ret.second = math::PointF(frame_size.width(), 0);
+  } else {
+    NOTREACHED();
+  }
+
+  return ret;
+}
+
+math::PointF IntersectLines(math::PointF point_a, math::Vector2dF dir_a,
+                            math::PointF point_b, math::Vector2dF dir_b) {
+  DCHECK(dir_a.y() != 0 || dir_b.y() != 0);
+
+  if (dir_a.x() == 0) {
+    // Swap a and b so that we are guaranteed not to divide by 0.
+    std::swap(point_a, point_b);
+    std::swap(dir_a, dir_b);
+  }
+
+  float slope_a = dir_a.y() / dir_a.x();
+
+  // Calculate how far from |point_b| we should travel in units of |dir_b|
+  // in order to reach the point of intersection.
+  float distance_from_point_b =
+      (point_a.y() - point_b.y() + slope_a * (point_b.x() - point_a.x())) /
+      (dir_b.y() - slope_a * dir_b.x());
+
+  dir_b.Scale(distance_from_point_b);
+  return point_b + dir_b;
+}
+}  // namespace
+
+std::pair<math::PointF, math::PointF> LinearGradientPointsFromAngle(
+    float ccw_radians_from_right, const math::SizeF& frame_size) {
+  // The method of defining the source and destination points for the linear
+  // gradient are defined here:
+  //   https://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradients
+  //
+  // "Starting from the center of the gradient box, extend a line at the
+  //  specified angle in both directions. The ending point is the point on the
+  //  gradient line where a line drawn perpendicular to the gradient line would
+  //  intersect the corner of the gradient box in the specified direction. The
+  //  starting point is determined identically, but in the opposite direction."
+
+  // First determine the line parallel to the gradient angle.
+  math::PointF gradient_line_point(frame_size.width() / 2.0f,
+                                   frame_size.height() / 2.0f);
+
+  // Note that we flip the y value here since we move down in our screen space
+  // as y increases.
+  math::Vector2dF gradient_vector(
+      static_cast<float>(cos(ccw_radians_from_right)),
+      static_cast<float>(-sin(ccw_radians_from_right)));
+
+  // Determine the line direction that is perpendicular to the gradient line.
+  math::Vector2dF perpendicular_vector(-gradient_vector.y(),
+                                       gradient_vector.x());
+
+  // Determine the corner points that should be used to calculate the source
+  // and destination points, based on which quadrant the gradient direction
+  // vector lies within.
+  std::pair<math::PointF, math::PointF> corners =
+      GetSourceAndDestinationPointsFromGradientVector(gradient_vector,
+                                                      frame_size);
+
+  // Intersect the perpendicular line running through the source corner with
+  // the gradient line to get our source point.
+  return std::make_pair(IntersectLines(gradient_line_point, gradient_vector,
+                                       corners.first, perpendicular_vector),
+                        IntersectLines(gradient_line_point, gradient_vector,
+                                       corners.second, perpendicular_vector));
+}
+
 }  // namespace render_tree
 }  // namespace cobalt
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h
index fba8063..b5be829 100644
--- a/src/cobalt/render_tree/brush.h
+++ b/src/cobalt/render_tree/brush.h
@@ -16,12 +16,14 @@
 #define COBALT_RENDER_TREE_BRUSH_H_
 
 #include <cmath>
+#include <utility>
 #include <vector>
 
 #include "base/compiler_specific.h"
 #include "base/memory/scoped_ptr.h"
 #include "cobalt/base/type_id.h"
 #include "cobalt/math/point_f.h"
+#include "cobalt/math/size_f.h"
 #include "cobalt/render_tree/brush_visitor.h"
 #include "cobalt/render_tree/color_rgba.h"
 
@@ -84,23 +86,53 @@
 bool IsNear(const render_tree::ColorStopList& lhs,
             const render_tree::ColorStopList& rhs, float epsilon);
 
+// Calculate the source and destination points to use for a linear gradient
+// of the specified angle to cover the given frame.
+//
+// The method of defining the source and destination points for the linear
+// gradient are defined here:
+//   https://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradients
+//
+// "Starting from the center of the gradient box, extend a line at the
+//  specified angle in both directions. The ending point is the point on the
+//  gradient line where a line drawn perpendicular to the gradient line would
+//  intersect the corner of the gradient box in the specified direction. The
+//  starting point is determined identically, but in the opposite direction."
+std::pair<math::PointF, math::PointF> LinearGradientPointsFromAngle(
+    float ccw_radians_from_right, const math::SizeF& frame_size);
+
 // Linear gradient brushes can be used to fill a shape with a linear color
 // gradient with arbitrary many color stops.  It is specified by a source
 // and destination point, which define a line segment along which the color
 // stops apply, each having a position between 0 and 1 representing the
 // position of the stop along that line.  Interpolation occurs in premultiplied
 // alpha space.
+// NOTE: The source and destination points may lie inside or outside the shape
+// which uses the gradient brush. Always consider the shape as a mask over the
+// gradient whose first and last color stops extend infinitely in their
+// respective directions.
 class LinearGradientBrush : public Brush {
  public:
   // The ColorStopList passed into LienarGradientBrush must have at least two
   // stops and they must be sorted in order of increasing position.
   LinearGradientBrush(const math::PointF& source, const math::PointF& dest,
                       const ColorStopList& color_stops);
+  LinearGradientBrush(const std::pair<math::PointF, math::PointF>& source_dest,
+                      const ColorStopList& color_stops)
+      : LinearGradientBrush(source_dest.first, source_dest.second,
+                            color_stops)
+      {}
 
   // Creates a 2-stop linear gradient from source to dest.
   LinearGradientBrush(const math::PointF& source, const math::PointF& dest,
                       const ColorRGBA& source_color,
                       const ColorRGBA& dest_color);
+  LinearGradientBrush(const std::pair<math::PointF, math::PointF>& source_dest,
+                      const ColorRGBA& source_color,
+                      const ColorRGBA& dest_color)
+      : LinearGradientBrush(source_dest.first, source_dest.second,
+                            source_color, dest_color)
+      {}
 
   struct Data {
     Data();
@@ -130,10 +162,14 @@
   const ColorStopList& color_stops() const { return data_.color_stops_; }
 
   // Returns true if, and only if the brush is horizontal.
-  bool IsHorizontal() const { return (data_.source_.y() == data_.dest_.y()); }
+  bool IsHorizontal(float epsilon = 0.001f) const {
+    return std::abs(data_.source_.y() - data_.dest_.y()) < epsilon;
+  }
 
   // Returns true if, and only if the brush is vertical.
-  bool IsVertical() const { return (data_.source_.x() == data_.dest_.x()); }
+  bool IsVertical(float epsilon = 0.001f) const {
+    return std::abs(data_.source_.x() - data_.dest_.x()) < epsilon;
+  }
 
   const Data& data() const { return data_; }
 
diff --git a/src/cobalt/render_tree/composition_node.h b/src/cobalt/render_tree/composition_node.h
index 2614822..a6175a0 100644
--- a/src/cobalt/render_tree/composition_node.h
+++ b/src/cobalt/render_tree/composition_node.h
@@ -78,9 +78,6 @@
     // method.
     void AddChild(const scoped_refptr<Node>& node);
 
-    // Remove all existing children.
-    void ClearChildren() { children_.clear(); }
-
     // Returns the specified child as a pointer so that it can be modified.
     scoped_refptr<Node>* GetChild(int child_index) {
       DCHECK_GE(child_index, 0);
diff --git a/src/cobalt/render_tree/render_tree.gyp b/src/cobalt/render_tree/render_tree.gyp
index aeb94fa..f6d52fb 100644
--- a/src/cobalt/render_tree/render_tree.gyp
+++ b/src/cobalt/render_tree/render_tree.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
 
   'targets': [
diff --git a/src/cobalt/renderer/backend/blitter/display.cc b/src/cobalt/renderer/backend/blitter/display.cc
index 91d7182..7bd4d91 100644
--- a/src/cobalt/renderer/backend/blitter/display.cc
+++ b/src/cobalt/renderer/backend/blitter/display.cc
@@ -34,7 +34,7 @@
   DisplayRenderTargetBlitter(SbBlitterDevice device,
                              system_window::SystemWindow* system_window);
 
-  const math::Size& GetSize() OVERRIDE;
+  const math::Size& GetSize() const OVERRIDE;
 
   SbBlitterRenderTarget GetSbRenderTarget() const OVERRIDE;
 
@@ -64,7 +64,7 @@
   size_.SetSize(window_size.width, window_size.height);
 }
 
-const math::Size& DisplayRenderTargetBlitter::GetSize() { return size_; }
+const math::Size& DisplayRenderTargetBlitter::GetSize() const { return size_; }
 
 DisplayRenderTargetBlitter::~DisplayRenderTargetBlitter() {
   SbBlitterDestroySwapChain(swap_chain_);
diff --git a/src/cobalt/renderer/backend/blitter/render_target.h b/src/cobalt/renderer/backend/blitter/render_target.h
index ea1c079..5f40d6f 100644
--- a/src/cobalt/renderer/backend/blitter/render_target.h
+++ b/src/cobalt/renderer/backend/blitter/render_target.h
@@ -28,7 +28,7 @@
  public:
   virtual SbBlitterRenderTarget GetSbRenderTarget() const = 0;
 
-  intptr_t GetPlatformHandle() OVERRIDE {
+  intptr_t GetPlatformHandle() const OVERRIDE {
     return reinterpret_cast<intptr_t>(SbBlitterRenderTarget());
   }
 
diff --git a/src/cobalt/renderer/backend/blitter/surface_render_target.cc b/src/cobalt/renderer/backend/blitter/surface_render_target.cc
index 2bdc05c..6b71860 100644
--- a/src/cobalt/renderer/backend/blitter/surface_render_target.cc
+++ b/src/cobalt/renderer/backend/blitter/surface_render_target.cc
@@ -41,7 +41,7 @@
   }
 }
 
-const math::Size& SurfaceRenderTargetBlitter::GetSize() { return size_; }
+const math::Size& SurfaceRenderTargetBlitter::GetSize() const { return size_; }
 
 SbBlitterRenderTarget SurfaceRenderTargetBlitter::GetSbRenderTarget() const {
   return render_target_;
diff --git a/src/cobalt/renderer/backend/blitter/surface_render_target.h b/src/cobalt/renderer/backend/blitter/surface_render_target.h
index 1a19e4f..48dc88e 100644
--- a/src/cobalt/renderer/backend/blitter/surface_render_target.h
+++ b/src/cobalt/renderer/backend/blitter/surface_render_target.h
@@ -30,7 +30,7 @@
   SurfaceRenderTargetBlitter(SbBlitterDevice device,
                              const math::Size& dimensions);
 
-  const math::Size& GetSize() OVERRIDE;
+  const math::Size& GetSize() const OVERRIDE;
 
   SbBlitterRenderTarget GetSbRenderTarget() const OVERRIDE;
 
diff --git a/src/cobalt/renderer/backend/egl/display.cc b/src/cobalt/renderer/backend/egl/display.cc
index c0603ca..5f1c382 100644
--- a/src/cobalt/renderer/backend/egl/display.cc
+++ b/src/cobalt/renderer/backend/egl/display.cc
@@ -27,7 +27,7 @@
   DisplayRenderTargetEGL(EGLDisplay display, EGLConfig config,
                          EGLNativeWindowType window_handle);
 
-  const math::Size& GetSize() OVERRIDE;
+  const math::Size& GetSize() const OVERRIDE;
 
   EGLSurface GetSurface() const OVERRIDE;
 
@@ -72,7 +72,7 @@
   size_.SetSize(egl_surface_width, egl_surface_height);
 }
 
-const math::Size& DisplayRenderTargetEGL::GetSize() { return size_; }
+const math::Size& DisplayRenderTargetEGL::GetSize() const { return size_; }
 
 DisplayRenderTargetEGL::~DisplayRenderTargetEGL() {
   eglDestroySurface(display_, surface_);
diff --git a/src/cobalt/renderer/backend/egl/framebuffer_render_target.h b/src/cobalt/renderer/backend/egl/framebuffer_render_target.h
index bdd6a10..0df7044 100644
--- a/src/cobalt/renderer/backend/egl/framebuffer_render_target.h
+++ b/src/cobalt/renderer/backend/egl/framebuffer_render_target.h
@@ -30,31 +30,32 @@
  public:
   FramebufferRenderTargetEGL(GraphicsContextEGL* graphics_context,
                              const math::Size& size)
-      : framebuffer_(
-            new FramebufferEGL(graphics_context, size, GL_RGBA, GL_NONE)) {}
+      : framebuffer_(graphics_context, size, GL_RGBA, GL_NONE) {}
 
-  const math::Size& GetSize() OVERRIDE { return framebuffer_->GetSize(); }
+  const math::Size& GetSize() const OVERRIDE { return framebuffer_.GetSize(); }
 
   // This render target does not have an EGLSurface.
   EGLSurface GetSurface() const OVERRIDE { return EGL_NO_SURFACE; }
 
   // This handle is suitable for use with glBindFramebuffer.
-  intptr_t GetPlatformHandle() OVERRIDE { return framebuffer_->gl_handle(); }
+  intptr_t GetPlatformHandle() const OVERRIDE {
+    return framebuffer_.gl_handle();
+  }
 
   // Create a depth buffer for the render target if it doesn't already have one.
   void EnsureDepthBufferAttached(GLenum depth_format) {
-    framebuffer_->EnsureDepthBufferAttached(depth_format);
+    framebuffer_.EnsureDepthBufferAttached(depth_format);
   }
 
   // Get the color texture attachment of the framebuffer.
   TextureEGL* GetColorTexture() const {
-    return framebuffer_->GetColorTexture();
+    return framebuffer_.GetColorTexture();
   }
 
  private:
   ~FramebufferRenderTargetEGL() OVERRIDE {}
 
-  scoped_ptr<FramebufferEGL> framebuffer_;
+  FramebufferEGL framebuffer_;
 };
 
 }  // namespace backend
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.cc b/src/cobalt/renderer/backend/egl/graphics_system.cc
index 86a40d1..ae84a45 100644
--- a/src/cobalt/renderer/backend/egl/graphics_system.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_system.cc
@@ -89,10 +89,6 @@
 #endif
                              EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES2_BIT,
-#if defined(COBALT_RASTERIZER_USES_DEPTH_BUFFER)
-                             EGL_DEPTH_SIZE,
-                             16,
-#endif
                              EGL_NONE};
 
   EGLint num_configs;
diff --git a/src/cobalt/renderer/backend/egl/pbuffer_render_target.cc b/src/cobalt/renderer/backend/egl/pbuffer_render_target.cc
index 6e8a7e2..897968f 100644
--- a/src/cobalt/renderer/backend/egl/pbuffer_render_target.cc
+++ b/src/cobalt/renderer/backend/egl/pbuffer_render_target.cc
@@ -47,7 +47,7 @@
   CHECK_EQ(EGL_SUCCESS, eglGetError());
 }
 
-const math::Size& PBufferRenderTargetEGL::GetSize() { return size_; }
+const math::Size& PBufferRenderTargetEGL::GetSize() const { return size_; }
 
 EGLSurface PBufferRenderTargetEGL::GetSurface() const {
   return surface_;
diff --git a/src/cobalt/renderer/backend/egl/pbuffer_render_target.h b/src/cobalt/renderer/backend/egl/pbuffer_render_target.h
index 66750f3..7ae3267 100644
--- a/src/cobalt/renderer/backend/egl/pbuffer_render_target.h
+++ b/src/cobalt/renderer/backend/egl/pbuffer_render_target.h
@@ -31,7 +31,7 @@
   PBufferRenderTargetEGL(
       EGLDisplay display, EGLConfig config, const math::Size& dimensions);
 
-  const math::Size& GetSize() OVERRIDE;
+  const math::Size& GetSize() const OVERRIDE;
 
   EGLSurface GetSurface() const OVERRIDE;
 
diff --git a/src/cobalt/renderer/backend/egl/render_target.h b/src/cobalt/renderer/backend/egl/render_target.h
index d947408..7f7fc6a 100644
--- a/src/cobalt/renderer/backend/egl/render_target.h
+++ b/src/cobalt/renderer/backend/egl/render_target.h
@@ -40,7 +40,7 @@
   // Since all RenderTargets defined at this level are EGL objects, they
   // will always be set via eglMakeCurrent() and so they can always be
   // referenced by OpenGL by binding framebuffer 0.
-  intptr_t GetPlatformHandle() OVERRIDE { return 0; }
+  intptr_t GetPlatformHandle() const OVERRIDE { return 0; }
 
   virtual bool IsWindowRenderTarget() const { return false; }
 
diff --git a/src/cobalt/renderer/backend/render_target.h b/src/cobalt/renderer/backend/render_target.h
index fef9bc6..05786a6 100644
--- a/src/cobalt/renderer/backend/render_target.h
+++ b/src/cobalt/renderer/backend/render_target.h
@@ -37,11 +37,11 @@
   RenderTarget();
 
   // Return metadata about the render target such as dimensions and format.
-  virtual const math::Size& GetSize() = 0;
+  virtual const math::Size& GetSize() const = 0;
 
   // Returns a platform-specific handle to the render target that can be
   // passed into platform-specific code.
-  virtual intptr_t GetPlatformHandle() = 0;
+  virtual intptr_t GetPlatformHandle() const = 0;
 
   // Each render is assigned a unique serial number on construction.
   int32_t GetSerialNumber() const { return serial_number_; }
diff --git a/src/cobalt/renderer/backend/render_target_stub.h b/src/cobalt/renderer/backend/render_target_stub.h
index 56ed5d3..5a3d8c3 100644
--- a/src/cobalt/renderer/backend/render_target_stub.h
+++ b/src/cobalt/renderer/backend/render_target_stub.h
@@ -28,9 +28,9 @@
  public:
   explicit RenderTargetStub(const math::Size& size) : size_(size) {}
 
-  const math::Size& GetSize() OVERRIDE { return size_; }
+  const math::Size& GetSize() const OVERRIDE { return size_; }
 
-  intptr_t GetPlatformHandle() OVERRIDE { return 0; }
+  intptr_t GetPlatformHandle() const OVERRIDE { return 0; }
 
  private:
   ~RenderTargetStub() {}
diff --git a/src/cobalt/renderer/fps_overlay.cc b/src/cobalt/renderer/fps_overlay.cc
new file mode 100644
index 0000000..090d426
--- /dev/null
+++ b/src/cobalt/renderer/fps_overlay.cc
@@ -0,0 +1,141 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/fps_overlay.h"
+
+#include <string>
+#include <vector>
+
+#include "base/stringprintf.h"
+#include "cobalt/render_tree/brush.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/render_tree/composition_node.h"
+#include "cobalt/render_tree/font.h"
+#include "cobalt/render_tree/glyph_buffer.h"
+#include "cobalt/render_tree/rect_node.h"
+#include "cobalt/render_tree/resource_provider.h"
+#include "cobalt/render_tree/text_node.h"
+
+namespace cobalt {
+namespace renderer {
+
+namespace {
+const render_tree::ColorRGBA kTextColor(1.0f, 1.0f, 1.0f, 1.0f);
+const render_tree::ColorRGBA kBackgroundColor(1.0f, 0.6f, 0.6f, 1.0f);
+const char* kTypefaceName = "Roboto";
+const float kFontSize = 14.0f;
+
+// Distance between the text bounds and the background bounds.
+const int kTextMarginInPixels = 5;
+
+// Spacing between each line of text.
+const int kTextVerticalSpacingInPixels = 5;
+
+scoped_refptr<render_tree::Node> ConvertLinesToOverlay(
+    render_tree::ResourceProvider* resource_provider, render_tree::Font* font,
+    const std::vector<std::string>& lines) {
+  // First create a composition node that composes all the lines of text
+  // together.
+  float y_offset = kTextMarginInPixels;
+  render_tree::CompositionNode::Builder text_builder;
+  for (auto line : lines) {
+    scoped_refptr<render_tree::GlyphBuffer> glyph_buffer =
+        resource_provider->CreateGlyphBuffer(line, font);
+    math::RectF bounds(glyph_buffer->GetBounds());
+
+    text_builder.AddChild(new render_tree::TextNode(
+        math::Vector2dF(-bounds.x() + kTextMarginInPixels,
+                        -bounds.y() + y_offset),
+        glyph_buffer, kTextColor));
+
+    y_offset += bounds.height() + kTextVerticalSpacingInPixels;
+  }
+
+  scoped_refptr<render_tree::CompositionNode> text =
+      new render_tree::CompositionNode(text_builder.Pass());
+
+  // Now compose that onto a solid background.
+  math::RectF background_bounds = text->GetBounds();
+  background_bounds.set_height(background_bounds.height() +
+                               2 * kTextMarginInPixels);
+  background_bounds.set_y(0);
+  background_bounds.set_width(background_bounds.width() +
+                              2 * kTextMarginInPixels);
+  background_bounds.set_x(0);
+
+  render_tree::CompositionNode::Builder text_with_background_builder;
+  text_with_background_builder.AddChild(new render_tree::RectNode(
+      background_bounds,
+      scoped_ptr<render_tree::Brush>(
+          new render_tree::SolidColorBrush(kBackgroundColor))));
+  text_with_background_builder.AddChild(text);
+
+  return new render_tree::CompositionNode(text_with_background_builder.Pass());
+}
+
+scoped_refptr<render_tree::Node> ConvertFPSStatsToOverlay(
+    render_tree::ResourceProvider* resource_provider, render_tree::Font* font,
+    const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+        fps_stats) {
+  std::vector<std::string> lines;
+  lines.push_back(base::StringPrintf(
+      "Samples: %d", static_cast<unsigned int>(fps_stats.sample_count)));
+  lines.push_back(base::StringPrintf("Average: %.1fms",
+                                     fps_stats.average.InMillisecondsF()));
+  lines.push_back(
+      base::StringPrintf("Min: %.1fms", fps_stats.minimum.InMillisecondsF()));
+  lines.push_back(
+      base::StringPrintf("Max: %.1fms", fps_stats.maximum.InMillisecondsF()));
+  lines.push_back(base::StringPrintf(
+      "25th Pct: %.1fms", fps_stats.percentile_25th.InMillisecondsF()));
+  lines.push_back(base::StringPrintf(
+      "50th Pct: %.1fms", fps_stats.percentile_50th.InMillisecondsF()));
+  lines.push_back(base::StringPrintf(
+      "75th Pct: %.1fms", fps_stats.percentile_75th.InMillisecondsF()));
+  lines.push_back(base::StringPrintf(
+      "95th Pct: %.1fms", fps_stats.percentile_95th.InMillisecondsF()));
+
+  return ConvertLinesToOverlay(resource_provider, font, lines);
+}
+}  // namespace
+
+FpsOverlay::FpsOverlay(render_tree::ResourceProvider* resource_provider)
+    : resource_provider_(resource_provider) {
+  font_ = resource_provider_
+              ->GetLocalTypeface(kTypefaceName, render_tree::FontStyle())
+              ->CreateFontWithSize(kFontSize);
+}
+
+void FpsOverlay::UpdateOverlay(
+    const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+        fps_stats) {
+  cached_overlay_ =
+      ConvertFPSStatsToOverlay(resource_provider_, font_, fps_stats);
+}
+
+scoped_refptr<render_tree::Node> FpsOverlay::AnnotateRenderTreeWithOverlay(
+    render_tree::Node* original_tree) {
+  if (!cached_overlay_) {
+    return original_tree;
+  } else {
+    // Compose the overlay onto the top left corner of the original render tree.
+    render_tree::CompositionNode::Builder builder;
+    builder.AddChild(original_tree);
+    builder.AddChild(cached_overlay_);
+    return new render_tree::CompositionNode(builder.Pass());
+  }
+}
+
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/fps_overlay.h b/src/cobalt/renderer/fps_overlay.h
new file mode 100644
index 0000000..816e450
--- /dev/null
+++ b/src/cobalt/renderer/fps_overlay.h
@@ -0,0 +1,46 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FPS_OVERLAY_H_
+#define COBALT_RENDERER_FPS_OVERLAY_H_
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/base/c_val_collection_timer_stats.h"
+#include "cobalt/render_tree/node.h"
+#include "cobalt/render_tree/resource_provider.h"
+
+namespace cobalt {
+namespace renderer {
+
+class FpsOverlay {
+ public:
+  explicit FpsOverlay(render_tree::ResourceProvider* resource_provider);
+
+  void UpdateOverlay(
+      const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+          fps_stats);
+  scoped_refptr<render_tree::Node> AnnotateRenderTreeWithOverlay(
+      render_tree::Node* original_tree);
+
+ private:
+  render_tree::ResourceProvider* resource_provider_;
+  scoped_refptr<render_tree::Font> font_;
+
+  scoped_refptr<render_tree::Node> cached_overlay_;
+};
+
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_FPS_OVERLAY_H_
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index c70c3ee..6baeb47 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -73,7 +73,8 @@
                    const scoped_refptr<backend::RenderTarget>& render_target,
                    backend::GraphicsContext* graphics_context,
                    bool submit_even_if_render_tree_is_unchanged,
-                   ShutdownClearMode clear_on_shutdown_mode)
+                   ShutdownClearMode clear_on_shutdown_mode,
+                   const Options& options)
     : rasterizer_created_event_(true, false),
       render_target_(render_target),
       graphics_context_(graphics_context),
@@ -85,24 +86,54 @@
       rasterize_periodic_timer_("Renderer.Rasterize.Duration",
                                 kRasterizePeriodicTimerEntriesPerUpdate,
                                 false /*enable_entry_list_c_val*/),
-      rasterize_animations_timer_("Renderer.Rasterize.Animations",
-                                  kRasterizeAnimationsTimerMaxEntries,
-                                  true /*enable_entry_list_c_val*/),
+      ALLOW_THIS_IN_INITIALIZER_LIST(rasterize_animations_timer_(
+          "Renderer.Rasterize.Animations", kRasterizeAnimationsTimerMaxEntries,
+          true /*enable_entry_list_c_val*/,
+          base::Bind(&Pipeline::FrameStatsOnFlushCallback,
+                     base::Unretained(this)))),
+      new_render_tree_rasterize_count_(
+          "Count.Renderer.Rasterize.NewRenderTree", 0,
+          "Total number of new render trees rasterized."),
+      new_render_tree_rasterize_time_(
+          "Time.Renderer.Rasterize.NewRenderTree", 0,
+          "The last time a new render tree was rasterized."),
       has_active_animations_c_val_(
           "Renderer.HasActiveAnimations", false,
-          "Is non-zero if the current render tree has active animations.")
+          "Is non-zero if the current render tree has active animations."),
+      animations_start_time_(
+          "Time.Renderer.Rasterize.Animations.Start", 0,
+          "The most recent time animations started playing."),
+      animations_end_time_("Time.Renderer.Rasterize.Animations.End", 0,
+                           "The most recent time animations ended playing."),
 #if defined(ENABLE_DEBUG_CONSOLE)
-      ,
       ALLOW_THIS_IN_INITIALIZER_LIST(dump_current_render_tree_command_handler_(
-          "dump_render_tree", base::Bind(&Pipeline::OnDumpCurrentRenderTree,
-                                         base::Unretained(this)),
+          "dump_render_tree",
+          base::Bind(&Pipeline::OnDumpCurrentRenderTree,
+                     base::Unretained(this)),
           "Dumps the current render tree to text.",
           "Dumps the current render tree either to the console if no parameter "
           "is specified, or to a file with the specified filename relative to "
-          "the debug output folder."))
+          "the debug output folder.")),
+      ALLOW_THIS_IN_INITIALIZER_LIST(toggle_fps_stdout_command_handler_(
+          "toggle_fps_stdout",
+          base::Bind(&Pipeline::OnToggleFpsStdout, base::Unretained(this)),
+          "Toggles printing framerate stats to stdout.",
+          "When enabled, at the end of each animation (or every time a maximum "
+          "number of frames are rendered), framerate statistics are printed "
+          "to stdout.")),
+      ALLOW_THIS_IN_INITIALIZER_LIST(toggle_fps_overlay_command_handler_(
+          "toggle_fps_overlay",
+          base::Bind(&Pipeline::OnToggleFpsOverlay, base::Unretained(this)),
+          "Toggles rendering framerate stats to an overlay on the display.",
+          "Framerate statistics are rendered to a display overlay.  The "
+          "numbers are updated at the end of each animation (or every time a "
+          "maximum number of frames are rendered), framerate statistics are "
+          "printed to stdout.")),
 #endif
-      ,
-      clear_on_shutdown_mode_(clear_on_shutdown_mode) {
+      clear_on_shutdown_mode_(clear_on_shutdown_mode),
+      enable_fps_stdout_(options.enable_fps_stdout),
+      enable_fps_overlay_(options.enable_fps_overlay),
+      fps_overlay_updated_(false) {
   TRACE_EVENT0("cobalt::renderer", "Pipeline::Pipeline()");
   // The actual Pipeline can be constructed from any thread, but we want
   // rasterizer_thread_checker_ to be associated with the rasterizer thread,
@@ -128,14 +159,9 @@
   // thread as it clears itself out (e.g. it may ask the rasterizer thread to
   // delete textures).  We wait for this shutdown to complete before proceeding
   // to shutdown the rasterizer thread.
-  base::WaitableEvent submission_queue_shutdown(true, false);
-  rasterizer_thread_.message_loop()->PostTask(
+  rasterizer_thread_.message_loop()->PostBlockingTask(
       FROM_HERE,
       base::Bind(&Pipeline::ShutdownSubmissionQueue, base::Unretained(this)));
-  rasterizer_thread_.message_loop()->PostTask(
-      FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
-                            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
@@ -167,13 +193,9 @@
 
 void Pipeline::Clear() {
   TRACE_EVENT0("cobalt::renderer", "Pipeline::Clear()");
-  base::WaitableEvent wait_event(true, false);
-  rasterizer_thread_.message_loop()->PostTask(
+  rasterizer_thread_.message_loop()->PostBlockingTask(
       FROM_HERE,
-      base::Bind(&Pipeline::ClearCurrentRenderTree, base::Unretained(this),
-                 base::Bind(&base::WaitableEvent::Signal,
-                            base::Unretained(&wait_event))));
-  wait_event.Wait();
+      base::Bind(&Pipeline::ClearCurrentRenderTree, base::Unretained(this)));
 }
 
 void Pipeline::RasterizeToRGBAPixels(
@@ -225,17 +247,12 @@
   }
 }
 
-void Pipeline::ClearCurrentRenderTree(
-    const base::Closure& clear_complete_callback) {
+void Pipeline::ClearCurrentRenderTree() {
   DCHECK(rasterizer_thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("cobalt::renderer", "Pipeline::ClearCurrentRenderTree()");
 
   submission_queue_->Reset();
   rasterize_timer_ = base::nullopt;
-
-  if (!clear_complete_callback.is_null()) {
-    clear_complete_callback.Run();
-  }
 }
 
 void Pipeline::RasterizeCurrentTree() {
@@ -246,13 +263,15 @@
   base::TimeTicks now = base::TimeTicks::Now();
   Submission submission = submission_queue_->GetCurrentSubmission(now);
 
-  bool has_render_tree_changed = last_render_animations_active_ ||
-                                 submission.render_tree != last_render_tree_;
+  bool is_new_render_tree = submission.render_tree != last_render_tree_;
+  bool has_render_tree_changed =
+      last_render_animations_active_ || is_new_render_tree;
 
   // If our render tree hasn't changed from the one that was previously
   // rendered and it's okay on this system to not flip the display buffer
   // frequently, then we can just not do anything here.
-  if (!submit_even_if_render_tree_is_unchanged_ && !has_render_tree_changed) {
+  if (!fps_overlay_updated_ && !submit_even_if_render_tree_is_unchanged_ &&
+      !has_render_tree_changed) {
     return;
   }
 
@@ -300,10 +319,16 @@
     rasterize_animations_timer_.Stop();
   }
 
-  // If animations are going from being active to expired, then set the c_val
-  // after rasterizing the final state of the tree. Now that we've finished
-  // tracking the animations, it's time to flush the timer.
-  if (last_render_animations_active_ && !are_animations_active) {
+  if (is_new_render_tree) {
+    ++new_render_tree_rasterize_count_;
+    new_render_tree_rasterize_time_ = base::TimeTicks::Now().ToInternalValue();
+  }
+
+  // Check for if the animations are starting or ending.
+  if (!last_render_animations_active_ && are_animations_active) {
+    animations_start_time_ = base::TimeTicks::Now().ToInternalValue();
+  } else if (last_render_animations_active_ && !are_animations_active) {
+    animations_end_time_ = base::TimeTicks::Now().ToInternalValue();
     has_active_animations_c_val_ = false;
     rasterize_animations_timer_.Flush();
   }
@@ -351,10 +376,15 @@
   }
   previous_animated_area_ = rounded_bounds;
 
+  scoped_refptr<render_tree::Node> submit_tree = results.animated;
+  if (enable_fps_overlay_ && fps_overlay_) {
+    submit_tree = fps_overlay_->AnnotateRenderTreeWithOverlay(results.animated);
+  }
+
   // Rasterize the animated render tree.
   rasterizer::Rasterizer::Options rasterizer_options;
   rasterizer_options.dirty = redraw_area;
-  rasterizer_->Submit(results.animated, render_target, rasterizer_options);
+  rasterizer_->Submit(submit_tree, render_target, rasterizer_options);
 
   if (!submission.on_rasterized_callback.is_null()) {
     submission.on_rasterized_callback.Run();
@@ -465,6 +495,28 @@
                          tree_dump.length());
   }
 }
+
+void Pipeline::OnToggleFpsStdout(const std::string& message) {
+  if (MessageLoop::current() != rasterizer_thread_.message_loop()) {
+    rasterizer_thread_.message_loop()->PostTask(
+        FROM_HERE, base::Bind(&Pipeline::OnToggleFpsStdout,
+                              base::Unretained(this), message));
+    return;
+  }
+
+  enable_fps_stdout_ = !enable_fps_stdout_;
+}
+
+void Pipeline::OnToggleFpsOverlay(const std::string& message) {
+  if (MessageLoop::current() != rasterizer_thread_.message_loop()) {
+    rasterizer_thread_.message_loop()->PostTask(
+        FROM_HERE, base::Bind(&Pipeline::OnToggleFpsOverlay,
+                              base::Unretained(this), message));
+    return;
+  }
+
+  enable_fps_overlay_ = !enable_fps_overlay_;
+}
 #endif  // #if defined(ENABLE_DEBUG_CONSOLE)
 
 Submission Pipeline::CollectAnimations(
@@ -478,5 +530,44 @@
   return collected_submission;
 }
 
+namespace {
+void PrintFPS(
+    const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+        results) {
+  SbLogRaw(base::StringPrintf("FPS => # samples: %d, avg: %.1fms, "
+                              "[min, max]: [%.1fms, %.1fms]\n"
+                              "       25th : 50th : 75th : 95th pct - "
+                              "%.1fms : %.1fms : %.1fms : %.1fms\n",
+                              static_cast<unsigned int>(results.sample_count),
+                              results.average.InMillisecondsF(),
+                              results.minimum.InMillisecondsF(),
+                              results.maximum.InMillisecondsF(),
+                              results.percentile_25th.InMillisecondsF(),
+                              results.percentile_50th.InMillisecondsF(),
+                              results.percentile_75th.InMillisecondsF(),
+                              results.percentile_95th.InMillisecondsF())
+               .c_str());
+}
+}  // namespace
+
+void Pipeline::FrameStatsOnFlushCallback(
+    const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+        flush_results) {
+  DCHECK(rasterizer_thread_checker_.CalledOnValidThread());
+
+  if (enable_fps_overlay_) {
+    if (!fps_overlay_) {
+      fps_overlay_.emplace(rasterizer_->GetResourceProvider());
+    }
+
+    fps_overlay_->UpdateOverlay(flush_results);
+    fps_overlay_updated_ = true;
+  }
+
+  if (enable_fps_stdout_) {
+    PrintFPS(flush_results);
+  }
+}
+
 }  // namespace renderer
 }  // namespace cobalt
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index 8fab055..4f2153d 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -28,6 +28,7 @@
 #include "cobalt/render_tree/animations/animate_node.h"
 #include "cobalt/render_tree/node.h"
 #include "cobalt/renderer/backend/graphics_context.h"
+#include "cobalt/renderer/fps_overlay.h"
 #include "cobalt/renderer/rasterizer/rasterizer.h"
 #include "cobalt/renderer/submission.h"
 #include "cobalt/renderer/submission_queue.h"
@@ -56,6 +57,13 @@
     kNoClear,
   };
 
+  struct Options {
+    Options() : enable_fps_stdout(false), enable_fps_overlay(false) {}
+
+    bool enable_fps_stdout;
+    bool enable_fps_overlay;
+  };
+
   // Using the provided rasterizer creation function, a rasterizer will be
   // created within the Pipeline on a separate rasterizer thread.  Thus,
   // the rasterizer created by the provided function should only reference
@@ -66,7 +74,8 @@
            const scoped_refptr<backend::RenderTarget>& render_target,
            backend::GraphicsContext* graphics_context,
            bool submit_even_if_render_tree_is_unchanged,
-           ShutdownClearMode clear_on_shutdown_mode);
+           ShutdownClearMode clear_on_shutdown_mode,
+           const Options& options = Options());
   ~Pipeline();
 
   // Submit a new render tree to the renderer pipeline.  After calling this
@@ -99,7 +108,7 @@
   void SetNewRenderTree(const Submission& render_tree_submission);
 
   // Clears the current render tree and calls the callback when this is done.
-  void ClearCurrentRenderTree(const base::Closure& clear_complete_callback);
+  void ClearCurrentRenderTree();
 
   // Called repeatedly (the rate is limited by the rasterizer, so likely it
   // will be called every 1/60th of a second) on the rasterizer thread and
@@ -130,6 +139,8 @@
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   void OnDumpCurrentRenderTree(const std::string&);
+  void OnToggleFpsStdout(const std::string&);
+  void OnToggleFpsOverlay(const std::string&);
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
   // Render trees may contain a number of AnimateNodes (or none).  In order
@@ -139,6 +150,10 @@
   // render tree.
   Submission CollectAnimations(const Submission& render_tree_submission);
 
+  void FrameStatsOnFlushCallback(
+      const base::CValCollectionTimerStats<base::CValPublic>::FlushResults&
+          flush_results);
+
   base::WaitableEvent rasterizer_created_event_;
 
   // The render_target that all submitted render trees will be rasterized to.
@@ -197,20 +212,45 @@
   // Timer tracking the amount of time spent in
   // |RasterizeSubmissionToRenderTarget| while animations are active. The
   // tracking is flushed when the animations expire.
-  base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_;
+  base::CValCollectionTimerStats<base::CValPublic> rasterize_animations_timer_;
 
-  // Tracks whether or not animations are currently playing.
+  // The total number of new render trees that have been rasterized.
+  base::CVal<int> new_render_tree_rasterize_count_;
+  // The last time that a newly encountered render tree was first rasterized.
+  base::CVal<int64> new_render_tree_rasterize_time_;
+
+  // Whether or not animations are currently playing.
   base::CVal<bool> has_active_animations_c_val_;
+  // The most recent time animations started playing.
+  base::CVal<int64> animations_start_time_;
+  // The most recent time animations ended playing.
+  base::CVal<int64> animations_end_time_;
 
 #if defined(ENABLE_DEBUG_CONSOLE)
   // Dumps the current render tree to the console.
   base::ConsoleCommandManager::CommandHandler
       dump_current_render_tree_command_handler_;
+
+  base::ConsoleCommandManager::CommandHandler
+      toggle_fps_stdout_command_handler_;
+  base::ConsoleCommandManager::CommandHandler
+      toggle_fps_overlay_command_handler_;
 #endif
 
   // If true, Pipeline's destructor will clear its render target to black on
   // shutdown.
   const ShutdownClearMode clear_on_shutdown_mode_;
+
+  // If true, we will print framerate statistics to stdout upon completion
+  // of each animation (or after a maximum number of frames has been issued).
+  bool enable_fps_stdout_;
+
+  // If true, an overlay will be displayed over the UI output that shows the
+  // FPS statistics from the last animation.
+  bool enable_fps_overlay_;
+
+  base::optional<FpsOverlay> fps_overlay_;
+  bool fps_overlay_updated_;
 };
 
 }  // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
index cdd0f00..f29b8c0 100644
--- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
@@ -282,8 +282,11 @@
       context, SbBlitterMakeRect(0, 0, size.width(), size.height())));
 
   RenderTreeNodeVisitor visitor(
-      context_->GetSbBlitterDevice(), context,
-      initial_render_state, NULL, NULL, NULL, NULL, NULL);
+      context_->GetSbBlitterDevice(), context, initial_render_state,
+      &scratch_surface_cache_,
+      surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL,
+      surface_cache_ ? &surface_cache_.value() : NULL, &software_surface_cache_,
+      &linear_gradient_cache_);
   render_tree->Accept(&visitor);
 
   CHECK(SbBlitterFlushContext(context));
diff --git a/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
index 68c53b2..a37b317 100644
--- a/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
+++ b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
@@ -39,6 +39,7 @@
 using cobalt::renderer::rasterizer::blitter::RenderState;
 using cobalt::renderer::rasterizer::blitter::SkiaToBlitterPixelFormat;
 using cobalt::renderer::rasterizer::blitter::RectFToRect;
+using cobalt::renderer::rasterizer::blitter::RectFToBlitterRect;
 using cobalt::renderer::rasterizer::blitter::LinearGradientCache;
 using cobalt::renderer::rasterizer::skia::SkiaColorStops;
 
@@ -223,14 +224,16 @@
 
   // 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
+  // done 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
+  // Once 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;
+  int width = brush.IsHorizontal() ?
+              std::abs(brush.dest().x() - brush.source().x()) : 1;
+  int height = brush.IsVertical() ?
+              std::abs(brush.dest().y() - brush.source().y()) : 1;
 
   if (SbBlitterIsSurfaceValid(surface) == false) {
     SkImageInfo image_info = SkImageInfo::MakeN32Premul(width, height);
@@ -288,13 +291,34 @@
     SbBlitterSetModulateBlitsWithColor(context, false);
     cobalt::math::Rect transformed_rect =
         RectFToRect(render_state.transform.TransformRect(rect));
-    SbBlitterRect source_rect = SbBlitterMakeRect(0, 0, width, height);
+
+    // It may be the case that the linear gradient is larger than the rect.
+    SbBlitterRect source_rect;
+    if (height == 1) {
+      int left = rect.x() - std::min(brush.source().x(), brush.dest().x());
+      source_rect = SbBlitterMakeRect(left, 0, rect.width(), 1);
+    } else {
+      int top = rect.y() - std::min(brush.source().y(), brush.dest().y());
+      source_rect = SbBlitterMakeRect(0, top, 1, rect.height());
+    }
+
     SbBlitterRect dest_rect =
         SbBlitterMakeRect(transformed_rect.x(), transformed_rect.y(),
                           transformed_rect.width(), transformed_rect.height());
     SbBlitterBlitRectToRect(context, surface, source_rect, dest_rect);
   }
 }
+
+void RenderColoredRect(SbBlitterDevice device, SbBlitterContext context,
+                       const RenderState& render_state, const ColorRGBA& color,
+                       const cobalt::math::RectF& rect) {
+  SbBlitterSetBlending(context, color.a() < 1.0f);
+  SbBlitterSetColor(context, SbBlitterColorFromRGBA(
+      color.rgb8_r(), color.rgb8_g(), color.rgb8_b(), color.rgb8_a()));
+  SbBlitterFillRect(context, RectFToBlitterRect(
+      render_state.transform.TransformRect(rect)));
+}
+
 }  // namespace
 
 namespace cobalt {
@@ -315,12 +339,66 @@
   if (!linear_gradient_brush) return false;
 
   // Currently, only vertical and horizontal gradients are accelerated.
-  if ((linear_gradient_brush->IsVertical() ||
-       linear_gradient_brush->IsHorizontal()) == false)
+  math::RectF content_rect = rect_node.data().rect;
+  if (linear_gradient_brush->IsVertical()) {
+    // Render solid-colored rect(s) to fill any gaps between the content_rect
+    // and the gradient.
+    float top = linear_gradient_brush->source().y();
+    float bottom = linear_gradient_brush->dest().y();
+    ColorRGBA top_color = linear_gradient_brush->color_stops().front().color;
+    ColorRGBA bottom_color = linear_gradient_brush->color_stops().back().color;
+    if (top > bottom) {
+      std::swap(top, bottom);
+      std::swap(top_color, bottom_color);
+    }
+
+    if (top > content_rect.y()) {
+      float gap = top - content_rect.y();
+      RenderColoredRect(device, context, render_state, top_color,
+          math::RectF(content_rect.x(), content_rect.y(), content_rect.width(),
+                      gap));
+      content_rect.set_y(top);
+      content_rect.set_height(content_rect.height() - gap);
+    }
+    if (bottom < content_rect.bottom()) {
+      float gap = content_rect.bottom() - bottom;
+      RenderColoredRect(device, context, render_state, bottom_color,
+          math::RectF(content_rect.x(), bottom, content_rect.width(), gap));
+      content_rect.set_height(content_rect.height() - gap);
+    }
+  } else if (linear_gradient_brush->IsHorizontal()) {
+    // Render solid-colored rect(s) to fill any gaps between the content_rect
+    // and the gradient.
+    float left = linear_gradient_brush->source().x();
+    float right = linear_gradient_brush->dest().x();
+    ColorRGBA left_color = linear_gradient_brush->color_stops().front().color;
+    ColorRGBA right_color = linear_gradient_brush->color_stops().back().color;
+    if (left > right) {
+      std::swap(left, right);
+      std::swap(left_color, right_color);
+    }
+
+    if (left > content_rect.x()) {
+      float gap = left - content_rect.x();
+      RenderColoredRect(device, context, render_state, left_color,
+          math::RectF(content_rect.x(), content_rect.y(), gap,
+                      content_rect.height()));
+      content_rect.set_x(left);
+      content_rect.set_width(content_rect.width() - gap);
+    }
+    if (right < content_rect.right()) {
+      float gap = content_rect.right() - right;
+      RenderColoredRect(device, context, render_state, right_color,
+          math::RectF(right, content_rect.y(), gap, content_rect.height()));
+      content_rect.set_width(content_rect.width() - gap);
+    }
+  } else {
+    // Angled gradients are not supported by the optimized path.
     return false;
+  }
 
   RenderOptimizedLinearGradient(device, context, render_state,
-                                rect_node.data().rect, *linear_gradient_brush,
+                                content_rect, *linear_gradient_brush,
                                 linear_gradient_cache);
   return true;
 }
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_callback.cc b/src/cobalt/renderer/rasterizer/egl/draw_callback.cc
new file mode 100644
index 0000000..c41246e
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_callback.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/egl/draw_callback.h"
+
+#include <GLES2/gl2.h>
+
+#include "base/basictypes.h"
+#include "cobalt/renderer/backend/egl/utils.h"
+#include "egl/generated_shader_impl.h"
+#include "starboard/memory.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+DrawCallback::DrawCallback(const base::Closure& rasterize_callback)
+    : rasterize_callback_(rasterize_callback) {}
+
+void DrawCallback::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  SB_UNREFERENCED_PARAMETER(graphics_state);
+  SB_UNREFERENCED_PARAMETER(program_manager);
+}
+
+void DrawCallback::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  SB_UNREFERENCED_PARAMETER(graphics_state);
+  SB_UNREFERENCED_PARAMETER(program_manager);
+  if (!rasterize_callback_.is_null()) {
+    rasterize_callback_.Run();
+  }
+}
+
+base::TypeId DrawCallback::GetTypeId() const {
+  return base::GetTypeId<DrawCallback>();
+}
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_callback.h b/src/cobalt/renderer/rasterizer/egl/draw_callback.h
new file mode 100644
index 0000000..3dd070f
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_callback.h
@@ -0,0 +1,46 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_EGL_DRAW_CALLBACK_H_
+#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_CALLBACK_H_
+
+#include "base/callback.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+// This is a proxy draw object that allows calling arbitrary functions.
+class DrawCallback : public DrawObject {
+ public:
+  explicit DrawCallback(const base::Closure& rasterize_callback);
+
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
+
+ private:
+  base::Closure rasterize_callback_;
+};
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_EGL_DRAW_CALLBACK_H_
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_clear.cc b/src/cobalt/renderer/rasterizer/egl/draw_clear.cc
new file mode 100644
index 0000000..ae883b1
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_clear.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/egl/draw_clear.h"
+
+#include <GLES2/gl2.h>
+
+#include "cobalt/renderer/backend/egl/utils.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+DrawClear::DrawClear(GraphicsState* graphics_state,
+    const BaseState& base_state, const render_tree::ColorRGBA& clear_color)
+    : DrawObject(base_state),
+      clear_color_(clear_color) {}
+
+void DrawClear::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  SB_UNREFERENCED_PARAMETER(graphics_state);
+  SB_UNREFERENCED_PARAMETER(program_manager);
+}
+
+void DrawClear::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  SB_UNREFERENCED_PARAMETER(program_manager);
+
+  graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+      base_state_.scissor.width(), base_state_.scissor.height());
+  graphics_state->Clear(clear_color_.r(), clear_color_.g(), clear_color_.b(),
+      clear_color_.a());
+}
+
+base::TypeId DrawClear::GetTypeId() const {
+  return base::GetTypeId<DrawClear>();
+}
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_clear.h b/src/cobalt/renderer/rasterizer/egl/draw_clear.h
new file mode 100644
index 0000000..8c533de
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_clear.h
@@ -0,0 +1,49 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_EGL_DRAW_CLEAR_H_
+#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_CLEAR_H_
+
+#include "base/callback.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+// This object handles clearing the current scissor area with the given color.
+class DrawClear : public DrawObject {
+ public:
+  DrawClear(GraphicsState* graphics_state,
+            const BaseState& base_state,
+            const render_tree::ColorRGBA& clear_color);
+
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
+
+ private:
+  render_tree::ColorRGBA clear_color_;
+};
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_EGL_DRAW_CLEAR_H_
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.cc b/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.cc
deleted file mode 100644
index dc6664c..0000000
--- a/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT 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/egl/draw_depth_stencil.h"
-
-#include <GLES2/gl2.h>
-
-#include "cobalt/renderer/backend/egl/utils.h"
-#include "starboard/log.h"
-
-namespace cobalt {
-namespace renderer {
-namespace rasterizer {
-namespace egl {
-
-DrawDepthStencil::DrawDepthStencil(GraphicsState* graphics_state,
-    const BaseState& base_state, const math::RectF& include_scissor,
-    const math::RectF& exclude_scissor)
-    : DrawPolyColor(base_state),
-      include_scissor_first_vert_(-1),
-      exclude_scissor_first_vert_(-1) {
-  attributes_.reserve(MaxVertsNeededForStencil());
-  AddStencil(include_scissor, exclude_scissor);
-  graphics_state->ReserveVertexData(
-      attributes_.size() * sizeof(VertexAttributes));
-}
-
-DrawDepthStencil::DrawDepthStencil(const BaseState& base_state)
-    : DrawPolyColor(base_state),
-      include_scissor_first_vert_(-1),
-      exclude_scissor_first_vert_(-1) {
-}
-
-void DrawDepthStencil::ExecuteOnscreenRasterize(
-    GraphicsState* graphics_state,
-    ShaderProgramManager* program_manager) {
-  SetupShader(graphics_state, program_manager);
-  DrawStencil(graphics_state);
-}
-
-void DrawDepthStencil::UndoStencilState(GraphicsState* graphics_state) {
-  graphics_state->ResetDepthFunc();
-}
-
-void DrawDepthStencil::DrawStencil(GraphicsState* graphics_state) {
-  // This must occur during the transparency pass.
-  SB_DCHECK(graphics_state->IsBlendEnabled());
-  SB_DCHECK(!graphics_state->IsDepthWriteEnabled());
-  SB_DCHECK(graphics_state->IsDepthTestEnabled());
-
-  // Set depth of pixels in the scissor rects.
-  graphics_state->EnableDepthWrite();
-  if (exclude_scissor_first_vert_ >= 0) {
-    GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, exclude_scissor_first_vert_, 4));
-  }
-  SB_DCHECK(include_scissor_first_vert_ >= 0);
-  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, include_scissor_first_vert_, 4));
-  graphics_state->DisableDepthWrite();
-
-  // Set the depth test function for subsequent draws to occur within the
-  // stencil. Be sure to call UndoStencilState() when drawing in the
-  // stencilled area is no longer desired.
-  GL_CALL(glDepthFunc(GL_EQUAL));
-}
-
-void DrawDepthStencil::AddStencil(
-    const math::RectF& include_scissor,
-    const math::RectF& exclude_scissor) {
-  // At least the include_scissor must be specified.
-  SB_DCHECK(!include_scissor.IsEmpty());
-
-  include_scissor_first_vert_ = static_cast<GLint>(attributes_.size());
-  AddRect(include_scissor, 0);
-  if (!exclude_scissor.IsEmpty()) {
-    // Exclude scissor must use the next closest depth.
-    float depth = base_state_.depth;
-    base_state_.depth = GraphicsState::NextClosestDepth(depth);
-    exclude_scissor_first_vert_ = static_cast<GLint>(attributes_.size());
-    AddRect(exclude_scissor, 0);
-    base_state_.depth = depth;
-  }
-}
-
-}  // namespace egl
-}  // namespace rasterizer
-}  // namespace renderer
-}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.h b/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.h
deleted file mode 100644
index 5233355..0000000
--- a/src/cobalt/renderer/rasterizer/egl/draw_depth_stencil.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2017 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT 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_EGL_DRAW_DEPTH_STENCIL_H_
-#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_DEPTH_STENCIL_H_
-
-#include <vector>
-
-#include "cobalt/math/rect_f.h"
-#include "cobalt/render_tree/color_rgba.h"
-#include "cobalt/renderer/rasterizer/egl/draw_poly_color.h"
-
-namespace cobalt {
-namespace renderer {
-namespace rasterizer {
-namespace egl {
-
-// Handles creation of a depth stencil. Pixels within the include scissor will
-// have their depth set to base_state.depth. Pixels within the exclude scissor,
-// if specified, will have their depth set to the next closest depth value. An
-// |include_scissor| must always be specified.
-//
-// NOTE: This object leaves the graphics state modified so that only the
-// stencilled pixels are affected by later draw calls. Use UndoStencilState
-// once finished with the stencil.
-// NOTE: If an |exclude_scissor| is specified, then this object uses two depth
-// values -- the incoming value in base_state.depth and the next closest.
-// NOTE: Since scissor rects pollute the depth buffer, they should only be
-// used during the tranparency pass because subsequent draws are guaranteed to
-// be above (or not overlap) these pixels.
-class DrawDepthStencil : public DrawPolyColor {
- public:
-  DrawDepthStencil(GraphicsState* graphics_state,
-                   const BaseState& base_state,
-                   const math::RectF& include_scissor,
-                   const math::RectF& exclude_scissor);
-
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
-      ShaderProgramManager* program_manager) OVERRIDE;
-
-  // ExecuteOnscreenRasterize / DrawDepthStencil change the graphics state
-  // so that subsequent draw calls affect only the stencilled pixels. This
-  // function reverts the graphics state back to normal.
-  void UndoStencilState(GraphicsState* graphics_state);
-
- protected:
-  explicit DrawDepthStencil(const BaseState& base_state);
-  void AddStencil(const math::RectF& include_scissor,
-                  const math::RectF& exclude_scissor);
-  void DrawStencil(GraphicsState* graphics_state);
-  static size_t MaxVertsNeededForStencil() { return 8; }
-
-  GLint include_scissor_first_vert_;
-  GLint exclude_scissor_first_vert_;
-};
-
-}  // namespace egl
-}  // namespace rasterizer
-}  // namespace renderer
-}  // namespace cobalt
-
-#endif  // COBALT_RENDERER_RASTERIZER_EGL_DRAW_DEPTH_STENCIL_H_
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object.cc b/src/cobalt/renderer/rasterizer/egl/draw_object.cc
index 1e14af3..105077f 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object.cc
@@ -32,13 +32,11 @@
       scissor(0, 0,
               std::numeric_limits<int>::max(),
               std::numeric_limits<int>::max()),
-      depth(GraphicsState::FarthestDepth()),
       opacity(1.0f) {}
 
 DrawObject::BaseState::BaseState(const BaseState& other)
     : transform(other.transform),
       scissor(other.scissor),
-      depth(other.depth),
       opacity(other.opacity) {}
 
 DrawObject::DrawObject(const BaseState& base_state)
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object.h b/src/cobalt/renderer/rasterizer/egl/draw_object.h
index 4924230..9410063 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object.h
@@ -37,40 +37,31 @@
 
     math::Matrix3F transform;
     math::Rect scissor;
-    float depth;
     float opacity;
   };
 
   virtual ~DrawObject() {}
 
-  // This stage is used to render to offscreen targets. It specifically runs
-  // before vertex data is updated for onscreen rendering in order to allow
-  // this stage to modify that data. For example, this stage may rasterize to
-  // an offscreen atlas, so the location within the atlas might impact the
-  // texture coordinates used for the onscreen rasterize stage.
-  //
-  // If this stage needs to use vertex data of its own, then a new stage,
-  // ExecuteOffscreenUpdateVertexBuffer, should be created. This stage should
-  // be handled similarly to the current ExecuteOnscreenUpdateVertexBuffer.
-  virtual void ExecuteOffscreenRasterize(GraphicsState* graphics_state,
-      ShaderProgramManager* program_manager) {}
-
-  // This stage is used to update the vertex buffer for the onscreen rasterize
+  // This stage is used to update the vertex buffer for the rasterize
   // stage. Vertex data is handled by the GraphicsState to minimize the number
   // of vertex buffers needed. Once this stage is executed, the rasterizer will
   // then notify the GraphicsState to send all vertex data from all draw
   // objects to the GPU.
-  virtual void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
+  virtual void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) = 0;
 
-  // This stage is used to render to the main render target. Although it can be
-  // used to rasterize to any number of render targets, it is best to use a
-  // different stage for offscreen rendering in order to minimize the cost of
-  // switching render targets.
-  virtual void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
+  // This stage is responsible for issuing the GPU commands to do the actual
+  // rendering.
+  virtual void ExecuteRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) = 0;
 
+  // Return a TypeId that can be used to sort draw objects in order to minimize
+  // state changes. This may be the class' TypeId, or the TypeId of the shader
+  // program which the class uses.
+  virtual base::TypeId GetTypeId() const = 0;
+
  protected:
+  DrawObject() {}
   explicit DrawObject(const BaseState& base_state);
 
   // Return a uint32_t suitable to be transferred as 4 unsigned bytes
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object_manager.cc b/src/cobalt/renderer/rasterizer/egl/draw_object_manager.cc
index a3f91c29..7662e3c 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object_manager.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object_manager.cc
@@ -14,98 +14,253 @@
 
 #include "cobalt/renderer/rasterizer/egl/draw_object_manager.h"
 
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/renderer/backend/egl/utils.h"
+#include "cobalt/renderer/rasterizer/egl/draw_callback.h"
+
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
 namespace egl {
 
-void DrawObjectManager::AddOpaqueDraw(scoped_ptr<DrawObject> object,
-                                      OnscreenType onscreen_type,
-                                      OffscreenType offscreen_type) {
-  if (offscreen_type != kOffscreenNone) {
-    offscreen_order_[offscreen_type].push_back(object.get());
-  }
+DrawObjectManager::DrawObjectManager(
+    const base::Closure& reset_external_rasterizer,
+    const base::Closure& flush_external_offscreen_draws)
+    : reset_external_rasterizer_(reset_external_rasterizer),
+      flush_external_offscreen_draws_(flush_external_offscreen_draws),
+      current_draw_id_(0) {}
 
-  draw_objects_[onscreen_type].push_back(object.release());
+uint32_t DrawObjectManager::AddOnscreenDraw(scoped_ptr<DrawObject> draw_object,
+    BlendType blend_type, base::TypeId draw_type,
+    const backend::RenderTarget* render_target,
+    const math::RectF& draw_bounds) {
+  onscreen_draws_.push_back(DrawInfo(
+      draw_object.Pass(), draw_type, blend_type, render_target,
+      draw_bounds, ++current_draw_id_));
+  return current_draw_id_;
 }
 
-void DrawObjectManager::AddTransparentDraw(scoped_ptr<DrawObject> object,
-                                           OnscreenType onscreen_type,
-                                           OffscreenType offscreen_type,
-                                           const math::RectF& bounds) {
-  // Try to sort the transparent object next to another object of its type.
-  // However, this can only be done as long as its bounds do not overlap with
-  // the other object while swapping draw order.
-  size_t position = transparent_object_info_.size();
-  while (position > 0) {
-    if (transparent_object_info_[position - 1].type <= onscreen_type) {
-      break;
-    }
-    if (transparent_object_info_[position - 1].bounds.Intersects(bounds)) {
-      break;
-    }
-    --position;
+uint32_t DrawObjectManager::AddOffscreenDraw(scoped_ptr<DrawObject> draw_object,
+    BlendType blend_type, base::TypeId draw_type,
+    const backend::RenderTarget* render_target,
+    const math::RectF& draw_bounds) {
+  // Put all draws using kBlendExternal into their own draw list since they
+  // use an external rasterizer which tracks its own state.
+  auto* draw_list = &offscreen_draws_;
+  if (blend_type == kBlendExternal) {
+    draw_list = &external_offscreen_draws_;
+
+    // Only the DrawCallback type should use kBlendExternal. All other draw
+    // object types use the native rasterizer.
+    DCHECK(base::polymorphic_downcast<DrawCallback*>(draw_object.get()));
   }
 
-  if (offscreen_type != kOffscreenNone) {
-    offscreen_order_[offscreen_type].push_back(object.get());
-  }
+  draw_list->push_back(DrawInfo(
+      draw_object.Pass(), draw_type, blend_type, render_target,
+      draw_bounds, ++current_draw_id_));
+  return current_draw_id_;
+}
 
-  transparent_object_info_.insert(
-      transparent_object_info_.begin() + position,
-      TransparentObjectInfo(onscreen_type, bounds));
-  draw_objects_[kOnscreenTransparent].insert(
-      draw_objects_[kOnscreenTransparent].begin() + position,
-      object.release());
+void DrawObjectManager::RemoveDraws(uint32_t last_valid_draw_id) {
+  TRACE_EVENT0("cobalt::renderer", "RemoveDraws");
+  RemoveDraws(&onscreen_draws_, last_valid_draw_id);
+  RemoveDraws(&offscreen_draws_, last_valid_draw_id);
+  RemoveDraws(&external_offscreen_draws_, last_valid_draw_id);
+}
+
+void DrawObjectManager::RemoveDraws(std::vector<DrawInfo>* draw_list,
+    uint32_t last_valid_draw_id) {
+  // Objects in the draw list should have ascending draw IDs at this point.
+  auto iter = draw_list->end();
+  for (; iter != draw_list->begin(); --iter) {
+    if ((iter - 1)->draw_id <= last_valid_draw_id) {
+      break;
+    }
+  }
+  if (iter != draw_list->end()) {
+    draw_list->erase(iter, draw_list->end());
+  }
 }
 
 void DrawObjectManager::ExecuteOffscreenRasterize(GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  graphics_state->DisableDepthTest();
-  for (int type = 0; type < kOffscreenCount; ++type) {
-    for (size_t index = 0; index < offscreen_order_[type].size(); ++index) {
-      offscreen_order_[type][index]->ExecuteOffscreenRasterize(graphics_state,
-          program_manager);
+  SortOffscreenDraws(&external_offscreen_draws_);
+
+  // Process draws handled by an external rasterizer.
+  {
+    TRACE_EVENT0("cobalt::renderer", "OffscreenExternalRasterizer");
+    for (auto draw = external_offscreen_draws_.begin();
+         draw != external_offscreen_draws_.end(); ++draw) {
+      draw->draw_object->ExecuteRasterize(graphics_state, program_manager);
     }
+    if (!flush_external_offscreen_draws_.is_null()) {
+      flush_external_offscreen_draws_.Run();
+    }
+  }
+
+  SortOffscreenDraws(&offscreen_draws_);
+  SortOnscreenDraws(&onscreen_draws_);
+
+  // Update the vertex buffer for all draws.
+  {
+    TRACE_EVENT0("cobalt::renderer", "UpdateVertexBuffer");
+    ExecuteUpdateVertexBuffer(graphics_state, program_manager);
+  }
+
+  // Process the native offscreen draws.
+  {
+    TRACE_EVENT0("cobalt::renderer", "OffscreenNativeRasterizer");
+    Rasterize(offscreen_draws_, graphics_state, program_manager);
   }
 }
 
-void DrawObjectManager::ExecuteOnscreenUpdateVertexBuffer(
-    GraphicsState* graphics_state,
-    ShaderProgramManager* program_manager) {
-  for (int type = 0; type < kOnscreenCount; ++type) {
-    for (size_t index = 0; index < draw_objects_[type].size(); ++index) {
-      draw_objects_[type][index]->ExecuteOnscreenUpdateVertexBuffer(
-          graphics_state, program_manager);
-    }
+void DrawObjectManager::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state, ShaderProgramManager* program_manager) {
+  for (auto draw = offscreen_draws_.begin(); draw != offscreen_draws_.end();
+       ++draw) {
+    draw->draw_object->ExecuteUpdateVertexBuffer(
+        graphics_state, program_manager);
   }
+  for (auto draw = onscreen_draws_.begin(); draw != onscreen_draws_.end();
+       ++draw) {
+    draw->draw_object->ExecuteUpdateVertexBuffer(
+        graphics_state, program_manager);
+  }
+  graphics_state->UpdateVertexData();
 }
 
 void DrawObjectManager::ExecuteOnscreenRasterize(GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  graphics_state->EnableDepthTest();
+  Rasterize(onscreen_draws_, graphics_state, program_manager);
+}
 
-  for (int type = 0; type < kOnscreenCount; ++type) {
-    if (type == kOnscreenTransparent) {
-      graphics_state->EnableBlend();
-      graphics_state->DisableDepthWrite();
+void DrawObjectManager::Rasterize(const std::vector<DrawInfo>& draw_list,
+    GraphicsState* graphics_state, ShaderProgramManager* program_manager) {
+  const backend::RenderTarget* current_target = nullptr;
+  bool using_native_rasterizer = true;
 
-      // Transparent objects must be drawn in the order they were added to
-      // maintain correctness.
-      for (size_t index = 0; index < draw_objects_[type].size(); ++index) {
-        draw_objects_[type][index]->ExecuteOnscreenRasterize(graphics_state,
-            program_manager);
+  // Starting from an unknown state.
+  graphics_state->SetDirty();
+
+  for (auto draw = draw_list.begin(); draw != draw_list.end(); ++draw) {
+    bool draw_uses_native_rasterizer = draw->blend_type != kBlendExternal;
+
+    if (draw_uses_native_rasterizer) {
+      if (!using_native_rasterizer) {
+        // Transitioning from external to native rasterizer. Set the native
+        // rasterizer's state as dirty.
+        graphics_state->SetDirty();
+      }
+
+      if (draw->render_target != current_target) {
+        current_target = draw->render_target;
+        graphics_state->BindFramebuffer(current_target);
+        graphics_state->Viewport(0, 0,
+                                 current_target->GetSize().width(),
+                                 current_target->GetSize().height());
+      }
+
+      if (draw->blend_type == kBlendNone) {
+        graphics_state->DisableBlend();
+      } else if (draw->blend_type == kBlendSrcAlpha) {
+        graphics_state->EnableBlend();
+      } else {
+        NOTREACHED() << "Unsupported blend type";
       }
     } else {
-      graphics_state->DisableBlend();
-      graphics_state->EnableDepthWrite();
+      if (using_native_rasterizer) {
+        // Transitioning from native to external rasterizer. Set the external
+        // rasterizer's state as dirty.
+        if (!reset_external_rasterizer_.is_null()) {
+          reset_external_rasterizer_.Run();
+        }
+      }
+    }
 
-      // Opaque objects can be drawn in front-to-back order to take advantage
-      // of z-culling.
-      for (size_t index = draw_objects_[type].size(); index > 0;) {
-        --index;
-        draw_objects_[type][index]->ExecuteOnscreenRasterize(graphics_state,
-            program_manager);
+    draw->draw_object->ExecuteRasterize(graphics_state, program_manager);
+    using_native_rasterizer = draw_uses_native_rasterizer;
+  }
+}
+
+void DrawObjectManager::SortOffscreenDraws(std::vector<DrawInfo>* draw_list) {
+  TRACE_EVENT0("cobalt::renderer", "SortOffscreenDraws");
+
+  // Sort offscreen draws to minimize GPU state changes.
+  for (auto iter = draw_list->begin(); iter != draw_list->end(); ++iter) {
+    for (auto current_draw = iter; current_draw != draw_list->begin();
+         std::swap(*current_draw, *(current_draw - 1)), current_draw--) {
+      auto prev_draw = current_draw - 1;
+
+      // Unlike onscreen draws, offscreen draws should be grouped by
+      // render target.
+      if (prev_draw->render_target > current_draw->render_target) {
+        continue;
+      } else if (prev_draw->render_target < current_draw->render_target) {
+        break;
+      }
+
+      if (prev_draw->draw_bounds.Intersects(current_draw->draw_bounds)) {
+        break;
+      }
+
+      if (prev_draw->draw_type > current_draw->draw_type) {
+        continue;
+      } else if (prev_draw->draw_type < current_draw->draw_type) {
+        break;
+      }
+
+      if (prev_draw->blend_type <= current_draw->blend_type) {
+        break;
+      }
+    }
+  }
+}
+
+void DrawObjectManager::SortOnscreenDraws(std::vector<DrawInfo>* draw_list) {
+  TRACE_EVENT0("cobalt::renderer", "SortOnscreenDraws");
+
+  // Sort onscreen draws to minimize GPU state changes.
+  for (auto iter = draw_list->begin(); iter != draw_list->end(); ++iter) {
+    for (auto current_draw = iter; current_draw != draw_list->begin();
+         std::swap(*current_draw, *(current_draw - 1)), current_draw--) {
+      auto prev_draw = current_draw - 1;
+
+      // Do not sort across different render targets since their contents may
+      // be generated just before consumed by a subsequent draw.
+      if (prev_draw->render_target != current_draw->render_target) {
+        break;
+      }
+
+      if (prev_draw->draw_bounds.Intersects(current_draw->draw_bounds)) {
+        break;
+      }
+
+      // Group native vs. non-native draws together.
+      bool next_uses_same_rasterizer = current_draw + 1 != draw_list->end() &&
+          ((current_draw + 1)->blend_type == kBlendExternal) ==
+          (current_draw->blend_type == kBlendExternal);
+      bool prev_uses_same_rasterizer =
+          (prev_draw->blend_type == kBlendExternal) ==
+          (current_draw->blend_type == kBlendExternal);
+      if (!next_uses_same_rasterizer && !prev_uses_same_rasterizer) {
+        continue;
+      }
+      if (next_uses_same_rasterizer && !prev_uses_same_rasterizer) {
+        break;
+      }
+
+      if (prev_draw->draw_type > current_draw->draw_type) {
+        continue;
+      } else if (prev_draw->draw_type < current_draw->draw_type) {
+        break;
+      }
+
+      if (prev_draw->blend_type <= current_draw->blend_type) {
+        break;
       }
     }
   }
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object_manager.h b/src/cobalt/renderer/rasterizer/egl/draw_object_manager.h
index 30963a0..0c66f88 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object_manager.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object_manager.h
@@ -17,9 +17,12 @@
 
 #include <vector>
 
+#include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/scoped_vector.h"
+#include "cobalt/base/type_id.h"
 #include "cobalt/math/rect_f.h"
+#include "cobalt/renderer/backend/render_target.h"
 #include "cobalt/renderer/rasterizer/egl/draw_object.h"
 #include "cobalt/renderer/rasterizer/egl/graphics_state.h"
 #include "cobalt/renderer/rasterizer/egl/shader_program_manager.h"
@@ -33,62 +36,93 @@
 // objects to minimize GPU state changes.
 class DrawObjectManager {
  public:
-  enum OnscreenType {
-    kOnscreenRectTexture = 0,
-    kOnscreenRectColorTexture,
-    kOnscreenPolyColor,
-    kOnscreenRectShadow,
-    kOnscreenRectShadowBlur,
-    kOnscreenTransparent,
-    kOnscreenCount,
+  enum BlendType {
+    // These draws use an external rasterizer which sets the GPU state.
+    kBlendExternal = 0,
+
+    // These draws use the native rasterizer, and the appropriate state must
+    // be set during execution.
+    kBlendNone,
+    kBlendSrcAlpha,
   };
 
-  // Order offscreen rendering by descending expected pixel area. This helps
-  // make better use of the offscreen texture atlas as smaller requests can
-  // fill in gaps created by the larger requests.
-  enum OffscreenType {
-    kOffscreenSkiaFilter = 0,
-    kOffscreenSkiaShadow,
-    kOffscreenSkiaMultiPlaneImage,
-    kOffscreenSkiaRectRounded,
-    kOffscreenSkiaRectBrush,
-    kOffscreenSkiaRectBorder,
-    kOffscreenSkiaText,
-    kOffscreenCount,
-    kOffscreenNone,     // ExecuteOffscreenRasterize will not be run for these.
-  };
+  DrawObjectManager(const base::Closure& reset_external_rasterizer,
+                    const base::Closure& flush_external_offscreen_draws);
 
-  void AddOpaqueDraw(scoped_ptr<DrawObject> object,
-                     OnscreenType onscreen_type,
-                     OffscreenType offscreen_type);
-  void AddTransparentDraw(scoped_ptr<DrawObject> object,
-                          OnscreenType onscreen_type,
-                          OffscreenType offscreen_type,
-                          const math::RectF& bounds);
+  // Add a draw object that will render to an offscreen render target. There
+  // must be a corresponding draw object added via AddOnscreenDraw() to
+  // render the contents onto the main render target. All offscreen draws are
+  // batched together and executed before any onscreen objects are processed
+  // in order to minimize the cost of switching render targets.
+  // Returns an ID which can be used to remove the queued draw.
+  uint32_t AddOffscreenDraw(scoped_ptr<DrawObject> draw_object,
+      BlendType blend_type, base::TypeId draw_type,
+      const backend::RenderTarget* render_target,
+      const math::RectF& draw_bounds);
+
+  // Add a draw object to be processed when rendering to the main render
+  // target. Although most draws are expected to go to the main render target,
+  // some draws may touch offscreen targets (e.g. when those offscreen targets
+  // are reused during the frame, so their contents must be rasterized just
+  // before being used for the main render target). Switching render targets
+  // has a major negative impact to performance, so it is preferable to avoid
+  // reusing offscreen targets during the frame.
+  // Returns an ID which can be used to remove the queued draw.
+  uint32_t AddOnscreenDraw(scoped_ptr<DrawObject> draw_object,
+      BlendType blend_type, base::TypeId draw_type,
+      const backend::RenderTarget* render_target,
+      const math::RectF& draw_bounds);
+
+  // Remove all queued draws whose ID comes after the given last_valid_draw_id.
+  // Calling RemoveDraws(0) will remove all draws that have been added.
+  void RemoveDraws(uint32_t last_valid_draw_id);
 
   void ExecuteOffscreenRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager);
-  void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
-      ShaderProgramManager* program_manager);
   void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager);
 
  private:
-  struct TransparentObjectInfo {
-    TransparentObjectInfo(OnscreenType onscreen_type,
-                          const math::RectF& object_bounds)
-        : bounds(object_bounds),
-          type(onscreen_type) {}
-    math::RectF bounds;
-    OnscreenType type;
+  struct DrawInfo {
+    DrawInfo(scoped_ptr<DrawObject> in_draw_object,
+             base::TypeId in_draw_type, BlendType in_blend_type,
+             const backend::RenderTarget* in_render_target,
+             const math::RectF& in_draw_bounds, uint32_t in_draw_id)
+        : draw_object(in_draw_object.release()),
+          render_target(in_render_target),
+          draw_bounds(in_draw_bounds),
+          draw_type(in_draw_type),
+          blend_type(in_blend_type),
+          draw_id(in_draw_id) {}
+    std::unique_ptr<DrawObject> draw_object;
+    const backend::RenderTarget* render_target;
+    math::RectF draw_bounds;
+    base::TypeId draw_type;
+    BlendType blend_type;
+    uint32_t draw_id;
   };
 
-  ScopedVector<DrawObject> draw_objects_[kOnscreenCount];
-  std::vector<TransparentObjectInfo> transparent_object_info_;
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager);
 
-  // Manage execution order of objects in the draw_objects_ vectors. This does
-  // not manage destruction of objects.
-  std::vector<DrawObject*> offscreen_order_[kOffscreenCount];
+  void Rasterize(const std::vector<DrawInfo>& draw_list,
+                 GraphicsState* graphics_state,
+                 ShaderProgramManager* program_manager);
+
+  void RemoveDraws(std::vector<DrawInfo>* draw_list,
+                   uint32_t last_valid_draw_id);
+
+  void SortOffscreenDraws(std::vector<DrawInfo>* draw_list);
+  void SortOnscreenDraws(std::vector<DrawInfo>* draw_list);
+
+  base::Closure reset_external_rasterizer_;
+  base::Closure flush_external_offscreen_draws_;
+
+  std::vector<DrawInfo> onscreen_draws_;
+  std::vector<DrawInfo> offscreen_draws_;
+  std::vector<DrawInfo> external_offscreen_draws_;
+
+  uint32_t current_draw_id_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
index dc4c7d1..7fbb6fe 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
@@ -41,7 +41,7 @@
       vertex_buffer_(NULL) {
 }
 
-void DrawPolyColor::ExecuteOnscreenUpdateVertexBuffer(
+void DrawPolyColor::ExecuteUpdateVertexBuffer(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
   vertex_buffer_ = graphics_state->AllocateVertexData(
@@ -50,13 +50,18 @@
                attributes_.size() * sizeof(VertexAttributes));
 }
 
-void DrawPolyColor::ExecuteOnscreenRasterize(
+void DrawPolyColor::ExecuteRasterize(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
   SetupShader(graphics_state, program_manager);
   GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_.size()));
 }
 
+base::TypeId DrawPolyColor::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColor,
+                       ShaderFragmentColor>::GetTypeId();
+}
+
 void DrawPolyColor::SetupShader(GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
   ShaderProgram<ShaderVertexColor,
@@ -71,7 +76,7 @@
   graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
       base_state_.scissor.width(), base_state_.scissor.height());
   graphics_state->VertexAttribPointer(
-      program->GetVertexShader().a_position(), 3, GL_FLOAT, GL_FALSE,
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
       sizeof(VertexAttributes), vertex_buffer_ +
       offsetof(VertexAttributes, position));
   graphics_state->VertexAttribPointer(
@@ -89,7 +94,7 @@
 }
 
 void DrawPolyColor::AddVertex(float x, float y, uint32_t color) {
-  VertexAttributes attribute = { { x, y, base_state_.depth }, color };
+  VertexAttributes attribute = { { x, y }, color };
   attributes_.push_back(attribute);
 }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.h b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.h
index bc549f6..cfce106 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.h
@@ -34,10 +34,11 @@
                 const math::RectF& rect,
                 const render_tree::ColorRGBA& color);
 
-  void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
 
  protected:
   explicit DrawPolyColor(const BaseState& base_state);
@@ -47,7 +48,7 @@
   void AddVertex(float x, float y, uint32_t color);
 
   struct VertexAttributes {
-    float position[3];
+    float position[2];
     uint32_t color;
   };
   std::vector<VertexAttributes> attributes_;
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc
index 2c04709..cc74dad 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc
@@ -28,7 +28,7 @@
 
 namespace {
 struct VertexAttributes {
-  float position[3];
+  float position[2];
   float texcoord[2];
   uint32_t color;
 };
@@ -51,50 +51,22 @@
   graphics_state->ReserveVertexData(4 * sizeof(VertexAttributes));
 }
 
-DrawRectColorTexture::DrawRectColorTexture(GraphicsState* graphics_state,
-    const BaseState& base_state,
-    const math::RectF& rect, const render_tree::ColorRGBA& color,
-    const backend::TextureEGL* texture,
-    const math::Matrix3F& texcoord_transform,
-    const base::Closure& draw_offscreen,
-    const base::Closure& draw_onscreen)
-    : DrawObject(base_state),
-      texcoord_transform_(texcoord_transform),
-      rect_(rect),
-      texture_(texture),
-      draw_offscreen_(draw_offscreen),
-      draw_onscreen_(draw_onscreen),
-      vertex_buffer_(NULL),
-      clamp_texcoords_(false),
-      tile_texture_(false) {
-  color_ = GetGLRGBA(color * base_state_.opacity);
-  graphics_state->ReserveVertexData(4 * sizeof(VertexAttributes));
-}
-
-void DrawRectColorTexture::ExecuteOffscreenRasterize(
-    GraphicsState* graphics_state,
-    ShaderProgramManager* program_manager) {
-  if (!draw_offscreen_.is_null()) {
-    draw_offscreen_.Run();
-  }
-}
-
-void DrawRectColorTexture::ExecuteOnscreenUpdateVertexBuffer(
+void DrawRectColorTexture::ExecuteUpdateVertexBuffer(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
   VertexAttributes attributes[4] = {
-    { { rect_.x(), rect_.bottom(), base_state_.depth },      // uv = (0,1)
+    { { rect_.x(), rect_.bottom() },      // uv = (0,1)
       { texcoord_transform_(0, 1) + texcoord_transform_(0, 2),
         texcoord_transform_(1, 1) + texcoord_transform_(1, 2) }, color_ },
-    { { rect_.right(), rect_.bottom(), base_state_.depth },  // uv = (1,1)
+    { { rect_.right(), rect_.bottom() },  // uv = (1,1)
       { texcoord_transform_(0, 0) + texcoord_transform_(0, 1) +
           texcoord_transform_(0, 2),
         texcoord_transform_(1, 0) + texcoord_transform_(1, 1) +
           texcoord_transform_(1, 2) }, color_ },
-    { { rect_.right(), rect_.y(), base_state_.depth },       // uv = (1,0)
+    { { rect_.right(), rect_.y() },       // uv = (1,0)
       { texcoord_transform_(0, 0) + texcoord_transform_(0, 2),
         texcoord_transform_(1, 0) + texcoord_transform_(1, 2) }, color_ },
-    { { rect_.x(), rect_.y(), base_state_.depth },           // uv = (0,0)
+    { { rect_.x(), rect_.y() },           // uv = (0,0)
       { texcoord_transform_(0, 2), texcoord_transform_(1, 2) }, color_ },
   };
   COMPILE_ASSERT(sizeof(attributes) == 4 * sizeof(VertexAttributes),
@@ -137,13 +109,9 @@
   }
 }
 
-void DrawRectColorTexture::ExecuteOnscreenRasterize(
+void DrawRectColorTexture::ExecuteRasterize(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  if (!draw_onscreen_.is_null()) {
-    draw_onscreen_.Run();
-  }
-
   ShaderProgram<ShaderVertexColorTexcoord,
                 ShaderFragmentColorTexcoord>* program;
   program_manager->GetProgram(&program);
@@ -156,7 +124,7 @@
   graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
       base_state_.scissor.width(), base_state_.scissor.height());
   graphics_state->VertexAttribPointer(
-      program->GetVertexShader().a_position(), 3, GL_FLOAT, GL_FALSE,
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
       sizeof(VertexAttributes), vertex_buffer_ +
       offsetof(VertexAttributes, position));
   graphics_state->VertexAttribPointer(
@@ -187,6 +155,11 @@
   }
 }
 
+base::TypeId DrawRectColorTexture::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColorTexcoord,
+                       ShaderFragmentColorTexcoord>::GetTypeId();
+}
+
 }  // namespace egl
 }  // namespace rasterizer
 }  // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h
index 11cec54..1cc0862 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h
@@ -38,21 +38,11 @@
                        const math::Matrix3F& texcoord_transform,
                        bool clamp_texcoords);
 
-  DrawRectColorTexture(GraphicsState* graphics_state,
-                       const BaseState& base_state,
-                       const math::RectF& rect,
-                       const render_tree::ColorRGBA& color,
-                       const backend::TextureEGL* texture,
-                       const math::Matrix3F& texcoord_transform,
-                       const base::Closure& draw_offscreen,
-                       const base::Closure& draw_onscreen);
-
-  void ExecuteOffscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
+  void ExecuteRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
-      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
 
  private:
   math::Matrix3F texcoord_transform_;
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc
index bf27f37..a4b3e4b 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc
@@ -21,6 +21,7 @@
 #include "cobalt/renderer/backend/egl/utils.h"
 #include "egl/generated_shader_impl.h"
 #include "starboard/log.h"
+#include "starboard/memory.h"
 
 namespace cobalt {
 namespace renderer {
@@ -28,8 +29,6 @@
 namespace egl {
 
 namespace {
-const float kEpsilon = 0.001f;
-
 size_t MaxVertsNeededForAlignedGradient(
     const render_tree::LinearGradientBrush& brush) {
   // Triangle strip for an axis-aligned rectangle. Two vertices are required
@@ -42,15 +41,16 @@
     const BaseState& base_state,
     const math::RectF& rect,
     const render_tree::LinearGradientBrush& brush)
-    : DrawDepthStencil(base_state),
-      first_rect_vert_(0),
-      gradient_angle_(0.0f) {
-  if (std::abs(brush.dest().y() - brush.source().y()) < kEpsilon) {
-    attributes_.reserve(MaxVertsNeededForAlignedGradient(brush));
+    : DrawObject(base_state),
+      transform_(math::Matrix3F::Identity()),
+      include_scissor_(rect),
+      vertex_buffer_(nullptr) {
+  attributes_.reserve(MaxVertsNeededForAlignedGradient(brush));
+
+  if (brush.IsHorizontal()) {
     AddRectWithHorizontalGradient(
         rect, brush.source(), brush.dest(), brush.color_stops());
-  } else if (std::abs(brush.dest().x() - brush.source().x()) < kEpsilon) {
-    attributes_.reserve(MaxVertsNeededForAlignedGradient(brush));
+  } else if (brush.IsVertical()) {
     AddRectWithVerticalGradient(
         rect, brush.source(), brush.dest(), brush.color_stops());
   } else {
@@ -60,44 +60,65 @@
       attributes_.size() * sizeof(VertexAttributes));
 }
 
-void DrawRectLinearGradient::ExecuteOnscreenRasterize(
+void DrawRectLinearGradient::ExecuteUpdateVertexBuffer(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  SetupShader(graphics_state, program_manager);
+  vertex_buffer_ = graphics_state->AllocateVertexData(
+      attributes_.size() * sizeof(VertexAttributes));
+  SbMemoryCopy(vertex_buffer_, &attributes_[0],
+               attributes_.size() * sizeof(VertexAttributes));
+}
 
-  if (first_rect_vert_ > 0) {
-    // Draw using stencil.
-    DrawStencil(graphics_state);
+void DrawRectLinearGradient::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  ShaderProgram<ShaderVertexColorOffset,
+                ShaderFragmentColorInclude>* program;
+  program_manager->GetProgram(&program);
+  graphics_state->UseProgram(program->GetHandle());
+  graphics_state->UpdateClipAdjustment(
+      program->GetVertexShader().u_clip_adjustment());
+  graphics_state->UpdateTransformMatrix(
+      program->GetVertexShader().u_view_matrix(),
+      base_state_.transform);
+  graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+      base_state_.scissor.width(), base_state_.scissor.height());
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, position));
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_color(), 4, GL_UNSIGNED_BYTE, GL_TRUE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, color));
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, offset));
+  graphics_state->VertexAttribFinish();
 
-    // Update the transform for the shader to rotate the horizontal gradient
-    // into the desired angled gradient.
-    ShaderProgram<ShaderVertexColor,
-                  ShaderFragmentColor>* program;
-    program_manager->GetProgram(&program);
-    SB_DCHECK(graphics_state->GetProgram() == program->GetHandle());
-    graphics_state->UpdateTransformMatrix(
-        program->GetVertexShader().u_view_matrix(),
-        base_state_.transform * math::RotateMatrix(gradient_angle_));
+  float include[4] = {
+    include_scissor_.x(),
+    include_scissor_.y(),
+    include_scissor_.right(),
+    include_scissor_.bottom()
+  };
+  GL_CALL(glUniform4fv(program->GetFragmentShader().u_include(), 1, include));
 
-    GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, first_rect_vert_,
-        attributes_.size() - first_rect_vert_));
-    UndoStencilState(graphics_state);
-  } else {
-    GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_.size()));
-  }
+  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_.size()));
+}
+
+base::TypeId DrawRectLinearGradient::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColorOffset,
+                       ShaderFragmentColorInclude>::GetTypeId();
 }
 
 void DrawRectLinearGradient::AddRectWithHorizontalGradient(
     const math::RectF& rect, const math::PointF& source,
     const math::PointF& dest, const render_tree::ColorStopList& color_stops) {
-  SB_DCHECK(source.x() >= rect.x() - kEpsilon &&
-            source.x() <= rect.right() + kEpsilon &&
-            dest.x() >= rect.x() - kEpsilon &&
-            dest.x() <= rect.right() + kEpsilon);
-
   size_t num_colors = color_stops.size();
-  float start = std::max(rect.x(), std::min(source.x(), rect.right()));
-  float length = std::max(rect.x(), std::min(dest.x(), rect.right())) - start;
+  float start = source.x();
+  float length = dest.x() - source.x();
   float position = color_stops[0].position * length + start;
   uint32_t color32 = GetGLColor(color_stops[0]);
 
@@ -133,14 +154,9 @@
 void DrawRectLinearGradient::AddRectWithVerticalGradient(
     const math::RectF& rect, const math::PointF& source,
     const math::PointF& dest, const render_tree::ColorStopList& color_stops) {
-  SB_DCHECK(source.y() >= rect.y() - kEpsilon &&
-            source.y() <= rect.bottom() + kEpsilon &&
-            dest.y() >= rect.y() - kEpsilon &&
-            dest.y() <= rect.bottom() + kEpsilon);
-
   size_t num_colors = color_stops.size();
-  float start = std::max(rect.y(), std::min(source.y(), rect.bottom()));
-  float length = std::max(rect.y(), std::min(dest.y(), rect.bottom())) - start;
+  float start = source.y();
+  float length = dest.y() - source.y();
   float position = color_stops[0].position * length + start;
   uint32_t color32 = GetGLColor(color_stops[0]);
 
@@ -179,34 +195,25 @@
   // draw a rect with horizontal gradient that will be rotated to cover the
   // desired rect with angled gradient. This is not as efficient as calculating
   // a triangle strip to describe the rect with angled gradient, but is simpler.
-  attributes_.reserve(MaxVertsNeededForStencil() +
-                      MaxVertsNeededForAlignedGradient(brush));
-  AddStencil(rect, math::RectF());
 
   // Calculate the angle needed to rotate a horizontal gradient into the
   // angled gradient. (Flip vertical distance because the input coordinate
   // system's origin is at the top-left.)
-  gradient_angle_ = atan2(brush.source().y() - brush.dest().y(),
-                          brush.dest().x() - brush.source().x());
+  float gradient_angle = atan2(brush.source().y() - brush.dest().y(),
+                               brush.dest().x() - brush.source().x());
+  transform_ = math::RotateMatrix(gradient_angle);
 
   // Calculate the endpoints for the horizontal gradient that, when rotated
-  // by gradient_angle_, will turn into the original angled gradient.
-  math::Matrix3F inverse_transform = math::RotateMatrix(-gradient_angle_);
+  // by gradient_angle, will turn into the original angled gradient.
+  math::Matrix3F inverse_transform = math::RotateMatrix(-gradient_angle);
   math::PointF mapped_source(inverse_transform * brush.source());
   math::PointF mapped_dest(inverse_transform * brush.dest());
   SB_DCHECK(mapped_source.x() <= mapped_dest.x());
 
-  // Calculate a rect large enough that, when rotated by gradient_angle_, it
+  // Calculate a rect large enough that, when rotated by gradient_angle, it
   // will contain the original rect.
   math::RectF mapped_rect = inverse_transform.MapRect(rect);
 
-  // Adjust the mapped_rect to include the gradient endpoints if needed.
-  float left = std::min(mapped_rect.x(), mapped_source.x());
-  float right = std::max(mapped_rect.right(), mapped_dest.x());
-  mapped_rect.SetRect(left, mapped_rect.y(),
-                      right - left, mapped_rect.height());
-
-  first_rect_vert_ = attributes_.size();
   AddRectWithHorizontalGradient(mapped_rect, mapped_source, mapped_dest,
                                 brush.color_stops());
 }
@@ -219,6 +226,16 @@
                    alpha);
 }
 
+void DrawRectLinearGradient::AddVertex(float x, float y, uint32_t color) {
+  math::PointF position = transform_ * math::PointF(x, y);
+  VertexAttributes attributes = {
+    { position.x(), position.y() },
+    { position.x(), position.y() },
+    color
+  };
+  attributes_.push_back(attributes);
+}
+
 }  // namespace egl
 }  // namespace rasterizer
 }  // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h
index e138460..6d54e53 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h
@@ -15,26 +15,33 @@
 #ifndef COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_LINEAR_GRADIENT_H_
 #define COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_LINEAR_GRADIENT_H_
 
+#include <vector>
+
+#include "cobalt/math/matrix3_f.h"
 #include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/brush.h"
-#include "cobalt/renderer/rasterizer/egl/draw_depth_stencil.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
 
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
 namespace egl {
 
-// Handles drawing a rectangle with a linear color gradient. This may use a
-// depth stencil, so it must be processed with other transparent draws.
-class DrawRectLinearGradient : public DrawDepthStencil {
+// Handles drawing a rectangle with a linear color gradient. This may use
+// transparency to mask out unwanted pixels, so it must be processed with other
+// transparent draws.
+class DrawRectLinearGradient : public DrawObject {
  public:
   DrawRectLinearGradient(GraphicsState* graphics_state,
                          const BaseState& base_state,
                          const math::RectF& rect,
                          const render_tree::LinearGradientBrush& brush);
 
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
 
  private:
   void AddRectWithHorizontalGradient(
@@ -46,14 +53,23 @@
   void AddRectWithAngledGradient(
       const math::RectF& rect, const render_tree::LinearGradientBrush& brush);
   uint32_t GetGLColor(const render_tree::ColorStop& color_stop);
+  void AddVertex(float x, float y, uint32_t color);
 
-  // Index of the first vertex for the rect with gradient.
-  size_t first_rect_vert_;
+  // For angled gradients, this is the rotation needed to transform a
+  // horizontal gradient into the desired rect with angled gradient. The
+  // include scissor will be used to ensure that only the desired pixels
+  // are modified.
+  math::Matrix3F transform_;
+  math::RectF include_scissor_;
 
-  // For angled gradients, this is the rotation angle needed to transform the
-  // submitted rect with horizontal gradient into the desired rect with angled
-  // gradient.
-  float gradient_angle_;
+  struct VertexAttributes {
+    float position[2];
+    float offset[2];
+    uint32_t color;
+  };
+  std::vector<VertexAttributes> attributes_;
+
+  uint8_t* vertex_buffer_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
index 6090782..e03dc0f 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
@@ -27,20 +27,23 @@
 namespace rasterizer {
 namespace egl {
 
+namespace {
+const int kVertexCount = 10;
+}
+
 DrawRectShadowBlur::DrawRectShadowBlur(GraphicsState* graphics_state,
     const BaseState& base_state, const math::RectF& inner_rect,
     const math::RectF& outer_rect, const math::RectF& blur_edge,
-    const render_tree::ColorRGBA& color, const math::RectF& exclude_scissor,
-    float blur_sigma, bool inset)
+    const render_tree::ColorRGBA& color, float blur_sigma, bool inset)
     : DrawObject(base_state),
-      draw_stencil_(graphics_state, base_state, outer_rect, exclude_scissor),
+      inner_rect_(inner_rect),
+      outer_rect_(outer_rect),
       blur_center_(blur_edge.CenterPoint()),
       // The sigma scale is used to transform pixel distances to sigma-relative
       // distances. The 0.5 multiplier is used to match skia's implementation.
       blur_sigma_scale_(0.5f / blur_sigma),
       vertex_buffer_(NULL) {
   float color_scale = 1.0f;
-  attributes_.reserve(10);
 
   // The blur radius dictates the distance from blur center at which the
   // shadow should be about 50% opacity of the shadow color. This is expressed
@@ -63,46 +66,43 @@
     const float size_scale = 1.0f / (1.5f * blur_sigma);
     color_scale = std::min(blur_edge.width() * size_scale, 1.0f) *
                   std::min(blur_edge.height() * size_scale, 1.0f);
+
+    // Ensure the outer_rect contains the inner_rect to avoid overlapping
+    // triangles in the draw call. The fragment shader will properly fade out
+    // the extra pixels.
+    outer_rect_.Union(inner_rect_);
   }
 
+  color_ = GetGLRGBA(color * color_scale * base_state_.opacity);
+  graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
+}
+
+void DrawRectShadowBlur::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
   // Add a triangle-strip to draw the area between outer_rect and inner_rect.
   // This is the area which the shadow covers.
-  uint32_t color32 = GetGLRGBA(color * color_scale * base_state_.opacity);
-  AddVertex(outer_rect.x(), outer_rect.y(), color32);
-  AddVertex(inner_rect.x(), inner_rect.y(), color32);
-  AddVertex(outer_rect.right(), outer_rect.y(), color32);
-  AddVertex(inner_rect.right(), inner_rect.y(), color32);
-  AddVertex(outer_rect.right(), outer_rect.bottom(), color32);
-  AddVertex(inner_rect.right(), inner_rect.bottom(), color32);
-  AddVertex(outer_rect.x(), outer_rect.bottom(), color32);
-  AddVertex(inner_rect.x(), inner_rect.bottom(), color32);
-  AddVertex(outer_rect.x(), outer_rect.y(), color32);
-  AddVertex(inner_rect.x(), inner_rect.y(), color32);
+  VertexAttributes attributes[kVertexCount];
+  SetVertex(&attributes[0], outer_rect_.x(), outer_rect_.y());
+  SetVertex(&attributes[1], inner_rect_.x(), inner_rect_.y());
+  SetVertex(&attributes[2], outer_rect_.right(), outer_rect_.y());
+  SetVertex(&attributes[3], inner_rect_.right(), inner_rect_.y());
+  SetVertex(&attributes[4], outer_rect_.right(), outer_rect_.bottom());
+  SetVertex(&attributes[5], inner_rect_.right(), inner_rect_.bottom());
+  SetVertex(&attributes[6], outer_rect_.x(), outer_rect_.bottom());
+  SetVertex(&attributes[7], inner_rect_.x(), inner_rect_.bottom());
+  SetVertex(&attributes[8], outer_rect_.x(), outer_rect_.y());
+  SetVertex(&attributes[9], inner_rect_.x(), inner_rect_.y());
 
-  graphics_state->ReserveVertexData(
-      attributes_.size() * sizeof(VertexAttributes));
+  vertex_buffer_ = graphics_state->AllocateVertexData(sizeof(attributes));
+  SbMemoryCopy(vertex_buffer_, attributes, sizeof(attributes));
 }
 
-void DrawRectShadowBlur::ExecuteOnscreenUpdateVertexBuffer(
+void DrawRectShadowBlur::ExecuteRasterize(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  draw_stencil_.ExecuteOnscreenUpdateVertexBuffer(
-      graphics_state, program_manager);
-  vertex_buffer_ = graphics_state->AllocateVertexData(
-      attributes_.size() * sizeof(VertexAttributes));
-  SbMemoryCopy(vertex_buffer_, &attributes_[0],
-               attributes_.size() * sizeof(VertexAttributes));
-}
-
-void DrawRectShadowBlur::ExecuteOnscreenRasterize(
-    GraphicsState* graphics_state,
-    ShaderProgramManager* program_manager) {
-  // Create a stencil for the pixels to be modified.
-  draw_stencil_.ExecuteOnscreenRasterize(
-      graphics_state, program_manager);
-
-  // Draw the blurred shadow in the stencilled area.
-  ShaderProgram<ShaderVertexColorBlur,
+  // Draw the blurred shadow.
+  ShaderProgram<ShaderVertexColorOffset,
                 ShaderFragmentColorBlur>* program;
   program_manager->GetProgram(&program);
   graphics_state->UseProgram(program->GetHandle());
@@ -114,7 +114,7 @@
   graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
       base_state_.scissor.width(), base_state_.scissor.height());
   graphics_state->VertexAttribPointer(
-      program->GetVertexShader().a_position(), 3, GL_FLOAT, GL_FALSE,
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
       sizeof(VertexAttributes), vertex_buffer_ +
       offsetof(VertexAttributes, position));
   graphics_state->VertexAttribPointer(
@@ -122,25 +122,32 @@
       sizeof(VertexAttributes), vertex_buffer_ +
       offsetof(VertexAttributes, color));
   graphics_state->VertexAttribPointer(
-      program->GetVertexShader().a_blur_position(), 2, GL_FLOAT, GL_FALSE,
+      program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE,
       sizeof(VertexAttributes), vertex_buffer_ +
-      offsetof(VertexAttributes, blur_position));
+      offsetof(VertexAttributes, offset));
   graphics_state->VertexAttribFinish();
   GL_CALL(glUniform2fv(program->GetFragmentShader().u_blur_radius(), 1,
       blur_radius_));
   GL_CALL(glUniform2fv(program->GetFragmentShader().u_scale_add(), 1,
       blur_scale_add_));
 
-  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, attributes_.size()));
-  draw_stencil_.UndoStencilState(graphics_state);
+  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, kVertexCount));
 }
 
-void DrawRectShadowBlur::AddVertex(float x, float y, uint32_t color) {
-  float blur_x = (x - blur_center_.x()) * blur_sigma_scale_;
-  float blur_y = (y - blur_center_.y()) * blur_sigma_scale_;
-  VertexAttributes attribute = { { x, y, base_state_.depth }, color,
-                                 { blur_x, blur_y } };
-  attributes_.push_back(attribute);
+base::TypeId DrawRectShadowBlur::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColorOffset,
+                       ShaderFragmentColorBlur>::GetTypeId();
+}
+
+void DrawRectShadowBlur::SetVertex(VertexAttributes* vertex,
+                                   float x, float y) {
+  vertex->position[0] = x;
+  vertex->position[1] = y;
+
+  vertex->offset[0] = (x - blur_center_.x()) * blur_sigma_scale_;
+  vertex->offset[1] = (y - blur_center_.y()) * blur_sigma_scale_;
+
+  vertex->color = color_;
 }
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
index 4d17b25..0add5be 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
@@ -19,7 +19,6 @@
 
 #include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/color_rgba.h"
-#include "cobalt/renderer/rasterizer/egl/draw_depth_stencil.h"
 #include "cobalt/renderer/rasterizer/egl/draw_object.h"
 
 namespace cobalt {
@@ -34,7 +33,7 @@
 //   |   | Box shadow "spread" region  |   |
 //   |   |   +---------------------+   |   |
 //   |   |   | Box shadow rect     |   |   |
-//   |   |   | (exclude scissor)   |   |   |
+//   |   |   | (exclude geometry)  |   |   |
 //   |   |   +---------------------+   |   |
 //   |   |                             |   |
 //   |   +-----------------------------+   |
@@ -44,8 +43,7 @@
 // "spread" region.
 
 // Handles drawing a box shadow with blur. This uses a gaussian kernel to fade
-// the "blur" region. A stencil is used to ensure only the desired pixels are
-// touched.
+// the "blur" region.
 //
 // This uses a shader to mimic skia's SkBlurMask.cpp.
 // See also http://stereopsis.com/shadowrect/ as reference for the formula
@@ -63,32 +61,30 @@
                      const math::RectF& outer_rect,
                      const math::RectF& blur_edge,
                      const render_tree::ColorRGBA& color,
-                     const math::RectF& exclude_scissor,
                      float blur_sigma, bool inset);
 
-  void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
 
  private:
-  void AddVertex(float x, float y, uint32_t color);
-
   struct VertexAttributes {
-    float position[3];
+    float position[2];
+    float offset[2];          // Expressed in terms of blur_sigma from center.
     uint32_t color;
-    float blur_position[2];   // Expressed in terms of blur_sigma from center.
   };
 
-  DrawDepthStencil draw_stencil_;
-  std::vector<VertexAttributes> attributes_;
+  void SetVertex(VertexAttributes* vertex, float x, float y);
+
+  math::RectF inner_rect_;
+  math::RectF outer_rect_;
   math::PointF blur_center_;
   float blur_radius_[2];      // Expressed in terms of blur_sigma.
   float blur_scale_add_[2];
-
-  // The sigma scale is used to transform pixel distances to sigma-relative
-  // distances.
-  float blur_sigma_scale_;
+  float blur_sigma_scale_;    // Used to express distances as sigma-relative.
+  uint32_t color_;
 
   uint8_t* vertex_buffer_;
 };
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
index 8f61e32..1a4e597 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
@@ -17,48 +17,103 @@
 #include <GLES2/gl2.h>
 
 #include "cobalt/renderer/backend/egl/utils.h"
-#include "starboard/log.h"
+#include "egl/generated_shader_impl.h"
+#include "starboard/memory.h"
 
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
 namespace egl {
 
+namespace {
+const int kVertexCount = 10;
+}
+
 DrawRectShadowSpread::DrawRectShadowSpread(GraphicsState* graphics_state,
     const BaseState& base_state, const math::RectF& inner_rect,
     const math::RectF& outer_rect, const render_tree::ColorRGBA& color,
-    const math::RectF& include_scissor, const math::RectF& exclude_scissor)
-    : DrawDepthStencil(base_state) {
-  // Reserve space for the box shadow's spread and possible scissor rects.
-  attributes_.reserve(10 + MaxVertsNeededForStencil());
-
-  // Draw the box shadow's spread. This is a triangle strip covering the area
-  // between outer rect and inner rect.
-  uint32_t color32 = GetGLRGBA(color * base_state_.opacity);
-  AddVertex(outer_rect.x(), outer_rect.y(), color32);
-  AddVertex(inner_rect.x(), inner_rect.y(), color32);
-  AddVertex(outer_rect.right(), outer_rect.y(), color32);
-  AddVertex(inner_rect.right(), inner_rect.y(), color32);
-  AddVertex(outer_rect.right(), outer_rect.bottom(), color32);
-  AddVertex(inner_rect.right(), inner_rect.bottom(), color32);
-  AddVertex(outer_rect.x(), outer_rect.bottom(), color32);
-  AddVertex(inner_rect.x(), inner_rect.bottom(), color32);
-  AddVertex(outer_rect.x(), outer_rect.y(), color32);
-  AddVertex(inner_rect.x(), inner_rect.y(), color32);
-
-  AddStencil(include_scissor, exclude_scissor);
-
-  graphics_state->ReserveVertexData(
-      attributes_.size() * sizeof(VertexAttributes));
+    const math::RectF& include_scissor)
+    : DrawObject(base_state),
+      inner_rect_(inner_rect),
+      outer_rect_(outer_rect),
+      include_scissor_(include_scissor),
+      vertex_buffer_(nullptr) {
+  color_ = GetGLRGBA(color * base_state_.opacity);
+  graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
 }
 
-void DrawRectShadowSpread::ExecuteOnscreenRasterize(
+void DrawRectShadowSpread::ExecuteUpdateVertexBuffer(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  SetupShader(graphics_state, program_manager);
-  DrawStencil(graphics_state);
-  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 10));
-  UndoStencilState(graphics_state);
+  // Draw the box shadow's spread. This is a triangle strip covering the area
+  // between outer rect and inner rect.
+  VertexAttributes attributes[kVertexCount];
+  SetVertex(&attributes[0], outer_rect_.x(), outer_rect_.y());
+  SetVertex(&attributes[1], inner_rect_.x(), inner_rect_.y());
+  SetVertex(&attributes[2], outer_rect_.right(), outer_rect_.y());
+  SetVertex(&attributes[3], inner_rect_.right(), inner_rect_.y());
+  SetVertex(&attributes[4], outer_rect_.right(), outer_rect_.bottom());
+  SetVertex(&attributes[5], inner_rect_.right(), inner_rect_.bottom());
+  SetVertex(&attributes[6], outer_rect_.x(), outer_rect_.bottom());
+  SetVertex(&attributes[7], inner_rect_.x(), inner_rect_.bottom());
+  SetVertex(&attributes[8], outer_rect_.x(), outer_rect_.y());
+  SetVertex(&attributes[9], inner_rect_.x(), inner_rect_.y());
+
+  vertex_buffer_ = graphics_state->AllocateVertexData(sizeof(attributes));
+  SbMemoryCopy(vertex_buffer_, attributes, sizeof(attributes));
+}
+
+void DrawRectShadowSpread::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  ShaderProgram<ShaderVertexColorOffset,
+                ShaderFragmentColorInclude>* program;
+  program_manager->GetProgram(&program);
+  graphics_state->UseProgram(program->GetHandle());
+  graphics_state->UpdateClipAdjustment(
+      program->GetVertexShader().u_clip_adjustment());
+  graphics_state->UpdateTransformMatrix(
+      program->GetVertexShader().u_view_matrix(),
+      base_state_.transform);
+  graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
+      base_state_.scissor.width(), base_state_.scissor.height());
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, position));
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_color(), 4, GL_UNSIGNED_BYTE, GL_TRUE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, color));
+  graphics_state->VertexAttribPointer(
+      program->GetVertexShader().a_offset(), 2, GL_FLOAT, GL_FALSE,
+      sizeof(VertexAttributes), vertex_buffer_ +
+      offsetof(VertexAttributes, offset));
+  graphics_state->VertexAttribFinish();
+
+  float include[4] = {
+    include_scissor_.x(),
+    include_scissor_.y(),
+    include_scissor_.right(),
+    include_scissor_.bottom()
+  };
+  GL_CALL(glUniform4fv(program->GetFragmentShader().u_include(), 1, include));
+
+  GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, kVertexCount));
+}
+
+base::TypeId DrawRectShadowSpread::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColorOffset,
+                       ShaderFragmentColorInclude>::GetTypeId();
+}
+
+void DrawRectShadowSpread::SetVertex(VertexAttributes* vertex,
+                                     float x, float y) {
+  vertex->position[0] = x;
+  vertex->position[1] = y;
+  vertex->offset[0] = x;
+  vertex->offset[1] = y;
+  vertex->color = color_;
 }
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
index 28d1cef..0621a45 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h
@@ -19,7 +19,7 @@
 
 #include "cobalt/math/rect_f.h"
 #include "cobalt/render_tree/color_rgba.h"
-#include "cobalt/renderer/rasterizer/egl/draw_depth_stencil.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
 
 namespace cobalt {
 namespace renderer {
@@ -33,19 +33,16 @@
 //   |   | Box shadow "spread" region  |   |
 //   |   |   +---------------------+   |   |
 //   |   |   | Box shadow rect     |   |   |
-//   |   |   | (exclude scissor)   |   |   |
+//   |   |   | (exclude geometry)  |   |   |
 //   |   |   +---------------------+   |   |
 //   |   |                             |   |
 //   |   +-----------------------------+   |
 //   | (include scissor)                   |
 //   +-------------------------------------+
-// When an offset is used for the shadow, the include and exclude scissors
-// play a critical role.
 
-// Handles drawing the solid "spread" portion of a box shadow. This also
-// creates a stencil, using the depth buffer, for the pixels inside
-// |include_scissor| excluding those in |exclude_scissor|.
-class DrawRectShadowSpread : public DrawDepthStencil {
+// Handles drawing the solid "spread" portion of a box shadow. The
+// |include_scissor| specifies which pixels can be touched.
+class DrawRectShadowSpread : public DrawObject {
  public:
   // Fill the area between |inner_rect| and |outer_rect| with the specified
   // color.
@@ -54,11 +51,29 @@
                        const math::RectF& inner_rect,
                        const math::RectF& outer_rect,
                        const render_tree::ColorRGBA& color,
-                       const math::RectF& include_scissor,
-                       const math::RectF& exclude_scissor);
+                       const math::RectF& include_scissor);
 
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
+
+ private:
+  struct VertexAttributes {
+    float position[2];
+    float offset[2];
+    uint32_t color;
+  };
+
+  void SetVertex(VertexAttributes* vertex, float x, float y);
+
+  math::RectF inner_rect_;
+  math::RectF outer_rect_;
+  math::RectF include_scissor_;
+  uint32_t color_;
+
+  uint8_t* vertex_buffer_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.cc
index 107f103..c5c0431 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.cc
@@ -28,7 +28,7 @@
 
 namespace {
 struct VertexAttributes {
-  float position[3];
+  float position[2];
   float texcoord[2];
 };
 }  // namespace
@@ -47,48 +47,22 @@
   graphics_state->ReserveVertexData(4 * sizeof(VertexAttributes));
 }
 
-DrawRectTexture::DrawRectTexture(GraphicsState* graphics_state,
-    const BaseState& base_state,
-    const math::RectF& rect,
-    const backend::TextureEGL* texture,
-    const math::Matrix3F& texcoord_transform,
-    const base::Closure& draw_offscreen,
-    const base::Closure& draw_onscreen)
-    : DrawObject(base_state),
-      texcoord_transform_(texcoord_transform),
-      rect_(rect),
-      texture_(texture),
-      draw_offscreen_(draw_offscreen),
-      draw_onscreen_(draw_onscreen),
-      vertex_buffer_(NULL),
-      tile_texture_(false) {
-  graphics_state->ReserveVertexData(4 * sizeof(VertexAttributes));
-}
-
-void DrawRectTexture::ExecuteOffscreenRasterize(
-    GraphicsState* graphics_state,
-    ShaderProgramManager* program_manager) {
-  if (!draw_offscreen_.is_null()) {
-    draw_offscreen_.Run();
-  }
-}
-
-void DrawRectTexture::ExecuteOnscreenUpdateVertexBuffer(
+void DrawRectTexture::ExecuteUpdateVertexBuffer(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
   VertexAttributes attributes[4] = {
-    { { rect_.x(), rect_.bottom(), base_state_.depth },      // uv = (0,1)
+    { { rect_.x(), rect_.bottom() },      // uv = (0,1)
       { texcoord_transform_(0, 1) + texcoord_transform_(0, 2),
         texcoord_transform_(1, 1) + texcoord_transform_(1, 2) } },
-    { { rect_.right(), rect_.bottom(), base_state_.depth },  // uv = (1,1)
+    { { rect_.right(), rect_.bottom() },  // uv = (1,1)
       { texcoord_transform_(0, 0) + texcoord_transform_(0, 1) +
           texcoord_transform_(0, 2),
         texcoord_transform_(1, 0) + texcoord_transform_(1, 1) +
           texcoord_transform_(1, 2) } },
-    { { rect_.right(), rect_.y(), base_state_.depth },       // uv = (1,0)
+    { { rect_.right(), rect_.y() },       // uv = (1,0)
       { texcoord_transform_(0, 0) + texcoord_transform_(0, 2),
         texcoord_transform_(1, 0) + texcoord_transform_(1, 2) } },
-    { { rect_.x(), rect_.y(), base_state_.depth },           // uv = (0,0)
+    { { rect_.x(), rect_.y() },           // uv = (0,0)
       { texcoord_transform_(0, 2), texcoord_transform_(1, 2) } },
   };
   COMPILE_ASSERT(sizeof(attributes) == 4 * sizeof(VertexAttributes),
@@ -104,13 +78,9 @@
   }
 }
 
-void DrawRectTexture::ExecuteOnscreenRasterize(
+void DrawRectTexture::ExecuteRasterize(
     GraphicsState* graphics_state,
     ShaderProgramManager* program_manager) {
-  if (!draw_onscreen_.is_null()) {
-    draw_onscreen_.Run();
-  }
-
   ShaderProgram<ShaderVertexTexcoord,
                 ShaderFragmentTexcoord>* program;
   program_manager->GetProgram(&program);
@@ -123,7 +93,7 @@
   graphics_state->Scissor(base_state_.scissor.x(), base_state_.scissor.y(),
       base_state_.scissor.width(), base_state_.scissor.height());
   graphics_state->VertexAttribPointer(
-      program->GetVertexShader().a_position(), 3, GL_FLOAT, GL_FALSE,
+      program->GetVertexShader().a_position(), 2, GL_FLOAT, GL_FALSE,
       sizeof(VertexAttributes), vertex_buffer_ +
       offsetof(VertexAttributes, position));
   graphics_state->VertexAttribPointer(
@@ -148,6 +118,11 @@
   }
 }
 
+base::TypeId DrawRectTexture::GetTypeId() const {
+  return ShaderProgram<ShaderVertexTexcoord,
+                       ShaderFragmentTexcoord>::GetTypeId();
+}
+
 }  // namespace egl
 }  // namespace rasterizer
 }  // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.h
index a0a284b..18363a8 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_texture.h
@@ -35,27 +35,16 @@
                   const backend::TextureEGL* texture,
                   const math::Matrix3F& texcoord_transform);
 
-  DrawRectTexture(GraphicsState* graphics_state,
-                  const BaseState& base_state,
-                  const math::RectF& rect,
-                  const backend::TextureEGL* texture,
-                  const math::Matrix3F& texcoord_transform,
-                  const base::Closure& draw_offscreen,
-                  const base::Closure& draw_onscreen);
-
-  void ExecuteOffscreenRasterize(GraphicsState* graphics_state,
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenUpdateVertexBuffer(GraphicsState* graphics_state,
+  void ExecuteRasterize(GraphicsState* graphics_state,
       ShaderProgramManager* program_manager) OVERRIDE;
-  void ExecuteOnscreenRasterize(GraphicsState* graphics_state,
-      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
 
  private:
   math::Matrix3F texcoord_transform_;
   math::RectF rect_;
   const backend::TextureEGL* texture_;
-  base::Closure draw_offscreen_;
-  base::Closure draw_onscreen_;
 
   uint8_t* vertex_buffer_;
   bool tile_texture_;
diff --git a/src/cobalt/renderer/rasterizer/egl/graphics_state.cc b/src/cobalt/renderer/rasterizer/egl/graphics_state.cc
index 3c2d1e9..d97d12a 100644
--- a/src/cobalt/renderer/rasterizer/egl/graphics_state.cc
+++ b/src/cobalt/renderer/rasterizer/egl/graphics_state.cc
@@ -23,8 +23,28 @@
 namespace rasterizer {
 namespace egl {
 
+namespace {
+
+void GLViewport(const math::Rect& viewport, const math::Size& target_size) {
+  // Incoming origin is top-left, but GL origin is bottom-left, so flip
+  // vertically.
+  GL_CALL(glViewport(viewport.x(), target_size.height() - viewport.bottom(),
+                     viewport.width(), viewport.height()));
+}
+
+void GLScissor(const math::Rect& scissor, const math::Size& target_size) {
+  // Incoming origin is top-left, but GL origin is bottom-left, so flip
+  // vertically.
+  GL_CALL(glScissor(scissor.x(), target_size.height() - scissor.bottom(),
+                    scissor.width(), scissor.height()));
+}
+
+}  // namespace
+
 GraphicsState::GraphicsState()
-    : frame_index_(0),
+    : render_target_handle_(0),
+      render_target_serial_(0),
+      frame_index_(0),
       vertex_data_reserved_(kVertexDataAlignment - 1),
       vertex_data_allocated_(0),
       vertex_data_buffer_updated_(false) {
@@ -32,12 +52,9 @@
   memset(clip_adjustment_, 0, sizeof(clip_adjustment_));
   SetDirty();
   blend_enabled_ = false;
-  depth_test_enabled_ = false;
-  depth_write_enabled_ = true;
   Reset();
 
   // These settings should only need to be set once. Nothing should touch them.
-  GL_CALL(glDepthRangef(0.0f, 1.0f));
   GL_CALL(glDisable(GL_DITHER));
   GL_CALL(glDisable(GL_CULL_FACE));
   GL_CALL(glDisable(GL_STENCIL_TEST));
@@ -63,8 +80,6 @@
   // cached state when needed.
   SetDirty();
   blend_enabled_ = false;
-  depth_test_enabled_ = false;
-  depth_write_enabled_ = true;
 }
 
 void GraphicsState::EndFrame() {
@@ -77,8 +92,6 @@
   // Force default GL state. The current state may be marked dirty, so don't
   // rely on any functions which check the cached state before issuing GL calls.
   GL_CALL(glDisable(GL_BLEND));
-  GL_CALL(glDisable(GL_DEPTH_TEST));
-  GL_CALL(glDepthMask(GL_TRUE));
   GL_CALL(glUseProgram(0));
 
   // Since the GL state was changed without going through the cache, mark it
@@ -87,13 +100,16 @@
 }
 
 void GraphicsState::Clear() {
+  Clear(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+void GraphicsState::Clear(float r, float g, float b, float a) {
   if (state_dirty_) {
     Reset();
   }
 
-  GL_CALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
-  GL_CALL(glClearDepthf(FarthestDepth()));
-  GL_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
+  GL_CALL(glClearColor(r, g, b, a));
+  GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
 }
 
 void GraphicsState::UseProgram(GLuint program) {
@@ -116,25 +132,48 @@
   disable_vertex_attrib_array_mask_ = enabled_vertex_attrib_array_mask_;
 }
 
+void GraphicsState::BindFramebuffer(
+    const backend::RenderTarget* render_target) {
+  if (render_target == nullptr) {
+    // Unbind the framebuffer immediately.
+    if (render_target_handle_ != 0) {
+      GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+    }
+    render_target_handle_ = 0;
+    render_target_serial_ = 0;
+    render_target_size_.SetSize(0, 0);
+    SetClipAdjustment();
+  } else if (render_target->GetPlatformHandle() != render_target_handle_ ||
+      render_target->GetSerialNumber() != render_target_serial_ ||
+      render_target->GetSize() != render_target_size_) {
+    render_target_handle_ = render_target->GetPlatformHandle();
+    render_target_serial_ = render_target->GetSerialNumber();
+    render_target_size_ = render_target->GetSize();
+    SetClipAdjustment();
+
+    if (!state_dirty_) {
+      GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, render_target_handle_));
+    }
+  }
+}
+
 void GraphicsState::Viewport(int x, int y, int width, int height) {
-  // Incoming origin is top-left, but GL origin is bottom-left, so flip
-  // vertically.
-  if (state_dirty_ || viewport_.x() != x || viewport_.y() != y ||
+  if (viewport_.x() != x || viewport_.y() != y ||
       viewport_.width() != width || viewport_.height() != height) {
     viewport_.SetRect(x, y, width, height);
-    GL_CALL(glViewport(x, render_target_size_.height() - y - height,
-                       width, height));
+    if (!state_dirty_) {
+      GLViewport(viewport_, render_target_size_);
+    }
   }
 }
 
 void GraphicsState::Scissor(int x, int y, int width, int height) {
-  // Incoming origin is top-left, but GL origin is bottom-left, so flip
-  // vertically.
-  if (state_dirty_ || scissor_.x() != x || scissor_.y() != y ||
+  if (scissor_.x() != x || scissor_.y() != y ||
       scissor_.width() != width || scissor_.height() != height) {
     scissor_.SetRect(x, y, width, height);
-    GL_CALL(glScissor(x, render_target_size_.height() - y - height,
-                      width, height));
+    if (!state_dirty_) {
+      GLScissor(scissor_, render_target_size_);
+    }
   }
 }
 
@@ -152,38 +191,6 @@
   }
 }
 
-void GraphicsState::EnableDepthTest() {
-  if (!depth_test_enabled_) {
-    depth_test_enabled_ = true;
-    GL_CALL(glEnable(GL_DEPTH_TEST));
-  }
-}
-
-void GraphicsState::DisableDepthTest() {
-  if (depth_test_enabled_) {
-    depth_test_enabled_ = false;
-    GL_CALL(glDisable(GL_DEPTH_TEST));
-  }
-}
-
-void GraphicsState::EnableDepthWrite() {
-  if (!depth_write_enabled_) {
-    depth_write_enabled_ = true;
-    GL_CALL(glDepthMask(GL_TRUE));
-  }
-}
-
-void GraphicsState::DisableDepthWrite() {
-  if (depth_write_enabled_) {
-    depth_write_enabled_ = false;
-    GL_CALL(glDepthMask(GL_FALSE));
-  }
-}
-
-void GraphicsState::ResetDepthFunc() {
-  GL_CALL(glDepthFunc(GL_LESS));
-}
-
 void GraphicsState::ActiveBindTexture(GLenum texture_unit, GLenum target,
                                       GLuint texture) {
   int texunit_index = texture_unit - GL_TEXTURE0;
@@ -228,26 +235,25 @@
   GL_CALL(glTexParameteri(target, GL_TEXTURE_WRAP_T, texture_wrap_mode));
 }
 
-void GraphicsState::SetClipAdjustment(const math::Size& render_target_size) {
-  render_target_size_ = render_target_size;
+void GraphicsState::SetClipAdjustment() {
   clip_adjustment_dirty_ = true;
 
   // Clip adjustment is a vec4 used to transform a given 2D position from view
   // space to clip space. Given a 2D position, pos, the output is:
   // output = pos * clip_adjustment_.xy + clip_adjustment_.zw
 
-  if (render_target_size.width() > 0) {
-    clip_adjustment_[0] = 2.0f / render_target_size.width();
+  if (render_target_size_.width() > 0) {
+    clip_adjustment_[0] = 2.0f / render_target_size_.width();
     clip_adjustment_[2] = -1.0f;
   } else {
     clip_adjustment_[0] = 0.0f;
     clip_adjustment_[2] = 0.0f;
   }
 
-  if (render_target_size.height() > 0) {
+  if (render_target_size_.height() > 0) {
     // Incoming origin is top-left, but GL origin is bottom-left, so flip the
     // image vertically.
-    clip_adjustment_[1] = -2.0f / render_target_size.height();
+    clip_adjustment_[1] = -2.0f / render_target_size_.height();
     clip_adjustment_[3] = 1.0f;
   } else {
     clip_adjustment_[1] = 0.0f;
@@ -337,31 +343,12 @@
   }
 }
 
-// static
-float GraphicsState::FarthestDepth() {
-  return 1.0f;
-}
-
-// static
-float GraphicsState::NextClosestDepth(float depth) {
-  // Our vertex shaders pass depth straight to gl_Position without any
-  // transformation, and gl_Position.w is always 1. To avoid clipping,
-  // |depth| should be [-1,1]. However, this range is then converted to [0,1],
-  // then scaled by (2^N - 1) to be the final value in the depth buffer, where
-  // N = number of bits in the depth buffer.
-
-  // In the worst case, we're using a 16-bit depth buffer. So each step in
-  // depth is 2 / (2^N - 1) (since depth is converted from [-1,1] to [0,1]).
-  // Also, because the default depth compare function is GL_LESS, vertices with
-  // smaller depth values appear on top of others.
-  return depth - 2.0f / 65535.0f;
-}
-
 void GraphicsState::Reset() {
   program_ = 0;
 
-  Viewport(viewport_.x(), viewport_.y(), viewport_.width(), viewport_.height());
-  Scissor(scissor_.x(), scissor_.y(), scissor_.width(), scissor_.height());
+  GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, render_target_handle_));
+  GLViewport(viewport_, render_target_size_);
+  GLScissor(scissor_, render_target_size_);
   GL_CALL(glEnable(GL_SCISSOR_TEST));
 
   array_buffer_handle_ = 0;
@@ -384,13 +371,7 @@
   }
   GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
 
-  if (depth_test_enabled_) {
-    GL_CALL(glEnable(GL_DEPTH_TEST));
-  } else {
-    GL_CALL(glDisable(GL_DEPTH_TEST));
-  }
-  GL_CALL(glDepthMask(depth_write_enabled_ ? GL_TRUE : GL_FALSE));
-  ResetDepthFunc();
+  GL_CALL(glDisable(GL_DEPTH_TEST));
 
   state_dirty_ = false;
 }
diff --git a/src/cobalt/renderer/rasterizer/egl/graphics_state.h b/src/cobalt/renderer/rasterizer/egl/graphics_state.h
index a8c48f4..dcc8c3c 100644
--- a/src/cobalt/renderer/rasterizer/egl/graphics_state.h
+++ b/src/cobalt/renderer/rasterizer/egl/graphics_state.h
@@ -22,6 +22,7 @@
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/math/rect.h"
 #include "cobalt/math/size.h"
+#include "cobalt/renderer/backend/render_target.h"
 
 namespace cobalt {
 namespace renderer {
@@ -47,16 +48,27 @@
 
   // Clear the color and depth buffers.
   void Clear();
+  void Clear(float r, float g, float b, float a);
 
   // Set the current shader program to be used.
   void UseProgram(GLuint program);
   GLuint GetProgram() const { return program_; }
 
-  // Set the viewport.
+  // Bind the specified framebuffer. This only goes through glBindFramebuffer
+  // and does not call eglMakeCurrent. This also SetClipAdjustment() to the
+  // render target's dimensions.
+  // |render_target| may be null to unbind the current framebuffer.
+  // NOTE: Be sure to call Viewport() and Scissor() after binding a new
+  //       framebuffer.
+  void BindFramebuffer(const backend::RenderTarget* render_target);
+
+  // Set the viewport. If changing render targets, then be sure to
+  // BindFramebuffer() before calling this.
   void Viewport(int x, int y, int width, int height);
   const math::Rect& GetViewport() const { return viewport_; }
 
-  // Set the scissor box.
+  // Set the scissor box. If changing render targets, then be sure to
+  // BindFramebuffer() before calling this.
   void Scissor(int x, int y, int width, int height);
   const math::Rect& GetScissor() const { return scissor_; }
 
@@ -66,21 +78,6 @@
   void DisableBlend();
   bool IsBlendEnabled() const { return blend_enabled_; }
 
-  // Control depth testing.
-  // Default = enabled.
-  void EnableDepthTest();
-  void DisableDepthTest();
-  bool IsDepthTestEnabled() const { return depth_test_enabled_; }
-
-  // Control writing to the depth buffer.
-  // Default = enabled.
-  void EnableDepthWrite();
-  void DisableDepthWrite();
-  bool IsDepthWriteEnabled() const { return depth_write_enabled_; }
-
-  // Reset to the default depth function.
-  void ResetDepthFunc();
-
   // Bind a texture to a given texture unit. Combines glActiveTexture and
   // glBindTexture.
   void ActiveBindTexture(GLenum texture_unit, GLenum target, GLuint texture);
@@ -90,10 +87,6 @@
   void ActiveBindTexture(GLenum texture_unit, GLenum target, GLuint texture,
                          GLint texture_wrap_mode);
 
-  // Set the clip adjustment to be used with vertex shaders. This transforms
-  // the vertex coordinates from view space to clip space.
-  void SetClipAdjustment(const math::Size& render_target_size);
-
   // Update the GPU with the current clip adjustment settings.
   void UpdateClipAdjustment(GLint handle);
 
@@ -125,19 +118,19 @@
   // VertexAttribPointer), but the current program does not.
   void VertexAttribFinish();
 
-  // Return the farthest depth value.
-  static float FarthestDepth();
-
-  // Return the next closest depth value. Vertices using the output depth are
-  // guaranteed to render on top of vertices using the incoming depth.
-  static float NextClosestDepth(float depth);
-
  private:
   void Reset();
 
+  // Set the clip adjustment to be used with vertex shaders. This transforms
+  // the vertex coordinates from view space to clip space.
+  void SetClipAdjustment();
+
   math::Rect viewport_;
   math::Rect scissor_;
+
   math::Size render_target_size_;
+  GLuint render_target_handle_;
+  int32_t render_target_serial_;
 
   GLuint program_;
   GLuint array_buffer_handle_;
@@ -145,11 +138,10 @@
   uint32_t enabled_vertex_attrib_array_mask_;
   uint32_t disable_vertex_attrib_array_mask_;
   float clip_adjustment_[4];
+
   bool clip_adjustment_dirty_;
   bool state_dirty_;
   bool blend_enabled_;
-  bool depth_test_enabled_;
-  bool depth_write_enabled_;
 
   static const int kNumTextureUnitsCached = 8;
   GLenum texunit_target_[kNumTextureUnitsCached];
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
index 10c3a9d..336efc7 100644
--- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
@@ -20,6 +20,7 @@
 #include "base/debug/trace_event.h"
 #include "base/memory/scoped_vector.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/render_tree/filter_node.h"
 #include "cobalt/renderer/backend/egl/framebuffer_render_target.h"
 #include "cobalt/renderer/backend/egl/graphics_context.h"
 #include "cobalt/renderer/backend/egl/graphics_system.h"
@@ -33,6 +34,8 @@
 #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h"
 #include "cobalt/renderer/rasterizer/skia/hardware_rasterizer.h"
 #include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkRefCnt.h"
+#include "third_party/skia/include/core/SkSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 
 namespace cobalt {
@@ -56,8 +59,8 @@
 
   void SubmitToFallbackRasterizer(
       const scoped_refptr<render_tree::Node>& render_tree,
-      const math::Matrix3F& transform,
-      const OffscreenTargetManager::TargetInfo& target);
+      SkCanvas* fallback_render_target, const math::Matrix3F& transform,
+      const math::RectF& scissor, float opacity, uint32_t rasterize_flags);
 
   render_tree::ResourceProvider* GetResourceProvider() {
     return fallback_rasterizer_->GetResourceProvider();
@@ -69,9 +72,14 @@
   }
 
   void ResetFallbackContextDuringFrame();
+  void FlushFallbackOffscreenDraws();
 
   void RasterizeTree(const scoped_refptr<render_tree::Node>& render_tree,
-                     backend::RenderTargetEGL* render_target);
+                     backend::RenderTargetEGL* render_target,
+                     const math::Rect& content_rect);
+
+  SkSurface* CreateFallbackSurface(
+      const backend::RenderTarget* render_target);
 
   scoped_ptr<skia::HardwareRasterizer> fallback_rasterizer_;
   scoped_ptr<GraphicsState> graphics_state_;
@@ -103,7 +111,10 @@
   graphics_state_.reset(new GraphicsState());
   shader_program_manager_.reset(new ShaderProgramManager());
   offscreen_target_manager_.reset(new OffscreenTargetManager(
-      graphics_context_, GetFallbackContext(), surface_cache_size_in_bytes));
+      graphics_context_,
+      base::Bind(&HardwareRasterizer::Impl::CreateFallbackSurface,
+                 base::Unretained(this)),
+      surface_cache_size_in_bytes));
 }
 
 HardwareRasterizer::Impl::~Impl() {
@@ -128,73 +139,59 @@
   backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
       graphics_context_, render_target_egl);
 
-  // Make sure this render target has a depth buffer. This is only relevant
-  // for framebuffer render targets. Other render target types should already
-  // have a depth buffer set up by the graphics system's config.
-  if (render_target_egl->GetSurface() == EGL_NO_SURFACE) {
-    backend::FramebufferRenderTargetEGL* framebuffer_render_target =
-        base::polymorphic_downcast<backend::FramebufferRenderTargetEGL*>(
-            render_target_egl);
-    framebuffer_render_target->EnsureDepthBufferAttached(GL_DEPTH_COMPONENT16);
-  }
-
   fallback_rasterizer_->AdvanceFrame();
 
-  const math::Size& target_size = render_target->GetSize();
-  graphics_state_->SetClipAdjustment(target_size);
-  graphics_state_->Viewport(0, 0, target_size.width(), target_size.height());
-
   // Update only the dirty pixels if the render target contents are preserved
   // between frames.
+  math::Rect content_rect(render_target->GetSize());
   if (options.dirty && render_target_egl->ContentWasPreservedAfterSwap()) {
-    graphics_state_->Scissor(options.dirty->x(), options.dirty->y(),
-        options.dirty->width(), options.dirty->height());
-  } else {
-    graphics_state_->Scissor(0, 0, target_size.width(), target_size.height());
+    content_rect = *options.dirty;
   }
 
-  offscreen_target_manager_->Update(target_size);
+  offscreen_target_manager_->Update(render_target->GetSize());
 
-  RasterizeTree(render_tree, render_target_egl);
+  RasterizeTree(render_tree, render_target_egl, content_rect);
 
   graphics_context_->SwapBuffers(render_target_egl);
 }
 
 void HardwareRasterizer::Impl::SubmitToFallbackRasterizer(
     const scoped_refptr<render_tree::Node>& render_tree,
-    const math::Matrix3F& transform,
-    const OffscreenTargetManager::TargetInfo& target) {
+    SkCanvas* fallback_render_target, const math::Matrix3F& transform,
+    const math::RectF& scissor, float opacity, uint32_t rasterize_flags) {
   DCHECK(thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("cobalt::renderer", "SubmitToFallbackRasterizer");
 
   // Use skia to rasterize to the allocated offscreen target.
-  target.skia_canvas->save();
+  fallback_render_target->save();
 
-  if (target.is_scratch_surface) {
-    // The scratch surface is used immediately after rendering to it. So we
-    // are switching from this rasterizer to skia, then will switch back to
-    // our rasterizer context.
-    ResetFallbackContextDuringFrame();
-    target.skia_canvas->clear(SK_ColorTRANSPARENT);
+  fallback_render_target->clipRect(SkRect::MakeXYWH(
+      scissor.x(), scissor.y(), scissor.width(), scissor.height()));
+  fallback_render_target->concat(skia::CobaltMatrixToSkia(transform));
+
+  if ((rasterize_flags & RenderTreeNodeVisitor::kFallbackShouldClear) != 0) {
+    fallback_render_target->clear(SK_ColorTRANSPARENT);
   }
 
-  target.skia_canvas->clipRect(SkRect::MakeXYWH(
-      target.region.x(), target.region.y(),
-      target.region.width(), target.region.height()));
-  target.skia_canvas->translate(target.region.x(), target.region.y());
-  target.skia_canvas->concat(skia::CobaltMatrixToSkia(transform));
-  fallback_rasterizer_->SubmitOffscreen(render_tree, target.skia_canvas);
-
-  if (target.is_scratch_surface) {
-    // Flush the skia draw calls so the contents can be used immediately.
-    target.skia_canvas->flush();
-
-    // Switch back to the current render target and context.
-    graphics_context_->ResetCurrentSurface();
-    graphics_state_->SetDirty();
+  if (opacity < 1.0f) {
+    scoped_refptr<render_tree::Node> opacity_node =
+        new render_tree::FilterNode(render_tree::OpacityFilter(opacity),
+                                    render_tree);
+    fallback_rasterizer_->SubmitOffscreen(opacity_node, fallback_render_target);
+  } else {
+    fallback_rasterizer_->SubmitOffscreen(render_tree, fallback_render_target);
   }
 
-  target.skia_canvas->restore();
+  if ((rasterize_flags & RenderTreeNodeVisitor::kFallbackShouldFlush) != 0) {
+    fallback_render_target->flush();
+  }
+
+  fallback_render_target->restore();
+}
+
+void HardwareRasterizer::Impl::FlushFallbackOffscreenDraws() {
+  TRACE_EVENT0("cobalt::renderer", "Skia Flush");
+  offscreen_target_manager_->Flush();
 }
 
 void HardwareRasterizer::Impl::ResetFallbackContextDuringFrame() {
@@ -202,27 +199,28 @@
   // states that this rasterizer pollutes.
   uint32_t untouched_states = kMSAAEnable_GrGLBackendState |
       kStencil_GrGLBackendState | kPixelStore_GrGLBackendState |
-      kFixedFunction_GrGLBackendState | kPathRendering_GrGLBackendState;
-
-  // Manually reset a subset of kMisc_GrGLBackendState
-  untouched_states |= kMisc_GrGLBackendState;
-  GL_CALL(glDisable(GL_DEPTH_TEST));
-  GL_CALL(glDepthMask(GL_FALSE));
+      kFixedFunction_GrGLBackendState | kPathRendering_GrGLBackendState |
+      kMisc_GrGLBackendState;
 
   GetFallbackContext()->resetContext(~untouched_states & kAll_GrBackendState);
 }
 
 void HardwareRasterizer::Impl::RasterizeTree(
     const scoped_refptr<render_tree::Node>& render_tree,
-    backend::RenderTargetEGL* render_target) {
-  DrawObjectManager draw_object_manager;
-  RenderTreeNodeVisitor::FallbackRasterizeFunction fallback_rasterize =
+    backend::RenderTargetEGL* render_target,
+    const math::Rect& content_rect) {
+  DrawObjectManager draw_object_manager(
+      base::Bind(&HardwareRasterizer::Impl::ResetFallbackContextDuringFrame,
+                 base::Unretained(this)),
+      base::Bind(&HardwareRasterizer::Impl::FlushFallbackOffscreenDraws,
+                 base::Unretained(this)));
+  RenderTreeNodeVisitor visitor(
+      graphics_state_.get(), &draw_object_manager,
+      offscreen_target_manager_.get(),
       base::Bind(&HardwareRasterizer::Impl::SubmitToFallbackRasterizer,
-                 base::Unretained(this));
-  RenderTreeNodeVisitor visitor(graphics_state_.get(),
-                                &draw_object_manager,
-                                offscreen_target_manager_.get(),
-                                &fallback_rasterize);
+                 base::Unretained(this)),
+      fallback_rasterizer_->GetCachedCanvas(render_target),
+      render_target, content_rect);
 
   // Traverse the render tree to populate the draw object manager.
   {
@@ -235,42 +233,53 @@
   // Rasterize to offscreen targets using skia.
   {
     TRACE_EVENT0("cobalt::renderer", "OffscreenRasterize");
-    backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
-        graphics_context_, render_target);
 
-    // Reset the skia graphics context since the egl rasterizer dirtied it.
+    // Ensure the skia context is fully reset.
     GetFallbackContext()->resetContext();
     draw_object_manager.ExecuteOffscreenRasterize(graphics_state_.get(),
         shader_program_manager_.get());
-
-    {
-      TRACE_EVENT0("cobalt::renderer", "Skia Flush");
-      offscreen_target_manager_->Flush();
-    }
-
-    // Reset the egl graphics state since skia dirtied it.
-    graphics_state_->SetDirty();
-    GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
   }
 
+  // Clear the dirty region of the render target.
+  graphics_state_->BindFramebuffer(render_target);
+  graphics_state_->Viewport(0, 0,
+                            render_target->GetSize().width(),
+                            render_target->GetSize().height());
+  graphics_state_->Scissor(content_rect.x(), content_rect.y(),
+                           content_rect.width(), content_rect.height());
   graphics_state_->Clear();
 
   {
-    TRACE_EVENT0("cobalt::renderer", "OnscreenUpdateVertexBuffer");
-    draw_object_manager.ExecuteOnscreenUpdateVertexBuffer(graphics_state_.get(),
-        shader_program_manager_.get());
-    graphics_state_->UpdateVertexData();
-  }
-
-  {
     TRACE_EVENT0("cobalt::renderer", "OnscreenRasterize");
     draw_object_manager.ExecuteOnscreenRasterize(graphics_state_.get(),
         shader_program_manager_.get());
   }
 
+  graphics_context_->ResetCurrentSurface();
   graphics_state_->EndFrame();
 }
 
+SkSurface* HardwareRasterizer::Impl::CreateFallbackSurface(
+    const backend::RenderTarget* render_target) {
+  // Wrap the given render target in a new skia surface.
+  GrBackendRenderTargetDesc skia_desc;
+  skia_desc.fWidth = render_target->GetSize().width();
+  skia_desc.fHeight = render_target->GetSize().height();
+  skia_desc.fConfig = kRGBA_8888_GrPixelConfig;
+  skia_desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
+  skia_desc.fSampleCnt = 0;
+  skia_desc.fStencilBits = 0;
+  skia_desc.fRenderTargetHandle = render_target->GetPlatformHandle();
+
+  SkAutoTUnref<GrRenderTarget> skia_render_target(
+      GetFallbackContext()->wrapBackendRenderTarget(skia_desc));
+  SkSurfaceProps skia_surface_props(
+      SkSurfaceProps::kUseDistanceFieldFonts_Flag,
+      SkSurfaceProps::kLegacyFontHost_InitType);
+  return SkSurface::NewRenderTargetDirect(
+      skia_render_target, &skia_surface_props);
+}
+
 HardwareRasterizer::HardwareRasterizer(
     backend::GraphicsContext* graphics_context, int skia_atlas_width,
     int skia_atlas_height, int skia_cache_size_in_bytes,
diff --git a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
index a530a84..d3feebb 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
@@ -19,10 +19,6 @@
 #include "base/hash_tables.h"
 #include "cobalt/renderer/rasterizer/egl/rect_allocator.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
-#include "third_party/skia/include/core/SkSurface.h"
-#include "third_party/skia/include/core/SkSurfaceProps.h"
-#include "third_party/skia/include/gpu/GrRenderTarget.h"
-#include "third_party/skia/include/gpu/GrTypes.h"
 
 namespace {
 // Structure describing the key for render target allocations in a given
@@ -111,16 +107,17 @@
   RectAllocator allocator;
   AllocationMap allocation_map;
   size_t allocations_used;
-  scoped_ptr<backend::FramebufferEGL> framebuffer;
+  scoped_refptr<backend::FramebufferRenderTargetEGL> framebuffer;
   SkAutoTUnref<SkSurface> skia_surface;
   bool needs_flush;
 };
 
 OffscreenTargetManager::OffscreenTargetManager(
-    backend::GraphicsContextEGL* graphics_context, GrContext* skia_context,
+    backend::GraphicsContextEGL* graphics_context,
+    const CreateFallbackSurfaceFunction& create_fallback_surface,
     size_t memory_limit)
     : graphics_context_(graphics_context),
-      skia_context_(skia_context),
+      create_fallback_surface_(create_fallback_surface),
       offscreen_target_size_mask_(0, 0),
       memory_limit_(memory_limit) {
 }
@@ -185,7 +182,6 @@
     out_target_info->framebuffer = offscreen_cache_->framebuffer.get();
     out_target_info->skia_canvas = offscreen_cache_->skia_surface->getCanvas();
     out_target_info->region = iter->second;
-    out_target_info->is_scratch_surface = false;
     return true;
   }
   return false;
@@ -228,12 +224,11 @@
     }
   }
 
-  // Use the scratch surface if needed.
-  bool scratch_surface_used = false;
   if (target_rect.IsEmpty()) {
-    scratch_surface_used = true;
-    atlas = scratch_surface_.get();
-    target_rect = math::Rect(atlas->framebuffer->GetSize());
+    // There wasn't enough room for the requested offscreen target.
+    out_target_info->framebuffer = nullptr;
+    out_target_info->skia_canvas = nullptr;
+    out_target_info->region.SetRect(0, 0, 0, 0);
   } else {
     // Inset to prevent interpolation with unwanted pixels at the edge.
     target_rect.Inset(kInterpolatePad, kInterpolatePad);
@@ -247,12 +242,11 @@
         AllocationKey(node, size), target_rect));
     atlas->allocations_used += 1;
     atlas->needs_flush = true;
-  }
 
-  out_target_info->framebuffer = atlas->framebuffer.get();
-  out_target_info->skia_canvas = atlas->skia_surface->getCanvas();
-  out_target_info->region = target_rect;
-  out_target_info->is_scratch_surface = scratch_surface_used;
+    out_target_info->framebuffer = atlas->framebuffer.get();
+    out_target_info->skia_canvas = atlas->skia_surface->getCanvas();
+    out_target_info->region = target_rect;
+  }
 }
 
 void OffscreenTargetManager::InitializeTargets(const math::Size& frame_size) {
@@ -266,43 +260,34 @@
     offscreen_target_size_mask_.SetSize(0, 0);
   }
 
-  math::Size max_size(NextPowerOf2(frame_size.width()) / 2,
-                      NextPowerOf2(frame_size.height()) / 2);
-  max_size.SetToMax(math::Size(1, 1));
+  // Allow offscreen targets to be as large as the frame.
+  math::Size max_size(std::max(frame_size.width(), 1),
+                      std::max(frame_size.height(), 1));
 
-  // A full-screen scratch surface is required. This avoids pixelation when an
-  // offscreen target is needed.
-  scratch_surface_.reset(CreateOffscreenAtlas(frame_size));
-
-  // Other offscreen render targets are optional but highly recommended. These
+  // Offscreen render targets are optional but highly recommended. These
   // allow caching of render results for improved performance. At least two
   // must exist -- one for the cache and the other a working scratch.
   size_t half_memory_limit = memory_limit_ / 2;
   math::Size atlas_size(1, 1);
   for (;;) {
-    if (atlas_size == max_size) {
-      break;
-    }
-    if (GetMemorySize(atlas_size) * 2 <= half_memory_limit) {
-      // Memory limit allows a bigger atlas.
-      if (atlas_size.width() <= atlas_size.height()) {
-        // Prefer growing by width.
-        if (atlas_size.width() < max_size.width()) {
-          atlas_size.set_width(atlas_size.width() * 2);
-        } else {
-          atlas_size.set_height(atlas_size.height() * 2);
-        }
-      } else {
-        // Prefer growing by height.
-        if (atlas_size.height() < max_size.height()) {
-          atlas_size.set_height(atlas_size.height() * 2);
-        } else {
-          atlas_size.set_width(atlas_size.width() * 2);
-        }
-      }
+    // See if the next atlas size will fit in the memory budget.
+    // Try to keep the atlas square-ish.
+    math::Size next_size(atlas_size);
+    if (atlas_size.width() < max_size.width() &&
+        (atlas_size.width() <= atlas_size.height() ||
+        atlas_size.height() >= max_size.height())) {
+      next_size.set_width(
+          std::min(atlas_size.width() * 2, max_size.width()));
+    } else if (atlas_size.height() < max_size.height()) {
+      next_size.set_height(
+          std::min(atlas_size.height() * 2, max_size.height()));
     } else {
       break;
     }
+    if (GetMemorySize(next_size) > half_memory_limit) {
+      break;
+    }
+    atlas_size = next_size;
   }
 
   // It is better to have fewer, large atlases than many small atlases to
@@ -335,26 +320,11 @@
   OffscreenAtlas* atlas = new OffscreenAtlas(size);
 
   // Create a new framebuffer.
-  atlas->framebuffer.reset(new backend::FramebufferEGL(
-      graphics_context_, size, GL_RGBA, GL_NONE));
+  atlas->framebuffer = new backend::FramebufferRenderTargetEGL(
+      graphics_context_, size);
 
   // Wrap the framebuffer as a skia surface.
-  GrBackendRenderTargetDesc skia_desc;
-  skia_desc.fWidth = size.width();
-  skia_desc.fHeight = size.height();
-  skia_desc.fConfig = kRGBA_8888_GrPixelConfig;
-  skia_desc.fOrigin = kTopLeft_GrSurfaceOrigin;
-  skia_desc.fSampleCnt = 0;
-  skia_desc.fStencilBits = 0;
-  skia_desc.fRenderTargetHandle = atlas->framebuffer->gl_handle();
-
-  SkAutoTUnref<GrRenderTarget> skia_render_target(
-      skia_context_->wrapBackendRenderTarget(skia_desc));
-  SkSurfaceProps skia_surface_props(
-      SkSurfaceProps::kUseDistanceFieldFonts_Flag,
-      SkSurfaceProps::kLegacyFontHost_InitType);
-  atlas->skia_surface.reset(SkSurface::NewRenderTargetDirect(
-      skia_render_target, &skia_surface_props));
+  atlas->skia_surface.reset(create_fallback_surface_.Run(atlas->framebuffer));
 
   return atlas;
 }
diff --git a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
index 5d23334..02c0704 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
@@ -15,15 +15,16 @@
 #ifndef COBALT_RENDERER_RASTERIZER_EGL_OFFSCREEN_TARGET_MANAGER_H_
 #define COBALT_RENDERER_RASTERIZER_EGL_OFFSCREEN_TARGET_MANAGER_H_
 
+#include "base/callback.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/scoped_vector.h"
 #include "cobalt/math/rect_f.h"
 #include "cobalt/math/size.h"
 #include "cobalt/render_tree/node.h"
-#include "cobalt/renderer/backend/egl/framebuffer.h"
+#include "cobalt/renderer/backend/egl/framebuffer_render_target.h"
 #include "cobalt/renderer/backend/egl/graphics_context.h"
 #include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/gpu/GrContext.h"
+#include "third_party/skia/include/core/SkSurface.h"
 
 namespace cobalt {
 namespace renderer {
@@ -35,19 +36,21 @@
 // minimize the cost of switching render targets.
 class OffscreenTargetManager {
  public:
+  typedef base::Callback<SkSurface*(const backend::RenderTarget*)>
+      CreateFallbackSurfaceFunction;
+
   struct TargetInfo {
     TargetInfo()
         : framebuffer(NULL),
-          skia_canvas(NULL),
-          is_scratch_surface(false) {}
-    backend::FramebufferEGL* framebuffer;
+          skia_canvas(NULL) {}
+    backend::FramebufferRenderTargetEGL* framebuffer;
     SkCanvas* skia_canvas;
     math::RectF region;
-    bool is_scratch_surface;
   };
 
   OffscreenTargetManager(backend::GraphicsContextEGL* graphics_context,
-                         GrContext* skia_context, size_t memory_limit);
+      const CreateFallbackSurfaceFunction& create_fallback_surface,
+      size_t memory_limit);
   ~OffscreenTargetManager();
 
   // Update must be called once per frame, before any allocation requests are
@@ -79,11 +82,10 @@
   OffscreenAtlas* CreateOffscreenAtlas(const math::Size& size);
 
   backend::GraphicsContextEGL* graphics_context_;
-  GrContext* skia_context_;
+  CreateFallbackSurfaceFunction create_fallback_surface_;
 
   ScopedVector<OffscreenAtlas> offscreen_atlases_;
   scoped_ptr<OffscreenAtlas> offscreen_cache_;
-  scoped_ptr<OffscreenAtlas> scratch_surface_;
 
   // Align offscreen targets to a particular size to more efficiently use the
   // offscreen target atlas. Use a power of 2 for the alignment so that a bit
diff --git a/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp b/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
index 6ff941a..42e592e 100644
--- a/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
@@ -37,8 +37,10 @@
       'type': 'static_library',
 
       'sources': [
-        'draw_depth_stencil.h',
-        'draw_depth_stencil.cc',
+        'draw_callback.h',
+        'draw_callback.cc',
+        'draw_clear.h',
+        'draw_clear.cc',
         'draw_object.h',
         'draw_object.cc',
         'draw_object_manager.h',
diff --git a/src/cobalt/renderer/rasterizer/egl/rect_allocator.cc b/src/cobalt/renderer/rasterizer/egl/rect_allocator.cc
index 2ebb99e..90d8d44 100644
--- a/src/cobalt/renderer/rasterizer/egl/rect_allocator.cc
+++ b/src/cobalt/renderer/rasterizer/egl/rect_allocator.cc
@@ -26,11 +26,10 @@
 // blocks at the end of the free list. Allocations will use the block with
 // the smallest area of sufficient dimensions in order to minimize waste.
   bool FirstRectIsBigger(const math::Rect& a, const math::Rect& b) {
-  const float area_a = a.width() * a.height();
-  const float area_b = b.width() * b.height();
-  return (area_a > area_b) ||
-         (area_a == area_b && (a.width() > b.width() ||
-                               a.height() > b.height()));
+    const int area_a = a.width() * a.height();
+    const int area_b = b.width() * b.height();
+    return (area_a > area_b) || (area_a == area_b && (a.width() > b.width() ||
+                                                      a.height() > b.height()));
 }
 }  // namespace
 
@@ -82,8 +81,8 @@
         if (memory.height() != allocation.height()) {
           // Return bottom and right of memory block as unused.
           // Preserve the largest block.
-          const float remaining_width = memory.width() - allocation.width();
-          const float remaining_height = memory.height() - allocation.height();
+          const int remaining_width = memory.width() - allocation.width();
+          const int remaining_height = memory.height() - allocation.height();
           if (memory.width() * remaining_height >=
               remaining_width * memory.height()) {
             // Bottom portion is bigger.
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
index 9622bb7..1685153 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -16,14 +16,18 @@
 
 #include <algorithm>
 #include <cmath>
+#include <limits>
 
 #include "base/debug/trace_event.h"
+#include "base/logging.h"
 #include "base/optional.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/type_id.h"
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/math/transform_2d.h"
 #include "cobalt/renderer/rasterizer/common/utils.h"
+#include "cobalt/renderer/rasterizer/egl/draw_callback.h"
+#include "cobalt/renderer/rasterizer/egl/draw_clear.h"
 #include "cobalt/renderer/rasterizer/egl/draw_poly_color.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h"
@@ -40,6 +44,9 @@
 
 namespace {
 
+const render_tree::ColorRGBA kOpaqueWhite(1.0f, 1.0f, 1.0f, 1.0f);
+const render_tree::ColorRGBA kTransparentBlack(0.0f, 0.0f, 0.0f, 0.0f);
+
 math::Rect RoundRectFToInt(const math::RectF& input) {
   int left = static_cast<int>(input.x() + 0.5f);
   int right = static_cast<int>(input.right() + 0.5f);
@@ -55,11 +62,12 @@
 
 math::Matrix3F GetTexcoordTransform(
     const OffscreenTargetManager::TargetInfo& target) {
+  // Flip the texture vertically to accommodate OpenGL's bottom-left origin.
   float scale_x = 1.0f / target.framebuffer->GetSize().width();
-  float scale_y = 1.0f / target.framebuffer->GetSize().height();
+  float scale_y = -1.0f / target.framebuffer->GetSize().height();
   return math::Matrix3F::FromValues(
       target.region.width() * scale_x, 0, target.region.x() * scale_x,
-      0, target.region.height() * scale_y, target.region.y() * scale_y,
+      0, target.region.height() * scale_y, 1.0f + target.region.y() * scale_y,
       0, 0, 1);
 }
 
@@ -68,16 +76,21 @@
 RenderTreeNodeVisitor::RenderTreeNodeVisitor(GraphicsState* graphics_state,
     DrawObjectManager* draw_object_manager,
     OffscreenTargetManager* offscreen_target_manager,
-    const FallbackRasterizeFunction* fallback_rasterize)
+    const FallbackRasterizeFunction& fallback_rasterize,
+    SkCanvas* fallback_render_target,
+    backend::RenderTarget* render_target,
+    const math::Rect& content_rect)
     : graphics_state_(graphics_state),
       draw_object_manager_(draw_object_manager),
       offscreen_target_manager_(offscreen_target_manager),
-      fallback_rasterize_(fallback_rasterize) {
-  // Let the first draw object render in front of the clear depth.
-  draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
-
-  draw_state_.scissor.Intersect(graphics_state->GetViewport());
-  draw_state_.scissor.Intersect(graphics_state->GetScissor());
+      fallback_rasterize_(fallback_rasterize),
+      fallback_render_target_(fallback_render_target),
+      render_target_(render_target),
+      render_target_is_offscreen_(false),
+      allow_offscreen_targets_(true),
+      failed_offscreen_target_request_(false),
+      last_draw_id_(0) {
+  draw_state_.scissor.Intersect(content_rect);
 }
 
 void RenderTreeNodeVisitor::Visit(
@@ -97,8 +110,10 @@
 
 void RenderTreeNodeVisitor::Visit(
     render_tree::MatrixTransform3DNode* transform_3d_node) {
-  // TODO: Ignore the 3D transform matrix for now.
-  transform_3d_node->data().source->Accept(this);
+  // This is used in conjunction with a map-to-mesh filter. If that filter is
+  // implemented natively, then this transform 3D must be handled natively
+  // as well. Otherwise, use the fallback rasterizer for both.
+  FallbackRasterize(transform_3d_node);
 }
 
 void RenderTreeNodeVisitor::Visit(
@@ -158,20 +173,46 @@
       !data.viewport_filter &&
       !data.blur_filter &&
       !data.map_to_mesh_filter) {
-    int opacity = static_cast<int>(data.opacity_filter->opacity() * 255.0f);
-    if (opacity <= 0) {
+    const float filter_opacity = data.opacity_filter->opacity();
+    if (filter_opacity <= 0.0f) {
       // Totally transparent. Ignore the source.
       return;
-    } else if (opacity >= 255) {
+    } else if (filter_opacity >= 1.0f) {
       // Totally opaque. Render like normal.
       data.source->Accept(this);
       return;
     } else if (common::utils::NodeCanRenderWithOpacity(data.source)) {
+      // Simple opacity that does not require an offscreen target.
       float old_opacity = draw_state_.opacity;
-      draw_state_.opacity *= data.opacity_filter->opacity();
+      draw_state_.opacity *= filter_opacity;
       data.source->Accept(this);
       draw_state_.opacity = old_opacity;
       return;
+    } else {
+      // Complex opacity that requires an offscreen target.
+      math::Matrix3F texcoord_transform(math::Matrix3F::Identity());
+      math::RectF content_rect;
+      const backend::TextureEGL* texture = nullptr;
+
+      // Render source at 100% opacity to an offscreen target, then render
+      // that result with the specified filter opacity.
+      OffscreenRasterize(data.source, &texture, &texcoord_transform,
+                         &content_rect);
+      if (texture != nullptr) {
+        if (content_rect.IsEmpty()) {
+          return;
+        }
+
+        // The content rect is already in screen space, so reset the transform.
+        math::Matrix3F old_transform = draw_state_.transform;
+        draw_state_.transform = math::Matrix3F::Identity();
+        scoped_ptr<DrawObject> draw(new DrawRectColorTexture(graphics_state_,
+            draw_state_, content_rect, kOpaqueWhite * filter_opacity, texture,
+            texcoord_transform, false /* clamp_texcoords */));
+        AddTransparentDraw(draw.Pass(), content_rect);
+        draw_state_.transform = old_transform;
+        return;
+      }
     }
   }
 
@@ -197,7 +238,7 @@
   }
 
   // Use the fallback rasterizer to handle everything else.
-  FallbackRasterize(filter_node, DrawObjectManager::kOffscreenSkiaFilter);
+  FallbackRasterize(filter_node);
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) {
@@ -247,28 +288,23 @@
   // Different shaders are used depending on whether the image has a single
   // plane or multiple planes.
   scoped_ptr<DrawObject> draw;
-  DrawObjectManager::OnscreenType onscreen_type;
 
   if (skia_image->GetTypeId() == base::GetTypeId<skia::SinglePlaneImage>()) {
     skia::HardwareFrontendImage* hardware_image =
         base::polymorphic_downcast<skia::HardwareFrontendImage*>(skia_image);
     if (clamp_texcoords || !is_opaque) {
-      onscreen_type = DrawObjectManager::kOnscreenRectColorTexture;
       draw.reset(new DrawRectColorTexture(graphics_state_, draw_state_,
-          data.destination_rect,
-          render_tree::ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
+          data.destination_rect, kOpaqueWhite,
           hardware_image->GetTextureEGL(), texcoord_transform,
           clamp_texcoords));
     } else {
-      onscreen_type = DrawObjectManager::kOnscreenRectTexture;
       draw.reset(new DrawRectTexture(graphics_state_, draw_state_,
           data.destination_rect, hardware_image->GetTextureEGL(),
           texcoord_transform));
     }
   } else if (skia_image->GetTypeId() ==
              base::GetTypeId<skia::MultiPlaneImage>()) {
-    FallbackRasterize(image_node,
-                      DrawObjectManager::kOffscreenSkiaMultiPlaneImage);
+    FallbackRasterize(image_node);
     return;
   } else {
     NOTREACHED();
@@ -276,11 +312,9 @@
   }
 
   if (is_opaque) {
-    AddOpaqueDraw(draw.Pass(), onscreen_type,
-        DrawObjectManager::kOffscreenNone);
+    AddOpaqueDraw(draw.Pass(), image_node->GetBounds());
   } else {
-    AddTransparentDraw(draw.Pass(), onscreen_type,
-        DrawObjectManager::kOffscreenNone, image_node->GetBounds());
+    AddTransparentDraw(draw.Pass(), image_node->GetBounds());
   }
 }
 
@@ -299,9 +333,8 @@
                  static_cast<int>(mapped_rect.height())));
 
   scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
-      draw_state_, data.rect, render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f)));
-  AddOpaqueDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
-      DrawObjectManager::kOffscreenNone);
+      draw_state_, data.rect, kTransparentBlack));
+  AddOpaqueDraw(draw.Pass(), video_node->GetBounds());
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) {
@@ -326,11 +359,11 @@
   const bool border_supported = !data.border;
 
   if (data.rounded_corners) {
-    FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectRounded);
+    FallbackRasterize(rect_node);
   } else if (!brush_supported) {
-    FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectBrush);
+    FallbackRasterize(rect_node);
   } else if (!border_supported) {
-    FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectBorder);
+    FallbackRasterize(rect_node);
   } else {
     DCHECK(!data.border);
     const math::RectF& content_rect(data.rect);
@@ -351,11 +384,9 @@
         scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
             draw_state_, content_rect, content_color));
         if (draw_state_.opacity * content_color.a() == 1.0f) {
-          AddOpaqueDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
-              DrawObjectManager::kOffscreenNone);
+          AddOpaqueDraw(draw.Pass(), rect_node->GetBounds());
         } else {
-          AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
-              DrawObjectManager::kOffscreenNone, rect_node->GetBounds());
+          AddTransparentDraw(draw.Pass(), rect_node->GetBounds());
         }
       } else {
         const render_tree::LinearGradientBrush* linear_brush =
@@ -363,11 +394,9 @@
                 (brush.get());
         scoped_ptr<DrawObject> draw(new DrawRectLinearGradient(graphics_state_,
             draw_state_, content_rect, *linear_brush));
-        // The draw may use transparent colors or a depth stencil (but only
-        // the inclusive one, so only one depth value is needed), so make it
-        // a transparent draw.
-        AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
-            DrawObjectManager::kOffscreenNone, rect_node->GetBounds());
+        // The draw may use transparent pixels to ensure only pixels in the
+        // content_rect are modified.
+        AddTransparentDraw(draw.Pass(), rect_node->GetBounds());
       }
     }
   }
@@ -381,7 +410,7 @@
 
   const render_tree::RectShadowNode::Builder& data = shadow_node->data();
   if (data.rounded_corners) {
-    FallbackRasterize(shadow_node, DrawObjectManager::kOffscreenSkiaShadow);
+    FallbackRasterize(shadow_node);
     return;
   }
 
@@ -391,8 +420,6 @@
       data.shadow.color.g() * data.shadow.color.a(),
       data.shadow.color.b() * data.shadow.color.a(),
       data.shadow.color.a());
-  DrawObjectManager::OnscreenType onscreen_type =
-      DrawObjectManager::kOnscreenRectShadow;
 
   math::RectF spread_rect(data.rect);
   spread_rect.Offset(data.shadow.offset);
@@ -418,12 +445,11 @@
         blur_rect.set_size(math::SizeF());
       }
       draw.reset(new DrawRectShadowBlur(graphics_state_, draw_state_,
-          blur_rect, data.rect, spread_rect, shadow_color, math::RectF(),
+          blur_rect, data.rect, spread_rect, shadow_color,
           data.shadow.blur_sigma, data.inset));
-      onscreen_type = DrawObjectManager::kOnscreenRectShadowBlur;
     } else {
       draw.reset(new DrawRectShadowSpread(graphics_state_, draw_state_,
-          spread_rect, data.rect, shadow_color, data.rect, math::RectF()));
+          spread_rect, data.rect, shadow_color, data.rect));
     }
   } else {
     // blur_rect is outermost.
@@ -439,30 +465,17 @@
       math::Vector2dF blur_extent(data.shadow.BlurExtent());
       blur_rect.Outset(blur_extent.x(), blur_extent.y());
       draw.reset(new DrawRectShadowBlur(graphics_state_, draw_state_,
-          data.rect, blur_rect, spread_rect, shadow_color, data.rect,
+          data.rect, blur_rect, spread_rect, shadow_color,
           data.shadow.blur_sigma, data.inset));
-      onscreen_type = DrawObjectManager::kOnscreenRectShadowBlur;
     } else {
       draw.reset(new DrawRectShadowSpread(graphics_state_, draw_state_,
-          data.rect, spread_rect, shadow_color, spread_rect, data.rect));
+          data.rect, spread_rect, shadow_color, spread_rect));
     }
     node_bounds.Union(blur_rect);
   }
 
-  // Include or exclude scissor will touch these pixels.
-  node_bounds.Union(data.rect);
-
-  // Since the depth buffer is polluted to create a stencil for pixels to be
-  // modified by the shadow, this draw must occur during the transparency
-  // pass. During this pass, all subsequent draws are guaranteed to be closer
-  // (i.e. pass the depth test) than pixels modified by previous transparency
-  // draws.
-  AddTransparentDraw(draw.Pass(), onscreen_type,
-      DrawObjectManager::kOffscreenNone, node_bounds);
-
-  // Since the box shadow draw objects use the depth stencil object, two depth
-  // values were used. So skip an additional depth value.
-  draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
+  // Transparency is used to skip pixels that are not shadowed.
+  AddTransparentDraw(draw.Pass(), node_bounds);
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) {
@@ -470,13 +483,29 @@
     return;
   }
 
-  FallbackRasterize(text_node, DrawObjectManager::kOffscreenSkiaText);
+  FallbackRasterize(text_node);
 }
 
-void RenderTreeNodeVisitor::FallbackRasterize(
+// Get an offscreen target to render |node|.
+// |out_content_cached| is true if the node's contents are already cached in
+//   the returned offscreen target.
+// |out_target_info| describes the offscreen surface into which |node| should
+//   be rendered.
+// |out_content_rect| is the onscreen rect (already in screen space) where the
+//   offscreen contents should be rendered.
+void RenderTreeNodeVisitor::GetOffscreenTarget(
     scoped_refptr<render_tree::Node> node,
-    DrawObjectManager::OffscreenType offscreen_type) {
-  DCHECK_NE(offscreen_type, DrawObjectManager::kOffscreenNone);
+    bool* out_content_cached,
+    OffscreenTargetManager::TargetInfo* out_target_info,
+    math::RectF* out_content_rect) {
+  // Default to telling the caller that nothing should be rendered.
+  *out_content_cached = true;
+  out_content_rect->SetRect(0, 0, 0, 0);
+
+  if (!allow_offscreen_targets_) {
+    failed_offscreen_target_request_ = true;
+    return;
+  }
 
   math::RectF node_bounds(node->GetBounds());
   math::RectF mapped_bounds(draw_state_.transform.MapRect(node_bounds));
@@ -484,14 +513,8 @@
     return;
   }
 
-  // Use the fallback rasterizer to render the tree. In order to preserve
-  // sharpness, let the fallback rasterizer handle the current transform.
-  math::Matrix3F old_transform = draw_state_.transform;
-  draw_state_.transform = math::Matrix3F::Identity();
-
   // Request a slightly larger render target than the calculated bounds. The
-  // fallback rasterizer may use an extra pixel along the edge of anti-aliased
-  // objects.
+  // rasterizer may use an extra pixel along the edge of anti-aliased objects.
   const float kBorderWidth = 1.0f;
 
   // The render target cache keys off the render_tree Node and target size.
@@ -502,23 +525,21 @@
   float offset_x = std::floor(mapped_bounds.x() + 0.5f);
   float offset_y = std::floor(mapped_bounds.y() + 0.5f);
   math::PointF content_offset(
-      // Shift contents towards the origin of the render target.
-      kBorderWidth + kFractionPad - offset_x,
-      kBorderWidth + kFractionPad - offset_y);
+      offset_x - kBorderWidth - kFractionPad,
+      offset_y - kBorderWidth - kFractionPad);
   math::SizeF content_size(
       std::ceil(mapped_bounds.width() + 2.0f * kBorderWidth + kFractionPad),
       std::ceil(mapped_bounds.height() + 2.0f * kBorderWidth + kFractionPad));
-  OffscreenTargetManager::TargetInfo target_info;
-  bool is_cached = offscreen_target_manager_->GetCachedOffscreenTarget(node,
-      content_size, &target_info);
-  if (!is_cached) {
+  *out_content_cached = offscreen_target_manager_->GetCachedOffscreenTarget(
+      node, content_size, out_target_info);
+  if (!(*out_content_cached)) {
     offscreen_target_manager_->AllocateOffscreenTarget(node,
-        content_size, &target_info);
+        content_size, out_target_info);
   }
 
-  // If the render target is the scratch surface, then just render what fits
-  // onscreen for better performance.
-  if (target_info.is_scratch_surface) {
+  // If no offscreen target was available, then set the content_rect as if
+  // rendering will occur on the current render target.
+  if (out_target_info->framebuffer == nullptr) {
     mapped_bounds.Outset(kBorderWidth, kBorderWidth);
     mapped_bounds.Intersect(draw_state_.scissor);
     if (mapped_bounds.IsEmpty()) {
@@ -528,60 +549,193 @@
     float right = std::ceil(mapped_bounds.right());
     float top = std::floor(mapped_bounds.y());
     float bottom = std::ceil(mapped_bounds.bottom());
-    content_offset.SetPoint(-left, -top);
+    content_offset.SetPoint(left, top);
     content_size.SetSize(right - left, bottom - top);
+  } else {
+    DCHECK_LE(content_size.width(), out_target_info->region.width());
+    DCHECK_LE(content_size.height(), out_target_info->region.height());
   }
 
-  // The returned target may be larger than actually needed. Clamp to just the
-  // needed size.
-  DCHECK_LE(content_size.width(), target_info.region.width());
-  DCHECK_LE(content_size.height(), target_info.region.height());
-  target_info.region.set_size(content_size);
-  math::RectF draw_rect(-content_offset.x(), -content_offset.y(),
-      content_size.width(), content_size.height());
+  out_target_info->region.set_size(content_size);
 
-  // Setup draw callbacks as needed.
-  base::Closure draw_offscreen;
-  base::Closure draw_onscreen;
-  if (!is_cached) {
-    // Pre-translate the contents so it starts near the origin.
-    math::Matrix3F content_transform(old_transform);
-    content_transform(0, 2) += content_offset.x();
-    content_transform(1, 2) += content_offset.y();
+  out_content_rect->set_origin(content_offset);
+  out_content_rect->set_size(content_size);
+}
 
-    if (target_info.is_scratch_surface) {
-      draw_onscreen = base::Bind(*fallback_rasterize_,
-          scoped_refptr<render_tree::Node>(node), content_transform,
-          target_info);
-    } else {
-      draw_offscreen = base::Bind(*fallback_rasterize_,
-          scoped_refptr<render_tree::Node>(node), content_transform,
-          target_info);
-    }
+void RenderTreeNodeVisitor::FallbackRasterize(
+    scoped_refptr<render_tree::Node> node) {
+  OffscreenTargetManager::TargetInfo target_info;
+  math::RectF content_rect;
+  bool content_is_cached = false;
+  GetOffscreenTarget(node, &content_is_cached, &target_info, &content_rect);
+
+  if (content_rect.IsEmpty()) {
+    return;
   }
 
+  // If no offscreen target was available, then just render directly onto the
+  // current render target.
+  if (target_info.framebuffer == nullptr) {
+    base::Closure rasterize_callback = base::Bind(fallback_rasterize_,
+        node, fallback_render_target_, draw_state_.transform, content_rect,
+        draw_state_.opacity, kFallbackShouldFlush);
+    scoped_ptr<DrawObject> draw(new DrawCallback(rasterize_callback));
+    AddExternalDraw(draw.Pass(), content_rect, node->GetTypeId());
+    return;
+  }
+
+  // Setup draw for the contents as needed.
+  if (!content_is_cached) {
+    FallbackRasterize(node, target_info, content_rect);
+  }
+
+  // Sub-pixel offsets are passed to the fallback rasterizer to preserve
+  // sharpness. The results should be drawn to |content_rect| which is already
+  // in screen space.
+  math::Matrix3F old_transform = draw_state_.transform;
+  draw_state_.transform = math::Matrix3F::Identity();
+
   // Create the appropriate draw object to call the draw callback, then render
-  // its results onscreen.
+  // its results onscreen. A transparent draw must be used even if the current
+  // opacity is 100% because the contents may have transparency.
   backend::TextureEGL* texture = target_info.framebuffer->GetColorTexture();
   math::Matrix3F texcoord_transform = GetTexcoordTransform(target_info);
   if (draw_state_.opacity == 1.0f) {
     scoped_ptr<DrawObject> draw(new DrawRectTexture(graphics_state_,
-        draw_state_, draw_rect, texture, texcoord_transform,
-        draw_offscreen, draw_onscreen));
-    AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenRectTexture,
-        offscreen_type, draw_rect);
+        draw_state_, content_rect, texture, texcoord_transform));
+    AddTransparentDraw(draw.Pass(), content_rect);
   } else {
     scoped_ptr<DrawObject> draw(new DrawRectColorTexture(graphics_state_,
-        draw_state_, draw_rect, render_tree::ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
-        texture, texcoord_transform, draw_offscreen, draw_onscreen));
-    AddTransparentDraw(draw.Pass(),
-        DrawObjectManager::kOnscreenRectColorTexture, offscreen_type,
-        draw_rect);
+        draw_state_, content_rect, kOpaqueWhite, texture, texcoord_transform,
+        false /* clamp_texcoords */));
+    AddTransparentDraw(draw.Pass(), content_rect);
   }
 
   draw_state_.transform = old_transform;
 }
 
+void RenderTreeNodeVisitor::FallbackRasterize(
+    scoped_refptr<render_tree::Node> node,
+    const OffscreenTargetManager::TargetInfo& target_info,
+    const math::RectF& content_rect) {
+  // It is not permitted to render to an offscreen target while already
+  // rendering to an offscreen target. To allow this path, ensure that
+  // render targets are not used as both the read and write targets for any
+  // call. (Although these reads and writes should occur in different regions
+  // of the target, not all drivers may handle this properly.) Also ensure the
+  // draw object manager will sort these draws properly.
+  DCHECK(!render_target_is_offscreen_);
+
+  uint32_t rasterize_flags = 0;
+
+  // Pre-translate the content so it starts in target_info.region.
+  math::Matrix3F content_transform =
+      math::TranslateMatrix(target_info.region.x() - content_rect.x(),
+                            target_info.region.y() - content_rect.y()) *
+      draw_state_.transform;
+  base::Closure rasterize_callback = base::Bind(fallback_rasterize_,
+      node, target_info.skia_canvas, content_transform, target_info.region,
+      draw_state_.opacity, rasterize_flags);
+  scoped_ptr<DrawObject> draw(new DrawCallback(rasterize_callback));
+
+  backend::RenderTarget* old_render_target = render_target_;
+  bool old_render_target_is_offscreen = render_target_is_offscreen_;
+
+  render_target_ = target_info.framebuffer;
+  render_target_is_offscreen_ = true;
+  AddExternalDraw(draw.Pass(), target_info.region, node->GetTypeId());
+
+  render_target_ = old_render_target;
+  render_target_is_offscreen_ = old_render_target_is_offscreen;
+}
+
+// Add draw objects to render |node| to an offscreen render target.
+// |out_texture| and |out_texcoord_transform| describe the texture subregion
+//   that will contain the result of rendering |node|. If not enough memory
+//   is available for the offscreen target, then |out_texture| will be null.
+// |out_content_rect| describes the onscreen rect (in screen space) which
+//   should be used to render node's contents.
+void RenderTreeNodeVisitor::OffscreenRasterize(
+    scoped_refptr<render_tree::Node> node,
+    const backend::TextureEGL** out_texture,
+    math::Matrix3F* out_texcoord_transform,
+    math::RectF* out_content_rect) {
+  OffscreenTargetManager::TargetInfo target_info;
+  bool content_is_cached = false;
+  GetOffscreenTarget(node, &content_is_cached, &target_info, out_content_rect);
+
+  if (out_content_rect->IsEmpty()) {
+    return;
+  }
+
+  if (target_info.framebuffer == nullptr) {
+    // No offscreen target was available.
+    *out_texture = nullptr;
+    return;
+  }
+
+  *out_texture = target_info.framebuffer->GetColorTexture();
+  *out_texcoord_transform = GetTexcoordTransform(target_info);
+
+  if (!content_is_cached) {
+    // Try to use the native rasterizer to handle the offscreen rendering.
+    // However, because offscreen targets are actually regions of a texture
+    // atlas, some drivers may not properly handle reading and writing to
+    // the same texture -- even if the operations occur in different regions.
+    // So native offscreen handling can only occur if |node| or its children
+    // do not also need offscreen targets (or this particular target). The
+    // alternative is to use the fallback rasterizer since it allocates its
+    // own render targets as needed.
+    //
+    // Ideally, pre-check |node| and its children to see if they will need
+    // an offscreen target. However, this would result in a lot of duplicate
+    // code. So just process the nodes into draws, and if any of them requests
+    // an offscreen target, then remove all the recently added draws, and use
+    // the fallback rasterizer instead of the native rasterizer.
+    uint32_t last_valid_draw_id = last_draw_id_;
+    bool old_allow_offscreen_targets = allow_offscreen_targets_;
+    bool old_failed_offscreen_target_request = failed_offscreen_target_request_;
+    allow_offscreen_targets_ = false;
+    failed_offscreen_target_request_ = false;
+
+    // Push a new render state to rasterize to the offscreen render target.
+    DrawObject::BaseState old_draw_state = draw_state_;
+    SkCanvas* old_fallback_render_target = fallback_render_target_;
+    backend::RenderTarget* old_render_target = render_target_;
+    bool old_render_target_is_offscreen = render_target_is_offscreen_;
+
+    // Adjust the transform to render into target_info.region.
+    draw_state_.transform =
+        math::TranslateMatrix(target_info.region.x() - out_content_rect->x(),
+                              target_info.region.y() - out_content_rect->y()) *
+        draw_state_.transform;
+    draw_state_.scissor = RoundRectFToInt(target_info.region);
+    draw_state_.opacity = 1.0f;
+    fallback_render_target_ = target_info.skia_canvas;
+    render_target_ = target_info.framebuffer;
+    render_target_is_offscreen_ = true;
+
+    node->Accept(this);
+
+    draw_state_ = old_draw_state;
+    fallback_render_target_ = old_fallback_render_target;
+    render_target_ = old_render_target;
+    render_target_is_offscreen_ = old_render_target_is_offscreen;
+
+    bool use_fallback_rasterizer = failed_offscreen_target_request_;
+    allow_offscreen_targets_ = old_allow_offscreen_targets;
+    failed_offscreen_target_request_ = old_failed_offscreen_target_request;
+
+    if (use_fallback_rasterizer) {
+      // The node or one of its children needed an offscreen target, so this
+      // cannot be rendered natively. Remove all the draws added for |node|,
+      // and just use the fallback rasterizer instead.
+      draw_object_manager_->RemoveDraws(last_valid_draw_id);
+      FallbackRasterize(node, target_info, *out_content_rect);
+    }
+  }
+}
+
 bool RenderTreeNodeVisitor::IsVisible(const math::RectF& bounds) {
   math::RectF intersection = IntersectRects(
       draw_state_.transform.MapRect(bounds), draw_state_.scissor);
@@ -589,20 +743,44 @@
 }
 
 void RenderTreeNodeVisitor::AddOpaqueDraw(scoped_ptr<DrawObject> object,
-    DrawObjectManager::OnscreenType onscreen_type,
-    DrawObjectManager::OffscreenType offscreen_type) {
-  draw_object_manager_->AddOpaqueDraw(object.Pass(), onscreen_type,
-      offscreen_type);
-  draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
+    const math::RectF& local_bounds) {
+  base::TypeId draw_type = object->GetTypeId();
+  if (render_target_is_offscreen_) {
+    last_draw_id_ = draw_object_manager_->AddOffscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendNone, draw_type, render_target_,
+        draw_state_.transform.MapRect(local_bounds));
+  } else {
+    last_draw_id_ = draw_object_manager_->AddOnscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendNone, draw_type, render_target_,
+        draw_state_.transform.MapRect(local_bounds));
+  }
 }
 
 void RenderTreeNodeVisitor::AddTransparentDraw(scoped_ptr<DrawObject> object,
-    DrawObjectManager::OnscreenType onscreen_type,
-    DrawObjectManager::OffscreenType offscreen_type,
     const math::RectF& local_bounds) {
-  draw_object_manager_->AddTransparentDraw(object.Pass(), onscreen_type,
-      offscreen_type, draw_state_.transform.MapRect(local_bounds));
-  draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
+  base::TypeId draw_type = object->GetTypeId();
+  if (render_target_is_offscreen_) {
+    last_draw_id_ = draw_object_manager_->AddOffscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendSrcAlpha, draw_type, render_target_,
+        draw_state_.transform.MapRect(local_bounds));
+  } else {
+    last_draw_id_ = draw_object_manager_->AddOnscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendSrcAlpha, draw_type, render_target_,
+        draw_state_.transform.MapRect(local_bounds));
+  }
+}
+
+void RenderTreeNodeVisitor::AddExternalDraw(scoped_ptr<DrawObject> object,
+    const math::RectF& world_bounds, base::TypeId draw_type) {
+  if (render_target_is_offscreen_) {
+    last_draw_id_ = draw_object_manager_->AddOffscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendExternal, draw_type, render_target_,
+        world_bounds);
+  } else {
+    last_draw_id_ = draw_object_manager_->AddOnscreenDraw(object.Pass(),
+        DrawObjectManager::kBlendExternal, draw_type, render_target_,
+        world_bounds);
+  }
 }
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
index c4e70d3..51044f8 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
@@ -33,10 +33,12 @@
 #include "cobalt/render_tree/rect_shadow_node.h"
 #include "cobalt/render_tree/text_node.h"
 #include "cobalt/renderer/backend/egl/texture.h"
+#include "cobalt/renderer/backend/render_target.h"
 #include "cobalt/renderer/rasterizer/egl/draw_object.h"
 #include "cobalt/renderer/rasterizer/egl/draw_object_manager.h"
 #include "cobalt/renderer/rasterizer/egl/graphics_state.h"
 #include "cobalt/renderer/rasterizer/egl/offscreen_target_manager.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 
 namespace cobalt {
 namespace renderer {
@@ -47,16 +49,23 @@
 // DrawObjects which must then be processed using calls to ExecuteDraw.
 class RenderTreeNodeVisitor : public render_tree::NodeVisitor {
  public:
+  enum FallbackRasterizeFlags {
+    kFallbackShouldClear = 1 << 0,
+    kFallbackShouldFlush = 1 << 1,
+  };
   typedef base::Callback<void(
       const scoped_refptr<render_tree::Node>& render_tree,
-      const math::Matrix3F& transform,
-      const OffscreenTargetManager::TargetInfo& target)>
+      SkCanvas* fallback_render_target, const math::Matrix3F& transform,
+      const math::RectF& scissor, float opacity, uint32_t rasterize_flags)>
       FallbackRasterizeFunction;
 
   RenderTreeNodeVisitor(GraphicsState* graphics_state,
                         DrawObjectManager* draw_object_manager,
                         OffscreenTargetManager* offscreen_target_manager,
-                        const FallbackRasterizeFunction* fallback_rasterize);
+                        const FallbackRasterizeFunction& fallback_rasterize,
+                        SkCanvas* fallback_render_target,
+                        backend::RenderTarget* render_target,
+                        const math::Rect& content_rect);
 
   void Visit(render_tree::animations::AnimateNode* /* animate */) OVERRIDE {
     NOTREACHED();
@@ -72,23 +81,41 @@
   void Visit(render_tree::TextNode* text_node) OVERRIDE;
 
  private:
+  void GetOffscreenTarget(scoped_refptr<render_tree::Node> node,
+                          bool* out_content_cached,
+                          OffscreenTargetManager::TargetInfo* out_target_info,
+                          math::RectF* out_content_rect);
+  void FallbackRasterize(scoped_refptr<render_tree::Node> node);
   void FallbackRasterize(scoped_refptr<render_tree::Node> node,
-                         DrawObjectManager::OffscreenType offscreen_type);
+                         const OffscreenTargetManager::TargetInfo& target_info,
+                         const math::RectF& content_rect);
+  void OffscreenRasterize(scoped_refptr<render_tree::Node> node,
+                          const backend::TextureEGL** out_texture,
+                          math::Matrix3F* out_texcoord_transform,
+                          math::RectF* out_content_rect);
+
   bool IsVisible(const math::RectF& bounds);
   void AddOpaqueDraw(scoped_ptr<DrawObject> object,
-                     DrawObjectManager::OnscreenType onscreen_type,
-                     DrawObjectManager::OffscreenType offscreen_type);
+                     const math::RectF& local_bounds);
   void AddTransparentDraw(scoped_ptr<DrawObject> object,
-                          DrawObjectManager::OnscreenType onscreen_type,
-                          DrawObjectManager::OffscreenType offscreen_type,
                           const math::RectF& local_bounds);
+  void AddExternalDraw(scoped_ptr<DrawObject> object,
+                       const math::RectF& world_bounds, base::TypeId draw_type);
 
   GraphicsState* graphics_state_;
   DrawObjectManager* draw_object_manager_;
   OffscreenTargetManager* offscreen_target_manager_;
-  const FallbackRasterizeFunction* fallback_rasterize_;
+  FallbackRasterizeFunction fallback_rasterize_;
 
   DrawObject::BaseState draw_state_;
+  SkCanvas* fallback_render_target_;
+  backend::RenderTarget* render_target_;
+  bool render_target_is_offscreen_;
+
+  bool allow_offscreen_targets_;
+  bool failed_offscreen_target_request_;
+
+  uint32_t last_draw_id_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_program.h b/src/cobalt/renderer/rasterizer/egl/shader_program.h
index 0abebbe..d21695b 100644
--- a/src/cobalt/renderer/rasterizer/egl/shader_program.h
+++ b/src/cobalt/renderer/rasterizer/egl/shader_program.h
@@ -16,6 +16,7 @@
 #define COBALT_RENDERER_RASTERIZER_EGL_SHADER_PROGRAM_H_
 
 #include "base/compiler_specific.h"
+#include "cobalt/base/type_id.h"
 #include "cobalt/renderer/rasterizer/egl/shader_base.h"
 
 namespace cobalt {
@@ -57,6 +58,9 @@
   const FragmentShader& GetFragmentShader() const {
     return fragment_shader_;
   }
+  static base::TypeId GetTypeId() {
+    return base::GetTypeId<ShaderProgram<VertexShader, FragmentShader> >();
+  }
 
  private:
   // Shader programs should only be created and destroyed by the
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
index a7831c4..9f04388 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl
@@ -1,13 +1,13 @@
 precision mediump float;

 uniform vec2 u_blur_radius;

 uniform vec2 u_scale_add;

-varying vec2 v_blur_position;   // Relative to the blur center.

+varying vec2 v_offset;   // Relative to the blur center.

 varying vec4 v_color;

 void main() {

   // Distance from the blur radius.

-  // Both v_blur_position and u_blur_radius are expressed in terms of the

+  // Both v_offset and u_blur_radius are expressed in terms of the

   //   blur sigma.

-  vec2 pos = abs(v_blur_position) - u_blur_radius;

+  vec2 pos = abs(v_offset) - u_blur_radius;

   vec2 pos2 = pos * pos;

   vec2 pos3 = pos2 * pos;

   vec4 posx = vec4(1.0, pos.x, pos2.x, pos3.x);

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl
new file mode 100644
index 0000000..ed660d6
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl
@@ -0,0 +1,8 @@
+precision mediump float;

+uniform vec4 u_include;   // include scissor (x_min, y_min, x_max, y_max)

+varying vec2 v_offset;

+varying vec4 v_color;

+void main() {

+  vec2 include = step(u_include.xy, v_offset) * step(v_offset, u_include.zw);

+  gl_FragColor = v_color * include.x * include.y;

+}

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
index 140b6b5..8738d12 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
@@ -22,10 +22,11 @@
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur.glsl',
+      '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_texcoord.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl',
-      '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_blur.glsl',
+      '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl',
     ],
   },
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl
index 5b6cd81..6a9389e 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl
@@ -1,11 +1,11 @@
 uniform vec4 u_clip_adjustment;

 uniform mat3 u_view_matrix;

-attribute vec3 a_position;

+attribute vec2 a_position;

 attribute vec4 a_color;

 varying vec4 v_color;

 void main() {

-  vec3 pos2d = u_view_matrix * vec3(a_position.xy, 1);

+  vec3 pos2d = u_view_matrix * vec3(a_position, 1);

   gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +

-                     u_clip_adjustment.zw, a_position.z, pos2d.z);

+                     u_clip_adjustment.zw, 0, pos2d.z);

   v_color = a_color;

 }

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_blur.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_blur.glsl
deleted file mode 100644
index 0b543d1..0000000
--- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_blur.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-uniform vec4 u_clip_adjustment;

-uniform mat3 u_view_matrix;

-attribute vec3 a_position;

-attribute vec4 a_color;

-attribute vec2 a_blur_position;

-varying vec4 v_color;

-varying vec2 v_blur_position;

-void main() {

-  vec3 pos2d = u_view_matrix * vec3(a_position.xy, 1);

-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +

-                     u_clip_adjustment.zw, a_position.z, pos2d.z);

-  v_color = a_color;

-  v_blur_position = a_blur_position;

-}

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl
new file mode 100644
index 0000000..879696f
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl
@@ -0,0 +1,14 @@
+uniform vec4 u_clip_adjustment;

+uniform mat3 u_view_matrix;

+attribute vec2 a_position;

+attribute vec4 a_color;

+attribute vec2 a_offset;

+varying vec4 v_color;

+varying vec2 v_offset;

+void main() {

+  vec3 pos2d = u_view_matrix * vec3(a_position, 1);

+  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +

+                     u_clip_adjustment.zw, 0, pos2d.z);

+  v_color = a_color;

+  v_offset = a_offset;

+}

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl
index c7df12c..ef26a5b 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl
@@ -1,14 +1,14 @@
 uniform vec4 u_clip_adjustment;

 uniform mat3 u_view_matrix;

-attribute vec3 a_position;

+attribute vec2 a_position;

 attribute vec4 a_color;

 attribute vec2 a_texcoord;

 varying vec4 v_color;

 varying vec2 v_texcoord;

 void main() {

-  vec3 pos2d = u_view_matrix * vec3(a_position.xy, 1);

+  vec3 pos2d = u_view_matrix * vec3(a_position, 1);

   gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +

-                     u_clip_adjustment.zw, a_position.z, pos2d.z);

+                     u_clip_adjustment.zw, 0, pos2d.z);

   v_color = a_color;

   v_texcoord = a_texcoord;

 }

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl
index bbbdbf4..1527625 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl
@@ -1,11 +1,11 @@
 uniform vec4 u_clip_adjustment;

 uniform mat3 u_view_matrix;

-attribute vec3 a_position;

+attribute vec2 a_position;

 attribute vec2 a_texcoord;

 varying vec2 v_texcoord;

 void main() {

-  vec3 pos2d = u_view_matrix * vec3(a_position.xy, 1);

+  vec3 pos2d = u_view_matrix * vec3(a_position, 1);

   gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +

-                     u_clip_adjustment.zw, a_position.z, pos2d.z);

+                     u_clip_adjustment.zw, 0, pos2d.z);

   v_texcoord = a_texcoord;

 }

diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index fb95695..9effe9e 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -475,6 +475,19 @@
   TestTree(new ImageNode(image));
 }
 
+TEST_F(PixelTest, SingleRGBAImageWithReflection) {
+  SizeF half_output_size = ScaleSize(output_surface_size(), 0.5f, 0.5f);
+  scoped_refptr<Image> image =
+      CreateColoredCheckersImage(GetResourceProvider(), output_surface_size());
+
+  TestTree(new MatrixTransformNode(
+      new ImageNode(image),
+      TranslateMatrix(half_output_size.width(), half_output_size.height()) *
+          ScaleMatrix(1.0f, -1.0f) *
+          TranslateMatrix(-half_output_size.width(),
+                          -half_output_size.height())));
+}
+
 TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners) {
   scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat(
       GetResourceProvider(), output_surface_size(),
@@ -1065,6 +1078,17 @@
                           -half_output_size.height())));
 }
 
+TEST_F(PixelTest, ThreePlaneYUVImageWithReflection) {
+  SizeF half_output_size = ScaleSize(output_surface_size(), 0.5f, 0.5f);
+  TestTree(new MatrixTransformNode(
+      new ImageNode(
+          MakeI420Image(GetResourceProvider(), output_surface_size())),
+      TranslateMatrix(half_output_size.width(), half_output_size.height()) *
+          ScaleMatrix(1.0f, -1.0f) *
+          TranslateMatrix(-half_output_size.width(),
+                          -half_output_size.height())));
+}
+
 // The software rasterizer does not support NV12 images.
 #if NV12_TEXTURE_SUPPORTED
 
@@ -2138,27 +2162,63 @@
                        20));
 }
 
+namespace {
+std::pair<PointF, PointF> LinearGradientPointsFromDegrees(
+    float ccw_degrees_from_right, const SizeF& frame_size, float frame_scale) {
+  float ccw_radians_from_right = static_cast<float>(
+      ccw_degrees_from_right * M_PI / 180.0f);
+
+  // Scale |frame_size| by |frame_scale|, and offset the source and destination
+  // points for the gradient so their midpoint coincides with the center of
+  // the original frame.
+  float offset_x = frame_size.width() * (1.0f - frame_scale) * 0.5f;
+  float offset_y = frame_size.height() * (1.0f - frame_scale) * 0.5f;
+  std::pair<PointF, PointF> source_and_dest =
+      render_tree::LinearGradientPointsFromAngle(ccw_radians_from_right,
+          ScaleSize(frame_size, frame_scale));
+  source_and_dest.first.Offset(offset_x, offset_y);
+  source_and_dest.second.Offset(offset_x, offset_y);
+  return source_and_dest;
+}
+
+RectF ScaledCenteredSurface(const SizeF& frame_size, float scale) {
+  math::PointF origin(frame_size.width() * (1.0f - scale) * 0.5f,
+                      frame_size.height() * (1.0f - scale) * 0.5f);
+  return math::RectF(origin, ScaleSize(frame_size, scale));
+}
+}  // namespace
+
 TEST_F(PixelTest, LinearGradient2StopsLeftRight) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new LinearGradientBrush(
-          PointF(0, 0), PointF(output_surface_size().width(), 0),
+          LinearGradientPointsFromDegrees(0, output_surface_size(), 0.9f),
           ColorRGBA(1.0, 0.0, 0.0, 1), ColorRGBA(0.0, 1.0, 0.0, 1)))));
 }
 
 TEST_F(PixelTest, LinearGradient2StopsTopBottom) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new LinearGradientBrush(
-          PointF(0, 0), PointF(0, output_surface_size().height()),
+          LinearGradientPointsFromDegrees(-90, output_surface_size(), 0.9f),
           ColorRGBA(1.0, 0.0, 0.0, 1), ColorRGBA(0.0, 1.0, 0.0, 1)))));
 }
 
-TEST_F(PixelTest, LinearGradient2StopsArbitraryAngle) {
+TEST_F(PixelTest, LinearGradient2Stops45Degrees) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new LinearGradientBrush(
-          PointF(30, 30), PointF(130, 180), ColorRGBA(1.0, 0.0, 0.0, 1),
+          LinearGradientPointsFromDegrees(45, output_surface_size(), 0.9f),
+          ColorRGBA(1.0, 0.0, 0.0, 1),
+          ColorRGBA(0.0, 1.0, 0.0, 1)))));
+}
+
+TEST_F(PixelTest, LinearGradient2Stops315Degrees) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(315, output_surface_size(), 0.9f),
+          ColorRGBA(1.0, 0.0, 0.0, 1),
           ColorRGBA(0.0, 1.0, 0.0, 1)))));
 }
 
@@ -2172,27 +2232,36 @@
 }
 }  // namespace
 
-TEST_F(PixelTest, LinearGradient3StopsLeftRight) {
-  TestTree(
-      new RectNode(RectF(output_surface_size()),
-                   scoped_ptr<Brush>(new LinearGradientBrush(
-                       PointF(0, 0), PointF(output_surface_size().width(), 0),
-                       Make3ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient3StopsLeftRightInset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(0, output_surface_size(), 0.8f),
+          Make3ColorEvenlySpacedStopList()))));
 }
 
-TEST_F(PixelTest, LinearGradient3StopsTopBottom) {
-  TestTree(
-      new RectNode(RectF(output_surface_size()),
-                   scoped_ptr<Brush>(new LinearGradientBrush(
-                       PointF(0, 0), PointF(0, output_surface_size().height()),
-                       Make3ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient3StopsTopBottomInset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(-90, output_surface_size(), 0.8f),
+          Make3ColorEvenlySpacedStopList()))));
 }
 
-TEST_F(PixelTest, LinearGradient3StopsArbitraryAngle) {
-  TestTree(new RectNode(RectF(output_surface_size()),
-                        scoped_ptr<Brush>(new LinearGradientBrush(
-                            PointF(30, 30), PointF(130, 180),
-                            Make3ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient3Stops30DegreesInset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(30, output_surface_size(), 0.8f),
+          Make3ColorEvenlySpacedStopList()))));
+}
+
+TEST_F(PixelTest, LinearGradient3Stops210DegreesInset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(210, output_surface_size(), 0.8f),
+          Make3ColorEvenlySpacedStopList()))));
 }
 
 namespace {
@@ -2207,27 +2276,36 @@
 }
 }  // namespace
 
-TEST_F(PixelTest, LinearGradient5StopsLeftRight) {
-  TestTree(
-      new RectNode(RectF(output_surface_size()),
-                   scoped_ptr<Brush>(new LinearGradientBrush(
-                       PointF(0, 0), PointF(output_surface_size().width(), 0),
-                       Make5ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient5StopsLeftRightOutset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(0, output_surface_size(), 1.0f),
+          Make5ColorEvenlySpacedStopList()))));
 }
 
-TEST_F(PixelTest, LinearGradient5StopsTopBottom) {
-  TestTree(
-      new RectNode(RectF(output_surface_size()),
-                   scoped_ptr<Brush>(new LinearGradientBrush(
-                       PointF(0, 0), PointF(0, output_surface_size().height()),
-                       Make5ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient5StopsTopBottomOutset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(-90, output_surface_size(), 1.0f),
+          Make5ColorEvenlySpacedStopList()))));
 }
 
-TEST_F(PixelTest, LinearGradient5StopsArbitraryAngle) {
-  TestTree(new RectNode(RectF(output_surface_size()),
-                        scoped_ptr<Brush>(new LinearGradientBrush(
-                            PointF(30, 30), PointF(130, 180),
-                            Make5ColorEvenlySpacedStopList()))));
+TEST_F(PixelTest, LinearGradient5Stops150DegreesOutset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(150, output_surface_size(), 1.0f),
+          Make5ColorEvenlySpacedStopList()))));
+}
+
+TEST_F(PixelTest, LinearGradient5Stops330DegreesOutset) {
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new LinearGradientBrush(
+          LinearGradientPointsFromDegrees(330, output_surface_size(), 1.0f),
+          Make5ColorEvenlySpacedStopList()))));
 }
 
 TEST_F(PixelTest, LinearGradientWithTransparencyOnWhiteBackground) {
@@ -2253,29 +2331,30 @@
 }
 
 TEST_F(PixelTest, RadialGradient2Stops) {
-  TestTree(new RectNode(RectF(output_surface_size()),
-                        scoped_ptr<Brush>(new RadialGradientBrush(
-                            PointF(75, 100), 75, ColorRGBA(1.0, 0.0, 0.0, 1),
-                            ColorRGBA(0.0, 1.0, 0.0, 1)))));
+  TestTree(new RectNode(
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
+      scoped_ptr<Brush>(new RadialGradientBrush(
+          PointF(75, 100), 75, ColorRGBA(1.0, 0.0, 0.0, 1),
+          ColorRGBA(0.0, 1.0, 0.0, 1)))));
 }
 
 TEST_F(PixelTest, RadialGradient3Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 75, Make3ColorEvenlySpacedStopList()))));
 }
 
 TEST_F(PixelTest, RadialGradient5Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 75, Make5ColorEvenlySpacedStopList()))));
 }
 
 TEST_F(PixelTest, VerticalEllipseGradient2Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(PointF(75, 100), 75, 100,
                                                 ColorRGBA(1.0, 0.0, 0.0, 1),
                                                 ColorRGBA(0.0, 1.0, 0.0, 1)))));
@@ -2283,21 +2362,21 @@
 
 TEST_F(PixelTest, VerticalEllipseGradient3Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 75, 100, Make3ColorEvenlySpacedStopList()))));
 }
 
 TEST_F(PixelTest, VerticalEllipseGradient5Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 75, 100, Make5ColorEvenlySpacedStopList()))));
 }
 
 TEST_F(PixelTest, HorizontalEllipseGradient2Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(PointF(75, 100), 100, 75,
                                                 ColorRGBA(1.0, 0.0, 0.0, 1),
                                                 ColorRGBA(0.0, 1.0, 0.0, 1)))));
@@ -2305,7 +2384,7 @@
 
 TEST_F(PixelTest, HorizontalEllipseGradient3Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 100, 75, Make3ColorEvenlySpacedStopList()))));
 }
@@ -2332,7 +2411,7 @@
 
 TEST_F(PixelTest, HorizontalEllipseGradient5Stops) {
   TestTree(new RectNode(
-      RectF(output_surface_size()),
+      ScaledCenteredSurface(output_surface_size(), 0.9f),
       scoped_ptr<Brush>(new RadialGradientBrush(
           PointF(75, 100), 100, 75, Make5ColorEvenlySpacedStopList()))));
 }
@@ -3297,6 +3376,17 @@
   TestTree(new ImageNode(offscreen_image));
 }
 
+// Tests that offscreen rendering works fine with YUV images.
+TEST_F(PixelTest, DrawOffscreenYUVImage) {
+  scoped_refptr<Image> image =
+      MakeI420Image(GetResourceProvider(), output_surface_size());
+
+  scoped_refptr<Image> offscreen_rendered_image =
+      GetResourceProvider()->DrawOffscreenImage(new ImageNode(image));
+
+  TestTree(new ImageNode(offscreen_rendered_image));
+}
+
 #if defined(ENABLE_MAP_TO_MESH)
 
 namespace {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.h b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
index eb9bb45..d0bff4f 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
@@ -215,6 +215,17 @@
     return planes_[plane_index];
   }
 
+  // Always fallback to custom non-skia code for rendering multi-plane images.
+  // The main reason to unconditionally fallback here is because Skia does a
+  // check internally to see if GL_RED is supported, and if so it will use
+  // GL_RED for 1-channel textures, and if not it will use GL_ALPHA for
+  // 1-channel textures.  If we want to create textures like this manually (and
+  // later wrap it into a Skia texture), we must know in advance which GL format
+  // to set it up as, but Skia's GL_RED support decision is private information
+  // that we can't access.  So, we choose instead to just not rely on Skia
+  // for this so that we don't have to worry about format mismatches.
+  bool CanRenderInSkia() const OVERRIDE { return false; }
+
   bool EnsureInitialized() OVERRIDE;
 
  private:
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index f88b6ee..78c8f60 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -32,6 +32,7 @@
 #include "cobalt/renderer/rasterizer/skia/surface_cache_delegate.h"
 #include "cobalt/renderer/rasterizer/skia/vertex_buffer_object.h"
 #include "third_party/glm/glm/gtc/matrix_inverse.hpp"
+#include "third_party/glm/glm/gtx/transform.hpp"
 #include "third_party/glm/glm/mat3x3.hpp"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkSurface.h"
@@ -73,6 +74,9 @@
       const scoped_refptr<render_tree::Node>& render_tree,
       const scoped_refptr<backend::RenderTarget>& render_target);
 
+  SkCanvas* GetCanvasFromRenderTarget(
+      const scoped_refptr<backend::RenderTarget>& render_target);
+
   render_tree::ResourceProvider* GetResourceProvider();
   GrContext* GetGrContext();
 
@@ -102,10 +106,8 @@
       const math::Size& size);
 
   void RasterizeRenderTreeToCanvas(
-      const scoped_refptr<render_tree::Node>& render_tree, SkCanvas* canvas);
-
-  SkCanvas* GetCanvasFromRenderTarget(
-      const scoped_refptr<backend::RenderTarget>& render_target);
+      const scoped_refptr<render_tree::Node>& render_tree, SkCanvas* canvas,
+      GrSurfaceOrigin origin);
 
   void ResetSkiaState();
 
@@ -131,6 +133,12 @@
   base::optional<common::SurfaceCache> surface_cache_;
 
   base::optional<egl::TexturedMeshRenderer> textured_mesh_renderer_;
+
+  // Valid only for the duration of a call to RasterizeRenderTreeToCanvas().
+  // Useful for directing textured_mesh_renderer_ on whether to flip its y-axis
+  // or not since Skia does not let us pull that information out of the
+  // SkCanvas object (which Skia would internally use to get this information).
+  base::optional<GrSurfaceOrigin> current_surface_origin_;
 };
 
 namespace {
@@ -166,7 +174,17 @@
   return skia_desc;
 }
 
+glm::mat4 ModelViewMatrixSurfaceOriginAdjustment(
+    GrSurfaceOrigin origin) {
+  if (origin == kTopLeft_GrSurfaceOrigin) {
+    return glm::scale(glm::vec3(1.0f, -1.0f, 1.0f));
+  } else {
+    return glm::mat4(1.0f);
+  }
+}
+
 glm::mat4 GetFallbackTextureModelViewProjectionMatrix(
+    GrSurfaceOrigin origin,
     const SkISize& canvas_size, const SkMatrix& total_matrix,
     const math::RectF& destination_rect) {
   // We define a transformation from GLES normalized device coordinates (e.g.
@@ -193,7 +211,9 @@
 
   // Since these matrices are applied in LIFO order, read the followin inlined
   // comments in reverse order.
-  return
+  glm::mat4 result =
+      // Flip the y axis depending on the destination surface's origin.
+      ModelViewMatrixSurfaceOriginAdjustment(origin) *
       // Finally transform back into normalized device coordinates so that
       // GL can digest the results.
       glm::affineInverse(gl_norm_coords_to_skia_canvas_coords) *
@@ -206,6 +226,8 @@
       // referenced by the RenderQuad() function will have its positions defined
       // within (e.g. [-1, 1]).
       gl_norm_coords_to_skia_canvas_coords;
+
+  return result;
 }
 
 // For stereoscopic video, the actual video is split (either horizontally or
@@ -301,7 +323,13 @@
   return result;
 }
 
-void SetupGLStateForImageRender(Image* image) {
+enum FaceOrientation {
+  FaceOrientation_Ccw,
+  FaceOrientation_Cw,
+};
+
+void SetupGLStateForImageRender(Image* image,
+                                FaceOrientation face_orientation) {
   if (image->IsOpaque()) {
     GL_CALL(glDisable(GL_BLEND));
   } else {
@@ -310,9 +338,25 @@
   GL_CALL(glDisable(GL_DEPTH_TEST));
   GL_CALL(glDisable(GL_STENCIL_TEST));
   GL_CALL(glEnable(GL_SCISSOR_TEST));
-  GL_CALL(glEnable(GL_CULL_FACE));
   GL_CALL(glCullFace(GL_BACK));
   GL_CALL(glFrontFace(GL_CCW));
+  if (face_orientation == FaceOrientation_Ccw) {
+    GL_CALL(glEnable(GL_CULL_FACE));
+  } else {
+    // Unfortunately, some GLES implementations (like software Mesa) have a
+    // problem with flipping glCullFrace() from GL_BACK to GL_FRONT, they seem
+    // to ignore it.  We need to render back faces though if the face
+    // orientation is flipped, so the only compatible solution is to disable
+    // back-face culling.
+    GL_CALL(glDisable(GL_CULL_FACE));
+  }
+}
+
+FaceOrientation GetFaceOrientationFromModelViewProjectionMatrix(
+    const glm::mat4& model_view_projection_matrix) {
+  return glm::determinant(model_view_projection_matrix) >= 0 ?
+             FaceOrientation_Ccw :
+             FaceOrientation_Cw;
 }
 
 }  // namespace
@@ -341,10 +385,21 @@
   // corner origin.
   GL_CALL(glScissor(
       canvas_boundsi.x(),
-      canvas_size.height() - canvas_boundsi.height() - canvas_boundsi.y(),
+      *current_surface_origin_ == kBottomLeft_GrSurfaceOrigin ?
+          canvas_size.height() - canvas_boundsi.height() - canvas_boundsi.y() :
+          canvas_boundsi.y(),
       canvas_boundsi.width(), canvas_boundsi.height()));
 
-  SetupGLStateForImageRender(image);
+  glm::mat4 model_view_projection_matrix =
+      GetFallbackTextureModelViewProjectionMatrix(
+          *current_surface_origin_,
+          canvas_size, draw_state->render_target->getTotalMatrix(),
+          image_node->data().destination_rect);
+
+  SetupGLStateForImageRender(
+      image,
+      GetFaceOrientationFromModelViewProjectionMatrix(
+          model_view_projection_matrix));
 
   if (!textured_mesh_renderer_) {
     textured_mesh_renderer_.emplace(graphics_context_);
@@ -353,9 +408,7 @@
   // Invoke our TexturedMeshRenderer to actually perform the draw call.
   textured_mesh_renderer_->RenderQuad(
       SkiaImageToTexturedMeshRendererImage(image, render_tree::kMono),
-      GetFallbackTextureModelViewProjectionMatrix(
-          canvas_size, draw_state->render_target->getTotalMatrix(),
-          image_node->data().destination_rect));
+      model_view_projection_matrix);
 
   // Let Skia know that we've modified GL state.
   uint32_t untouched_states =
@@ -387,7 +440,14 @@
   GL_CALL(glViewport(0, 0, canvas_size.width(), canvas_size.height()));
   GL_CALL(glScissor(0, 0, canvas_size.width(), canvas_size.height()));
 
-  SetupGLStateForImageRender(image);
+  glm::mat4 model_view_projection_matrix =
+      ModelViewMatrixSurfaceOriginAdjustment(*current_surface_origin_) *
+      draw_state->transform_3d;
+
+  SetupGLStateForImageRender(
+      image,
+      GetFaceOrientationFromModelViewProjectionMatrix(
+          model_view_projection_matrix));
 
   if (!textured_mesh_renderer_) {
     textured_mesh_renderer_.emplace(graphics_context_);
@@ -403,7 +463,7 @@
       mono_vbo->GetHandle(), mono_vbo->GetVertexCount(),
       mono_vbo->GetDrawMode(),
       SkiaImageToTexturedMeshRendererImage(image, mesh_filter.stereo_mode()),
-      draw_state->transform_3d);
+      model_view_projection_matrix);
 
   // Let Skia know that we've modified GL state.
   gr_context_->resetContext();
@@ -538,7 +598,7 @@
   }
 
   // Rasterize the passed in render tree to our hardware render target.
-  RasterizeRenderTreeToCanvas(render_tree, canvas);
+  RasterizeRenderTreeToCanvas(render_tree, canvas, kBottomLeft_GrSurfaceOrigin);
 
   {
     TRACE_EVENT0("cobalt::renderer", "Skia Flush");
@@ -552,7 +612,7 @@
 void HardwareRasterizer::Impl::SubmitOffscreen(
     const scoped_refptr<render_tree::Node>& render_tree, SkCanvas* canvas) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  RasterizeRenderTreeToCanvas(render_tree, canvas);
+  RasterizeRenderTreeToCanvas(render_tree, canvas, kBottomLeft_GrSurfaceOrigin);
 }
 
 void HardwareRasterizer::Impl::SubmitOffscreenToRenderTarget(
@@ -573,7 +633,7 @@
   canvas->clear(SkColorSetARGB(0, 0, 0, 0));
 
   // Render to the canvas and clean up.
-  RasterizeRenderTreeToCanvas(render_tree, canvas);
+  RasterizeRenderTreeToCanvas(render_tree, canvas, kTopLeft_GrSurfaceOrigin);
   canvas->flush();
   sk_output_surface->unref();
 }
@@ -679,11 +739,15 @@
 }
 
 void HardwareRasterizer::Impl::RasterizeRenderTreeToCanvas(
-    const scoped_refptr<render_tree::Node>& render_tree, SkCanvas* canvas) {
+    const scoped_refptr<render_tree::Node>& render_tree, SkCanvas* canvas,
+    GrSurfaceOrigin origin) {
   TRACE_EVENT0("cobalt::renderer", "RasterizeRenderTreeToCanvas");
   // TODO: This trace uses the name in the current benchmark to keep it work as
   // expected. Remove after switching to webdriver benchmark.
   TRACE_EVENT0("cobalt::renderer", "VisitRenderTree");
+
+  current_surface_origin_.emplace(origin);
+
   RenderTreeNodeVisitor::CreateScratchSurfaceFunction
       create_scratch_surface_function =
           base::Bind(&HardwareRasterizer::Impl::CreateScratchSurface,
@@ -700,6 +764,8 @@
       surface_cache_ ? &surface_cache_.value() : NULL);
   DCHECK(render_tree);
   render_tree->Accept(&visitor);
+
+  current_surface_origin_ = base::nullopt;
 }
 
 void HardwareRasterizer::Impl::ResetSkiaState() { gr_context_->resetContext(); }
@@ -732,6 +798,11 @@
   impl_->SubmitOffscreen(render_tree, canvas);
 }
 
+SkCanvas* HardwareRasterizer::GetCachedCanvas(
+    const scoped_refptr<backend::RenderTarget>& render_target) {
+  return impl_->GetCanvasFromRenderTarget(render_target);
+}
+
 void HardwareRasterizer::AdvanceFrame() {
   impl_->AdvanceFrame();
 }
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
index aba9fd4..268d03c 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
@@ -68,6 +68,12 @@
   void SubmitOffscreen(const scoped_refptr<render_tree::Node>& render_tree,
                        SkCanvas* canvas);
 
+  // Get the cached canvas for a render target that would normally go through
+  // Submit(). The cache size is limited, so this should not be used for
+  // generic offscreen render targets.
+  SkCanvas* GetCachedCanvas(
+      const scoped_refptr<backend::RenderTarget>& render_target);
+
   // If Submit() is not called, then use this function to tell rasterizer that
   // a frame has been submitted.
   void AdvanceFrame();
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 847f2b5..77e0de7 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -86,11 +86,7 @@
   // Wait for any resource-related to complete (by waiting for all tasks to
   // complete).
   if (MessageLoop::current() != self_message_loop_) {
-    base::WaitableEvent completion(true, false);
-    self_message_loop_->PostTask(FROM_HERE,
-                                 base::Bind(&base::WaitableEvent::Signal,
-                                            base::Unretained(&completion)));
-    completion.Wait();
+    self_message_loop_->WaitForFence();
   }
 }
 
diff --git a/src/cobalt/renderer/rasterizer/skia/image.h b/src/cobalt/renderer/rasterizer/skia/image.h
index 39aaf3f..453e730 100644
--- a/src/cobalt/renderer/rasterizer/skia/image.h
+++ b/src/cobalt/renderer/rasterizer/skia/image.h
@@ -60,6 +60,12 @@
                                           int source_pitch_in_bytes,
                                           render_tree::PixelFormat pixel_format,
                                           const uint8_t* source_pixels);
+
+  // While of course most skia::Image objects can be rendered in Skia, sometimes
+  // this is not true, such as when they are backed by SbDecodeTarget objects
+  // that assume a specific rasterizer such as GLES2.  In this case, we can
+  // fallback to a rasterizer-provided renderer function.
+  virtual bool CanRenderInSkia() const { return true; }
 };
 
 // A single-plane image is an image where all data to describe a single pixel
@@ -79,12 +85,6 @@
   // If not-null, indicates a rectangle within the image in which the valid
   // pixel data is to be found.
   virtual const math::Rect* GetContentRegion() const { return NULL; }
-
-  // While of course most skia::Image objects can be rendered in Skia, sometimes
-  // this is not true, such as when they are backed by SbDecodeTarget objects
-  // that assume a specific rasterizer such as GLES2.  In this case, we can
-  // fallback to a rasterizer-provided renderer function.
-  virtual bool CanRenderInSkia() const { return true; }
 };
 
 // A multi-plane image is one where different channels may have different planes
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 f8b34ab..0f2159b 100644
--- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -696,23 +696,23 @@
 
   // We issue different skia rasterization commands to render the image
   // depending on whether it's single or multi planed.
-  if (image->GetTypeId() == base::GetTypeId<SinglePlaneImage>()) {
-    SinglePlaneImage* single_plane_image =
-        base::polymorphic_downcast<SinglePlaneImage*>(image);
+  if (!image->CanRenderInSkia()) {
+    render_image_fallback_function_.Run(image_node, &draw_state_);
+  } else {
+    if (image->GetTypeId() == base::GetTypeId<SinglePlaneImage>()) {
+      SinglePlaneImage* single_plane_image =
+          base::polymorphic_downcast<SinglePlaneImage*>(image);
 
-    if (!single_plane_image->CanRenderInSkia()) {
-      render_image_fallback_function_.Run(image_node, &draw_state_);
-    } else {
       RenderSinglePlaneImage(single_plane_image, &draw_state_,
                              image_node->data().destination_rect,
                              &(image_node->data().local_transform));
+    } else if (image->GetTypeId() == base::GetTypeId<MultiPlaneImage>()) {
+      RenderMultiPlaneImage(base::polymorphic_downcast<MultiPlaneImage*>(image),
+                            &draw_state_, image_node->data().destination_rect,
+                            &(image_node->data().local_transform));
+    } else {
+      NOTREACHED();
     }
-  } else if (image->GetTypeId() == base::GetTypeId<MultiPlaneImage>()) {
-    RenderMultiPlaneImage(base::polymorphic_downcast<MultiPlaneImage*>(image),
-                          &draw_state_, image_node->data().destination_rect,
-                          &(image_node->data().local_transform));
-  } else {
-    NOTREACHED();
   }
 
 #if ENABLE_FLUSH_AFTER_EVERY_NODE
diff --git a/src/cobalt/renderer/rasterizer/testdata/DrawOffscreenYUVImage-expected.png b/src/cobalt/renderer/rasterizer/testdata/DrawOffscreenYUVImage-expected.png
new file mode 100644
index 0000000..94d8db6
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/DrawOffscreenYUVImage-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient2Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient2Stops-expected.png
index cf6a82e..8d03f81 100644
--- a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient2Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient2Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient3Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient3Stops-expected.png
index 08755a5..0dfa9d3 100644
--- a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient3Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient3Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient5Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient5Stops-expected.png
index c69c5ed..0ae6de9 100644
--- a/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient5Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/HorizontalEllipseGradient5Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops315Degrees-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops315Degrees-expected.png
new file mode 100644
index 0000000..fd3b33d
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops315Degrees-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops45Degrees-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops45Degrees-expected.png
new file mode 100644
index 0000000..1c04513
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2Stops45Degrees-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsArbitraryAngle-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsArbitraryAngle-expected.png
deleted file mode 100644
index 16b7f1b..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsArbitraryAngle-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsLeftRight-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsLeftRight-expected.png
index 5ca9d51..bbe0bf0 100644
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsLeftRight-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsLeftRight-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsTopBottom-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsTopBottom-expected.png
index b5a2e93..8e5000d 100644
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsTopBottom-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient2StopsTopBottom-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops210DegreesInset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops210DegreesInset-expected.png
new file mode 100644
index 0000000..7ca5cd0
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops210DegreesInset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops30DegreesInset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops30DegreesInset-expected.png
new file mode 100644
index 0000000..3d5a3b5
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3Stops30DegreesInset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsArbitraryAngle-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsArbitraryAngle-expected.png
deleted file mode 100644
index 3fdd4f3..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsArbitraryAngle-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRight-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRight-expected.png
deleted file mode 100644
index e728fed..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRight-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRightInset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRightInset-expected.png
new file mode 100644
index 0000000..934dfca
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsLeftRightInset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottom-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottom-expected.png
deleted file mode 100644
index 9bcbc01..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottom-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottomInset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottomInset-expected.png
new file mode 100644
index 0000000..2f998ce
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient3StopsTopBottomInset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops150DegreesOutset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops150DegreesOutset-expected.png
new file mode 100644
index 0000000..1917730
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops150DegreesOutset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops330DegreesOutset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops330DegreesOutset-expected.png
new file mode 100644
index 0000000..a4ddeb5
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5Stops330DegreesOutset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsArbitraryAngle-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsArbitraryAngle-expected.png
deleted file mode 100644
index bc936c2..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsArbitraryAngle-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRight-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRight-expected.png
deleted file mode 100644
index cc2bbf8..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRight-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRightOutset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRightOutset-expected.png
new file mode 100644
index 0000000..9c380e3
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsLeftRightOutset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottom-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottom-expected.png
deleted file mode 100644
index 8a4b0c4..0000000
--- a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottom-expected.png
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottomOutset-expected.png b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottomOutset-expected.png
new file mode 100644
index 0000000..a61d06c
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LinearGradient5StopsTopBottomOutset-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/RadialGradient2Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/RadialGradient2Stops-expected.png
index 52c0789..6d34fad 100644
--- a/src/cobalt/renderer/rasterizer/testdata/RadialGradient2Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/RadialGradient2Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/RadialGradient3Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/RadialGradient3Stops-expected.png
index e0e4204..a1468e3 100644
--- a/src/cobalt/renderer/rasterizer/testdata/RadialGradient3Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/RadialGradient3Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/RadialGradient5Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/RadialGradient5Stops-expected.png
index 7ba845e..2152126 100644
--- a/src/cobalt/renderer/rasterizer/testdata/RadialGradient5Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/RadialGradient5Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithReflection-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithReflection-expected.png
new file mode 100644
index 0000000..9100c4a
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithReflection-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/ThreePlaneYUVImageWithReflection-expected.png b/src/cobalt/renderer/rasterizer/testdata/ThreePlaneYUVImageWithReflection-expected.png
new file mode 100644
index 0000000..4c07e04
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/ThreePlaneYUVImageWithReflection-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient2Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient2Stops-expected.png
index db6e775..fd1e097 100644
--- a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient2Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient2Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient3Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient3Stops-expected.png
index 544b88e..8f4522d 100644
--- a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient3Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient3Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient5Stops-expected.png b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient5Stops-expected.png
index b06e3d6..b19c591 100644
--- a/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient5Stops-expected.png
+++ b/src/cobalt/renderer/rasterizer/testdata/VerticalEllipseGradient5Stops-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp
index 3a92967..c57e6f7 100644
--- a/src/cobalt/renderer/renderer.gyp
+++ b/src/cobalt/renderer/renderer.gyp
@@ -22,6 +22,8 @@
       'target_name': 'renderer',
       'type': 'static_library',
       'sources': [
+        'fps_overlay.cc',
+        'fps_overlay.h',
         'pipeline.cc',
         'pipeline.h',
         'renderer_module.cc',
diff --git a/src/cobalt/renderer/renderer_module.cc b/src/cobalt/renderer/renderer_module.cc
index 084390e..a983e1b 100644
--- a/src/cobalt/renderer/renderer_module.cc
+++ b/src/cobalt/renderer/renderer_module.cc
@@ -25,7 +25,9 @@
 
 RendererModule::Options::Options()
     : skia_glyph_texture_atlas_dimensions(2048, 2048),
-      purge_skia_font_caches_on_destruction(true) {
+      purge_skia_font_caches_on_destruction(true),
+      enable_fps_stdout(false),
+      enable_fps_overlay(false) {
   // Call into platform-specific code for setting up render module options.
   SetPerPlatformDefaultOptions();
 }
@@ -76,12 +78,16 @@
   // Direct it to render directly to the display.
   {
     TRACE_EVENT0("cobalt::renderer", "new renderer::Pipeline()");
+    renderer::Pipeline::Options pipeline_options;
+    pipeline_options.enable_fps_stdout = options_.enable_fps_stdout;
+    pipeline_options.enable_fps_overlay = options_.enable_fps_overlay;
+
     pipeline_ = make_scoped_ptr(new renderer::Pipeline(
         base::Bind(options_.create_rasterizer_function, graphics_context_.get(),
                    options_),
         display_->GetRenderTarget(), graphics_context_.get(),
         options_.submit_even_if_render_tree_is_unchanged,
-        renderer::Pipeline::kClearToBlack));
+        renderer::Pipeline::kClearToBlack, pipeline_options));
   }
 }
 
diff --git a/src/cobalt/renderer/renderer_module.h b/src/cobalt/renderer/renderer_module.h
index 70d0364..49a3187 100644
--- a/src/cobalt/renderer/renderer_module.h
+++ b/src/cobalt/renderer/renderer_module.h
@@ -87,6 +87,9 @@
     // transforms needed to render the scene from the camera's view.
     GetCameraTransformCallback get_camera_transform;
 
+    bool enable_fps_stdout;
+    bool enable_fps_overlay;
+
    private:
     // Implemented per-platform, and allows each platform to customize
     // the renderer options.
@@ -105,6 +108,14 @@
     return display_->GetRenderTarget();
   }
 
+  render_tree::ResourceProvider* resource_provider() {
+    if (!pipeline_) {
+      return NULL;
+    }
+
+    return pipeline_->GetResourceProvider();
+  }
+
  private:
   system_window::SystemWindow* system_window_;
   Options options_;
diff --git a/src/cobalt/renderer/renderer_parameters_setup.gypi b/src/cobalt/renderer/renderer_parameters_setup.gypi
index ce438b5..6bc5d4f 100644
--- a/src/cobalt/renderer/renderer_parameters_setup.gypi
+++ b/src/cobalt/renderer/renderer_parameters_setup.gypi
@@ -31,7 +31,6 @@
     ['rasterizer_type == "direct-gles"', {
       'defines': [
         'COBALT_FORCE_DIRECT_GLES_RASTERIZER',
-        'COBALT_RASTERIZER_USES_DEPTH_BUFFER',
       ],
     }],
   ],
diff --git a/src/cobalt/script/javascript_engine.h b/src/cobalt/script/javascript_engine.h
index 39f9c0d..4ea8881 100644
--- a/src/cobalt/script/javascript_engine.h
+++ b/src/cobalt/script/javascript_engine.h
@@ -50,6 +50,12 @@
   static scoped_ptr<JavaScriptEngine> CreateEngine(
       const Options& options = Options());
 
+  // Updates the memory usage and returns the total memory that is reserved
+  // across all of the engines. This includes the part that is actually occupied
+  // by JS objects, and the part that is not yet.
+  // This function is defined per-implementation.
+  static size_t UpdateMemoryStatsAndReturnReserved();
+
   // Create a new JavaScript global object proxy.
   virtual scoped_refptr<GlobalEnvironment> CreateGlobalEnvironment() = 0;
 
@@ -60,11 +66,6 @@
   // Javascript object. This may mean collection needs to happen sooner.
   virtual void ReportExtraMemoryCost(size_t bytes) = 0;
 
-  // Updates the memory usage and returns the total memory that is reserved by
-  // the engine. This includes the part that is actually occupied by JS objects,
-  // and the part that is not yet.
-  virtual size_t UpdateMemoryStatsAndReturnReserved() = 0;
-
   // Installs an ErrorHandler for listening to javascript errors.
   // Returns true if the error handler could be installed. False otherwise.
   virtual bool RegisterErrorHandler(ErrorHandler handler) = 0;
diff --git a/src/cobalt/script/mozjs-45/conversion_helpers.h b/src/cobalt/script/mozjs-45/conversion_helpers.h
index c8e4c93..c23b660 100644
--- a/src/cobalt/script/mozjs-45/conversion_helpers.h
+++ b/src/cobalt/script/mozjs-45/conversion_helpers.h
@@ -489,6 +489,14 @@
   out_value.setObject(*object);
 }
 
+// raw object pointer -> JSValue
+template <typename T>
+inline void ToJSValue(JSContext* context, T* in_object,
+                      JS::MutableHandleValue out_value) {
+  TRACK_MEMORY_SCOPE("Javascript");
+  ToJSValue(context, scoped_refptr<T>(in_object), out_value);
+}
+
 // JSValue -> object
 template <typename T>
 inline void FromJSValue(JSContext* context, JS::HandleValue value,
@@ -509,25 +517,24 @@
     return;
   }
   DCHECK(js_object);
-  if (js::IsProxy(js_object)) {
-    JS::RootedObject wrapper(context, js::GetProxyTargetObject(js_object));
-    MozjsGlobalEnvironment* global_environment =
-        static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
-    const WrapperFactory* wrapper_factory =
-        global_environment->wrapper_factory();
-    if (wrapper_factory->IsWrapper(wrapper)) {
-      bool object_implements_interface =
-          wrapper_factory->DoesObjectImplementInterface(js_object,
-                                                        base::GetTypeId<T>());
-      if (!object_implements_interface) {
-        exception_state->SetSimpleException(kDoesNotImplementInterface);
-        return;
-      }
-      WrapperPrivate* wrapper_private =
-          WrapperPrivate::GetFromWrapperObject(wrapper);
-      *out_object = wrapper_private->wrappable<T>();
+  JS::RootedObject wrapper(context, js::IsProxy(js_object)
+                                        ? js::GetProxyTargetObject(js_object)
+                                        : js_object);
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  const WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (wrapper_factory->IsWrapper(wrapper)) {
+    bool object_implements_interface =
+        wrapper_factory->DoesObjectImplementInterface(js_object,
+                                                      base::GetTypeId<T>());
+    if (!object_implements_interface) {
+      exception_state->SetSimpleException(kDoesNotImplementInterface);
       return;
     }
+    WrapperPrivate* wrapper_private =
+        WrapperPrivate::GetFromWrapperObject(wrapper);
+    *out_object = wrapper_private->wrappable<T>();
+    return;
   }
   // This is not a platform object. Return a type error.
   exception_state->SetSimpleException(kDoesNotImplementInterface);
diff --git a/src/cobalt/script/mozjs-45/mozjs_engine.cc b/src/cobalt/script/mozjs-45/mozjs_engine.cc
index e1d482d..6882f71 100644
--- a/src/cobalt/script/mozjs-45/mozjs_engine.cc
+++ b/src/cobalt/script/mozjs-45/mozjs_engine.cc
@@ -26,6 +26,7 @@
 #include "starboard/once.h"
 #include "third_party/mozjs-45/js/public/Initialization.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
+#include "third_party/mozjs-45/js/src/vm/Runtime.h"
 
 namespace cobalt {
 namespace script {
@@ -79,38 +80,35 @@
                      StaticMemorySingletonTraits<EngineStats> >::get();
   }
 
-  void EngineCreated() {
-    base::AutoLock auto_lock(lock_);
-    ++engine_count_;
-  }
-
-  void EngineDestroyed() {
-    base::AutoLock auto_lock(lock_);
-    --engine_count_;
-  }
+  void EngineCreated() { ++engine_count_; }
+  void EngineDestroyed() { --engine_count_; }
 
   size_t UpdateMemoryStatsAndReturnReserved() {
-    base::AutoLock auto_lock(lock_);
-    if (engine_count_.value() == 0) {
-      return 0u;
-    }
+    // Accessing CVals triggers a lock, so rely on local variables when
+    // possible to avoid unecessary locking.
+    size_t allocated_memory =
+        MemoryAllocatorReporter::Get()->GetCurrentBytesAllocated();
+    size_t mapped_memory =
+        MemoryAllocatorReporter::Get()->GetCurrentBytesMapped();
 
-    return 0u;
+    allocated_memory_ = allocated_memory;
+    mapped_memory_ = mapped_memory;
+
+    return allocated_memory + mapped_memory;
   }
 
  private:
-  base::Lock lock_;
-  base::CVal<size_t, base::CValPublic> allocated_memory_;
-  base::CVal<size_t, base::CValPublic> mapped_memory_;
-  base::CVal<size_t> engine_count_;
+  base::CVal<int> engine_count_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic> allocated_memory_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic> mapped_memory_;
 };
 
 EngineStats::EngineStats()
-    : allocated_memory_("Memory.JS.AllocatedMemory", 0,
+    : engine_count_("Count.JS.Engine", 0,
+                    "Total JavaScript engine registered."),
+      allocated_memory_("Memory.JS.AllocatedMemory", 0,
                         "JS memory occupied by the Mozjs allocator."),
-      mapped_memory_("Memory.JS.MappedMemory", 0, "JS mapped memory."),
-      engine_count_("Count.JS.Engine", 0,
-                    "Total JavaScript engine registered.") {}
+      mapped_memory_("Memory.JS.MappedMemory", 0, "JS mapped memory.") {}
 
 // Pretend we always preserve wrappers since we never call
 // SetPreserveWrapperCallback anywhere else. This is necessary for
@@ -126,6 +124,7 @@
 void CallShutDown(void*) { JS_ShutDown(); }
 
 void CallInitAndRegisterShutDownOnce() {
+  js::DisableExtraThreads();
   const bool js_init_result = JS_Init();
   CHECK(js_init_result);
   base::AtExitManager::RegisterCallback(CallShutDown, NULL);
@@ -231,10 +230,6 @@
   }
 }
 
-size_t MozjsEngine::UpdateMemoryStatsAndReturnReserved() {
-  return EngineStats::GetInstance()->UpdateMemoryStatsAndReturnReserved();
-}
-
 bool MozjsEngine::RegisterErrorHandler(JavaScriptEngine::ErrorHandler handler) {
   error_handler_ = handler;
   return true;
@@ -331,5 +326,10 @@
   return make_scoped_ptr<JavaScriptEngine>(new mozjs::MozjsEngine(moz_options));
 }
 
+size_t JavaScriptEngine::UpdateMemoryStatsAndReturnReserved() {
+  return mozjs::EngineStats::GetInstance()
+      ->UpdateMemoryStatsAndReturnReserved();
+}
+
 }  // namespace script
 }  // namespace cobalt
diff --git a/src/cobalt/script/mozjs-45/mozjs_engine.h b/src/cobalt/script/mozjs-45/mozjs_engine.h
index e4a3765..9adb4e2 100644
--- a/src/cobalt/script/mozjs-45/mozjs_engine.h
+++ b/src/cobalt/script/mozjs-45/mozjs_engine.h
@@ -39,7 +39,6 @@
   scoped_refptr<GlobalEnvironment> CreateGlobalEnvironment() OVERRIDE;
   void CollectGarbage() OVERRIDE;
   void ReportExtraMemoryCost(size_t bytes) OVERRIDE;
-  size_t UpdateMemoryStatsAndReturnReserved() OVERRIDE;
   bool RegisterErrorHandler(JavaScriptEngine::ErrorHandler handler) OVERRIDE;
 
  private:
diff --git a/src/cobalt/script/mozjs-45/native_promise.h b/src/cobalt/script/mozjs-45/native_promise.h
index cc4be5d..fbe330c 100644
--- a/src/cobalt/script/mozjs-45/native_promise.h
+++ b/src/cobalt/script/mozjs-45/native_promise.h
@@ -169,6 +169,19 @@
   out_value.setObjectOrNull(native_promise->promise());
 }
 
+// Explicitly defer to the const version here so that a more generic non-const
+// version of this function does not get called instead, in the case that
+// |promise_holder| is not const.
+template <typename T>
+inline void ToJSValue(JSContext* context,
+                      ScriptValue<Promise<T> >* promise_holder,
+                      JS::MutableHandleValue out_value) {
+  TRACK_MEMORY_SCOPE("Javascript");
+  ToJSValue(context,
+            const_cast<const ScriptValue<Promise<T> >*>(promise_holder),
+            out_value);
+}
+
 // Destroys |promise_holder| as soon as the conversion is done.
 // This is useful when a wrappable is not interested in retaining a reference
 // to a promise, typically when a promise is resolved or rejected synchronously.
diff --git a/src/cobalt/script/mozjs-45/wrapper_factory.cc b/src/cobalt/script/mozjs-45/wrapper_factory.cc
index 9395b73..dd429b9 100644
--- a/src/cobalt/script/mozjs-45/wrapper_factory.cc
+++ b/src/cobalt/script/mozjs-45/wrapper_factory.cc
@@ -64,7 +64,10 @@
 }
 
 bool WrapperFactory::IsWrapper(JS::HandleObject wrapper) const {
-  return JS_GetPrivate(wrapper) != NULL;
+  // If the object doesn't have a wrapper private, it means that it is not a
+  // platform object.
+  return (JS_GetClass(wrapper)->flags & JSCLASS_HAS_PRIVATE) &&
+         JS_GetPrivate(wrapper) != NULL;
 }
 
 scoped_ptr<Wrappable::WeakWrapperHandle> WrapperFactory::CreateWrapper(
diff --git a/src/cobalt/script/mozjs/conversion_helpers.h b/src/cobalt/script/mozjs/conversion_helpers.h
index 0aa9192..bb5a90d 100644
--- a/src/cobalt/script/mozjs/conversion_helpers.h
+++ b/src/cobalt/script/mozjs/conversion_helpers.h
@@ -471,6 +471,14 @@
   out_value.set(OBJECT_TO_JSVAL(object));
 }
 
+// raw object pointer -> JSValue
+template <typename T>
+inline void ToJSValue(JSContext* context, T* in_object,
+                      JS::MutableHandleValue out_value) {
+  TRACK_MEMORY_SCOPE("Javascript");
+  ToJSValue(context, scoped_refptr<T>(in_object), out_value);
+}
+
 // JSValue -> object
 template <typename T>
 inline void FromJSValue(JSContext* context, JS::HandleValue value,
@@ -491,25 +499,24 @@
     return;
   }
   DCHECK(js_object);
-  if (js::IsProxy(js_object)) {
-    JS::RootedObject wrapper(context, js::GetProxyTargetObject(js_object));
-    MozjsGlobalEnvironment* global_environment =
-        static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
-    const WrapperFactory* wrapper_factory =
-        global_environment->wrapper_factory();
-    if (wrapper_factory->IsWrapper(wrapper)) {
-      bool object_implements_interface =
-          wrapper_factory->DoesObjectImplementInterface(js_object,
-                                                        base::GetTypeId<T>());
-      if (!object_implements_interface) {
-        exception_state->SetSimpleException(kDoesNotImplementInterface);
-        return;
-      }
-      WrapperPrivate* wrapper_private =
-          WrapperPrivate::GetFromWrapperObject(wrapper);
-      *out_object = wrapper_private->wrappable<T>();
+  JS::RootedObject wrapper(context, js::IsProxy(js_object)
+                                        ? js::GetProxyTargetObject(js_object)
+                                        : js_object);
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  const WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (wrapper_factory->IsWrapper(wrapper)) {
+    bool object_implements_interface =
+        wrapper_factory->DoesObjectImplementInterface(js_object,
+                                                      base::GetTypeId<T>());
+    if (!object_implements_interface) {
+      exception_state->SetSimpleException(kDoesNotImplementInterface);
       return;
     }
+    WrapperPrivate* wrapper_private =
+        WrapperPrivate::GetFromWrapperObject(wrapper);
+    *out_object = wrapper_private->wrappable<T>();
+    return;
   }
   // This is not a platform object. Return a type error.
   exception_state->SetSimpleException(kDoesNotImplementInterface);
diff --git a/src/cobalt/script/mozjs/mozjs_engine.cc b/src/cobalt/script/mozjs/mozjs_engine.cc
index 7040fb3..0f1760e 100644
--- a/src/cobalt/script/mozjs/mozjs_engine.cc
+++ b/src/cobalt/script/mozjs/mozjs_engine.cc
@@ -55,41 +55,35 @@
                      StaticMemorySingletonTraits<EngineStats> >::get();
   }
 
-  void EngineCreated() {
-    base::AutoLock auto_lock(lock_);
-    ++engine_count_;
-  }
-
-  void EngineDestroyed() {
-    base::AutoLock auto_lock(lock_);
-    --engine_count_;
-  }
+  void EngineCreated() { ++engine_count_; }
+  void EngineDestroyed() { --engine_count_; }
 
   size_t UpdateMemoryStatsAndReturnReserved() {
-    base::AutoLock auto_lock(lock_);
-    if (engine_count_.value() == 0) {
-      return 0;
-    }
-    allocated_memory_ =
+    // Accessing CVals triggers a lock, so rely on local variables when
+    // possible to avoid unecessary locking.
+    size_t allocated_memory =
         MemoryAllocatorReporter::Get()->GetCurrentBytesAllocated();
-    mapped_memory_ = MemoryAllocatorReporter::Get()->GetCurrentBytesMapped();
-    return allocated_memory_ + mapped_memory_;
+    size_t mapped_memory =
+        MemoryAllocatorReporter::Get()->GetCurrentBytesMapped();
+
+    allocated_memory_ = allocated_memory;
+    mapped_memory_ = mapped_memory;
+
+    return allocated_memory + mapped_memory;
   }
 
  private:
-  base::Lock lock_;
-  base::CVal<size_t, base::CValPublic> allocated_memory_;
-  base::CVal<size_t, base::CValPublic> mapped_memory_;
-  base::CVal<size_t> engine_count_;
+  base::CVal<int> engine_count_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic> allocated_memory_;
+  base::CVal<base::cval::SizeInBytes, base::CValPublic> mapped_memory_;
 };
 
 EngineStats::EngineStats()
-    : allocated_memory_("Memory.JS.AllocatedMemory", 0,
+    : engine_count_("Count.JS.Engine", 0,
+                    "Total JavaScript engine registered."),
+      allocated_memory_("Memory.JS.AllocatedMemory", 0,
                         "JS memory occupied by the Mozjs allocator."),
-      mapped_memory_("Memory.JS.MappedMemory", 0, "JS mapped memory."),
-      engine_count_("Count.JS.Engine", 0,
-                    "Total JavaScript engine registered.") {
-}
+      mapped_memory_("Memory.JS.MappedMemory", 0, "JS mapped memory.") {}
 
 // Pretend we always preserve wrappers since we never call
 // SetPreserveWrapperCallback anywhere else. This is necessary for
@@ -182,10 +176,6 @@
   }
 }
 
-size_t MozjsEngine::UpdateMemoryStatsAndReturnReserved() {
-  return EngineStats::GetInstance()->UpdateMemoryStatsAndReturnReserved();
-}
-
 bool MozjsEngine::RegisterErrorHandler(JavaScriptEngine::ErrorHandler handler) {
   error_handler_ = handler;
   JSDebugErrorHook hook = ErrorHookCallback;
@@ -303,5 +293,10 @@
       new mozjs::MozjsEngine(moz_options));
 }
 
+size_t JavaScriptEngine::UpdateMemoryStatsAndReturnReserved() {
+  return mozjs::EngineStats::GetInstance()
+      ->UpdateMemoryStatsAndReturnReserved();
+}
+
 }  // namespace script
 }  // namespace cobalt
diff --git a/src/cobalt/script/mozjs/mozjs_engine.h b/src/cobalt/script/mozjs/mozjs_engine.h
index 3bd5235..5c3d460 100644
--- a/src/cobalt/script/mozjs/mozjs_engine.h
+++ b/src/cobalt/script/mozjs/mozjs_engine.h
@@ -41,7 +41,6 @@
 
   void CollectGarbage() OVERRIDE;
   void ReportExtraMemoryCost(size_t bytes) OVERRIDE;
-  size_t UpdateMemoryStatsAndReturnReserved() OVERRIDE;
   bool RegisterErrorHandler(JavaScriptEngine::ErrorHandler handler) OVERRIDE;
 
  private:
diff --git a/src/cobalt/script/mozjs/native_promise.h b/src/cobalt/script/mozjs/native_promise.h
index f184076..25f9e99 100644
--- a/src/cobalt/script/mozjs/native_promise.h
+++ b/src/cobalt/script/mozjs/native_promise.h
@@ -169,6 +169,19 @@
   out_value.setObjectOrNull(native_promise->promise());
 }
 
+// Explicitly defer to the const version here so that a more generic non-const
+// version of this function does not get called instead, in the case that
+// |promise_holder| is not const.
+template <typename T>
+inline void ToJSValue(JSContext* context,
+                      ScriptValue<Promise<T> >* promise_holder,
+                      JS::MutableHandleValue out_value) {
+  TRACK_MEMORY_SCOPE("Javascript");
+  ToJSValue(context,
+            const_cast<const ScriptValue<Promise<T> >*>(promise_holder),
+            out_value);
+}
+
 // Destroys |promise_holder| as soon as the conversion is done.
 // This is useful when a wrappable is not interested in retaining a reference
 // to a promise, typically when a promise is resolved or rejected synchronously.
diff --git a/src/cobalt/script/mozjs/wrapper_factory.cc b/src/cobalt/script/mozjs/wrapper_factory.cc
index c456283..649bb9a 100644
--- a/src/cobalt/script/mozjs/wrapper_factory.cc
+++ b/src/cobalt/script/mozjs/wrapper_factory.cc
@@ -64,7 +64,10 @@
 }
 
 bool WrapperFactory::IsWrapper(JS::HandleObject wrapper) const {
-  return JS_GetPrivate(wrapper) != NULL;
+  // If the object doesn't have a wrapper private, it means that it is not a
+  // platform object.
+  return (JS_GetClass(wrapper)->flags & JSCLASS_HAS_PRIVATE) &&
+         JS_GetPrivate(wrapper) != NULL;
 }
 
 scoped_ptr<Wrappable::WeakWrapperHandle> WrapperFactory::CreateWrapper(
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 1b49b80..23dedfd 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -290,14 +290,14 @@
 
   const char* speech_api_key = "";
 #if defined(OS_STARBOARD)
-#if SB_VERSION(2)
+#if SB_API_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  // SB_API_VERSION >= 2
 #endif  // defined(OS_STARBOARD)
 
   up_url = AppendQueryParameter(up_url, "key", speech_api_key);
diff --git a/src/cobalt/speech/speech.gyp b/src/cobalt/speech/speech.gyp
index c3b77f8..8b3e15b 100644
--- a/src/cobalt/speech/speech.gyp
+++ b/src/cobalt/speech/speech.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/speech/speech_configuration.h b/src/cobalt/speech/speech_configuration.h
index 0c421e3..8b9e0ba 100644
--- a/src/cobalt/speech/speech_configuration.h
+++ b/src/cobalt/speech/speech_configuration.h
@@ -19,14 +19,12 @@
 
 #if defined(OS_STARBOARD)
 #include "starboard/configuration.h"
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 #define SB_USE_SB_MICROPHONE 1
 #endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 #define SB_USE_SB_SPEECH_RECOGNIZER 1
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 #endif  // defined(OS_STARBOARD)
 
 #endif  // COBALT_SPEECH_SPEECH_CONFIGURATION_H_
diff --git a/src/cobalt/speech/speech_synthesis_utterance.cc b/src/cobalt/speech/speech_synthesis_utterance.cc
index e6d67f5..010dfab 100644
--- a/src/cobalt/speech/speech_synthesis_utterance.cc
+++ b/src/cobalt/speech/speech_synthesis_utterance.cc
@@ -20,7 +20,11 @@
 SpeechSynthesisUtterance::SpeechSynthesisUtterance()
     : volume_(1.0f), rate_(1.0f), pitch_(1.0f), pending_speak_(false) {}
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(const std::string& text)
-    : text_(text), pending_speak_(false) {}
+    : text_(text),
+      volume_(1.0f),
+      rate_(1.0f),
+      pitch_(1.0f),
+      pending_speak_(false) {}
 
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(
     const scoped_refptr<SpeechSynthesisUtterance>& utterance)
diff --git a/src/cobalt/storage/storage.gyp b/src/cobalt/storage/storage.gyp
index 994fe1c..94bfd46 100644
--- a/src/cobalt/storage/storage.gyp
+++ b/src/cobalt/storage/storage.gyp
@@ -16,7 +16,7 @@
   'includes': [ '../build/contents_dir.gypi' ],
 
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/storage/storage_manager.cc b/src/cobalt/storage/storage_manager.cc
index 0f4670f..ac4d7a2 100644
--- a/src/cobalt/storage/storage_manager.cc
+++ b/src/cobalt/storage/storage_manager.cc
@@ -416,12 +416,7 @@
   // it is possible that there are no flushes pending at this instant, but there
   // are tasks queued on |sql_message_loop_| that will begin a flush, and so
   // we make sure that these are executed first.
-  base::WaitableEvent current_queue_finished_event_(true, false);
-  sql_message_loop_->PostTask(
-      FROM_HERE,
-      base::Bind(&base::WaitableEvent::Signal,
-                 base::Unretained(&current_queue_finished_event_)));
-  current_queue_finished_event_.Wait();
+  sql_message_loop_->WaitForFence();
 
   // Now wait for all pending flushes to wrap themselves up.  This may involve
   // the savegame I/O thread and the SQL thread posting tasks to each other.
diff --git a/src/cobalt/streams/embedded_scripts/readable_stream.js b/src/cobalt/streams/embedded_scripts/readable_stream.js
index 6a27d8c..837f0b7 100644
--- a/src/cobalt/streams/embedded_scripts/readable_stream.js
+++ b/src/cobalt/streams/embedded_scripts/readable_stream.js
@@ -1,48 +1,20 @@
-'use strict';(function(global){const v8_InternalPackedArray=global.Array;function v8_createPrivateSymbol(x){return Symbol(x)}function v8_simpleBind(fn,obj){return fn.bind(obj)}const arraySlice=global.Array.prototype.slice;function v8_uncurryThis(fn){return function(obj){return fn.apply(obj,arraySlice.call(arguments,1))}}const v8_PromiseBase=global.Promise;const _promiseResolve=v8_createPrivateSymbol("[[Resolve]]");const _promiseReject=v8_createPrivateSymbol("[[Reject]]");function v8_Promise(){var that=
-this;v8_PromiseBase.call(this,function(resolve,reject){that[_promiseResolve]=resolve;that[_promiseReject]=reject})}v8_Promise.prototype=v8_PromiseBase.prototype;function v8_createPromise(){return new v8_Promise}function v8_isPromise(obj){return obj instanceof v8_Promise}function v8_resolvePromise(promise,value){promise[_promiseResolve](value)}function v8_rejectPromise(promise,reason){promise[_promiseReject](reason)}function v8_markPromiseAsHandled(promise){try{thenPromise(promise,undefined,()=>{})}catch(error){}}
-const QUEUE_MAX_ARRAY_SIZE=16384;class SimpleQueue{constructor(){this.front={elements:new v8_InternalPackedArray,next:undefined};this.back=this.front;this.cursor=0;this.size=0}get length(){return this.size}push(element){++this.size;if(this.back.elements.length===QUEUE_MAX_ARRAY_SIZE){const oldBack=this.back;this.back={elements:new v8_InternalPackedArray,next:undefined};oldBack.next=this.back}this.back.elements.push(element)}shift(){--this.size;if(this.front.elements.length===this.cursor){this.front=
-this.front.next;this.cursor=0}const element=this.front.elements[this.cursor];this.front.elements[this.cursor]=undefined;++this.cursor;return element}forEach(callback){let i=this.cursor;let node=this.front;let elements=node.elements;while(i!==elements.length||node.next!==undefined){if(i===elements.length){node=node.next;elements=node.elements;i=0}callback(elements[i]);++i}}peek(){if(this.front.elements.length===this.cursor)return this.front.next.elements[0];return this.front.elements[this.cursor]}}
-const streamErrors_illegalInvocation="Illegal invocation";const streamErrors_illegalConstructor="Illegal constructor";const streamErrors_invalidType="Invalid type is specified";const streamErrors_invalidSize="The return value of a queuing strategy's size function must be a finite, non-NaN, non-negative number";const streamErrors_sizeNotAFunction="A queuing strategy's size property must be a function";const streamErrors_invalidHWM="A queueing strategy's highWaterMark property must be a nonnegative, non-NaN number";
-const _reader=v8_createPrivateSymbol("[[reader]]");const _storedError=v8_createPrivateSymbol("[[storedError]]");const _controller=v8_createPrivateSymbol("[[controller]]");const _closedPromise=v8_createPrivateSymbol("[[closedPromise]]");const _ownerReadableStream=v8_createPrivateSymbol("[[ownerReadableStream]]");const _readRequests=v8_createPrivateSymbol("[[readRequests]]");const createWithExternalControllerSentinel=v8_createPrivateSymbol("flag for UA-created ReadableStream to pass");const _readableStreamBits=
-v8_createPrivateSymbol("bit field for [[state]] and [[disturbed]]");const DISTURBED=1;const STATE_MASK=6;const STATE_BITS_OFFSET=1;const STATE_READABLE=0;const STATE_CLOSED=1;const STATE_ERRORED=2;const _underlyingSource=v8_createPrivateSymbol("[[underlyingSource]]");const _controlledReadableStream=v8_createPrivateSymbol("[[controlledReadableStream]]");const _queue=v8_createPrivateSymbol("[[queue]]");const _totalQueuedSize=v8_createPrivateSymbol("[[totalQueuedSize]]");const _strategySize=v8_createPrivateSymbol("[[strategySize]]");
-const _strategyHWM=v8_createPrivateSymbol("[[strategyHWM]]");const _readableStreamDefaultControllerBits=v8_createPrivateSymbol("bit field for [[started]], [[closeRequested]], [[pulling]], [[pullAgain]]");const STARTED=1;const CLOSE_REQUESTED=2;const PULLING=4;const PULL_AGAIN=8;const EXTERNALLY_CONTROLLED=16;const undefined=global.undefined;const Infinity=global.Infinity;const defineProperty=global.Object.defineProperty;const hasOwnProperty=v8_uncurryThis(global.Object.hasOwnProperty);const callFunction=
-v8_uncurryThis(global.Function.prototype.call);const applyFunction=v8_uncurryThis(global.Function.prototype.apply);const TypeError=global.TypeError;const RangeError=global.RangeError;const Number=global.Number;const Number_isNaN=Number.isNaN;const Number_isFinite=Number.isFinite;const Promise=global.Promise;const thenPromise=v8_uncurryThis(Promise.prototype.then);const Promise_resolve=v8_simpleBind(Promise.resolve,Promise);const Promise_reject=v8_simpleBind(Promise.reject,Promise);const errCancelLockedStream=
-"Cannot cancel a readable stream that is locked to a reader";const errEnqueueCloseRequestedStream="Cannot enqueue a chunk into a readable stream that is closed or has been requested to be closed";const errCancelReleasedReader="This readable stream reader has been released and cannot be used to cancel its previous owner stream";const errReadReleasedReader="This readable stream reader has been released and cannot be used to read from its previous owner stream";const errCloseCloseRequestedStream="Cannot close a readable stream that has already been requested to be closed";
-const errEnqueueClosedStream="Cannot enqueue a chunk into a closed readable stream";const errEnqueueErroredStream="Cannot enqueue a chunk into an errored readable stream";const errCloseClosedStream="Cannot close a closed readable stream";const errCloseErroredStream="Cannot close an errored readable stream";const errErrorClosedStream="Cannot error a close readable stream";const errErrorErroredStream="Cannot error a readable stream that is already errored";const errGetReaderNotByteStream="This readable stream does not support BYOB readers";
-const errGetReaderBadMode='Invalid reader mode given: expected undefined or "byob"';const errReaderConstructorBadArgument="ReadableStreamReader constructor argument is not a readable stream";const errReaderConstructorStreamAlreadyLocked="ReadableStreamReader constructor can only accept readable streams that are not yet locked to a reader";const errReleaseReaderWithPendingRead="Cannot release a readable stream reader when it still has outstanding read() calls that have not yet settled";const errReleasedReaderClosedPromise=
-"This readable stream reader has been released and cannot be used to monitor the stream's state";const errTmplMustBeFunctionOrUndefined=(name)=>`${name} must be a function or undefined`;class ReadableStream{constructor(){const underlyingSource=arguments[0]===undefined?{}:arguments[0];const strategy=arguments[1]===undefined?{}:arguments[1];const size=strategy.size;let highWaterMark=strategy.highWaterMark;if(highWaterMark===undefined)highWaterMark=1;this[_readableStreamBits]=0;ReadableStreamSetState(this,
-STATE_READABLE);this[_reader]=undefined;this[_storedError]=undefined;this[_controller]=undefined;const type=underlyingSource.type;const typeString=String(type);if(typeString==="bytes")throw new RangeError("bytes type is not yet implemented");else if(type!==undefined)throw new RangeError(streamErrors_invalidType);this[_controller]=new ReadableStreamDefaultController(this,underlyingSource,size,highWaterMark,arguments[2])}get locked(){if(IsReadableStream(this)===false)throw new TypeError(streamErrors_illegalInvocation);
-return IsReadableStreamLocked(this)}cancel(reason){if(IsReadableStream(this)===false)return Promise_reject(new TypeError(streamErrors_illegalInvocation));if(IsReadableStreamLocked(this)===true)return Promise_reject(new TypeError(errCancelLockedStream));return ReadableStreamCancel(this,reason)}getReader({mode}={}){if(IsReadableStream(this)===false)throw new TypeError(streamErrors_illegalInvocation);if(mode==="byob")throw new TypeError(errGetReaderNotByteStream);if(mode===undefined)return AcquireReadableStreamDefaultReader(this);
-throw new RangeError(errGetReaderBadMode);}pipeThrough({writable,readable},options){throw new TypeError("pipeThrough not implemented");}pipeTo(dest,{preventClose,preventAbort,preventCancel}={}){throw new TypeError("pipeTo not implemented");}tee(){if(IsReadableStream(this)===false)throw new TypeError(streamErrors_illegalInvocation);return ReadableStreamTee(this)}}class ReadableStreamDefaultController{constructor(stream,underlyingSource,size,highWaterMark){if(IsReadableStream(stream)===false)throw new TypeError(streamErrors_illegalConstructor);
-if(stream[_controller]!==undefined)throw new TypeError(streamErrors_illegalConstructor);this[_controlledReadableStream]=stream;this[_underlyingSource]=underlyingSource;this[_queue]=new SimpleQueue;this[_totalQueuedSize]=0;this[_readableStreamDefaultControllerBits]=0;if(arguments[4]===createWithExternalControllerSentinel)this[_readableStreamDefaultControllerBits]|=EXTERNALLY_CONTROLLED;const normalizedStrategy=ValidateAndNormalizeQueuingStrategy(size,highWaterMark);this[_strategySize]=normalizedStrategy.size;
-this[_strategyHWM]=normalizedStrategy.highWaterMark;const controller=this;const startResult=CallOrNoop(underlyingSource,"start",this,"underlyingSource.start");thenPromise(Promise_resolve(startResult),()=>{controller[_readableStreamDefaultControllerBits]|=STARTED;ReadableStreamDefaultControllerCallPullIfNeeded(controller)},(r)=>{if(ReadableStreamGetState(stream)===STATE_READABLE)ReadableStreamDefaultControllerError(controller,r)})}get desiredSize(){if(IsReadableStreamDefaultController(this)===false)throw new TypeError(streamErrors_illegalInvocation);
-return ReadableStreamDefaultControllerGetDesiredSize(this)}close(){if(IsReadableStreamDefaultController(this)===false)throw new TypeError(streamErrors_illegalInvocation);const stream=this[_controlledReadableStream];if(this[_readableStreamDefaultControllerBits]&CLOSE_REQUESTED)throw new TypeError(errCloseCloseRequestedStream);const state=ReadableStreamGetState(stream);if(state===STATE_ERRORED)throw new TypeError(errCloseErroredStream);if(state===STATE_CLOSED)throw new TypeError(errCloseClosedStream);
-return ReadableStreamDefaultControllerClose(this)}enqueue(chunk){if(IsReadableStreamDefaultController(this)===false)throw new TypeError(streamErrors_illegalInvocation);const stream=this[_controlledReadableStream];if(this[_readableStreamDefaultControllerBits]&CLOSE_REQUESTED)throw new TypeError(errEnqueueCloseRequestedStream);const state=ReadableStreamGetState(stream);if(state===STATE_ERRORED)throw new TypeError(errEnqueueErroredStream);if(state===STATE_CLOSED)throw new TypeError(errEnqueueClosedStream);
-return ReadableStreamDefaultControllerEnqueue(this,chunk)}error(e){if(IsReadableStreamDefaultController(this)===false)throw new TypeError(streamErrors_illegalInvocation);const stream=this[_controlledReadableStream];const state=ReadableStreamGetState(stream);if(state===STATE_ERRORED)throw new TypeError(errErrorErroredStream);if(state===STATE_CLOSED)throw new TypeError(errErrorClosedStream);return ReadableStreamDefaultControllerError(this,e)}}function ReadableStreamDefaultControllerCancel(controller,
-reason){controller[_queue]=new SimpleQueue;const underlyingSource=controller[_underlyingSource];return PromiseCallOrNoop(underlyingSource,"cancel",reason,"underlyingSource.cancel")}function ReadableStreamDefaultControllerPull(controller){const stream=controller[_controlledReadableStream];if(controller[_queue].length>0){const chunk=DequeueValue(controller);if(controller[_readableStreamDefaultControllerBits]&CLOSE_REQUESTED&&controller[_queue].length===0)ReadableStreamClose(stream);else ReadableStreamDefaultControllerCallPullIfNeeded(controller);
-return Promise_resolve(CreateIterResultObject(chunk,false))}const pendingPromise=ReadableStreamAddReadRequest(stream);ReadableStreamDefaultControllerCallPullIfNeeded(controller);return pendingPromise}function ReadableStreamAddReadRequest(stream){const promise=v8_createPromise();stream[_reader][_readRequests].push(promise);return promise}class ReadableStreamDefaultReader{constructor(stream){if(IsReadableStream(stream)===false)throw new TypeError(errReaderConstructorBadArgument);if(IsReadableStreamLocked(stream)===
-true)throw new TypeError(errReaderConstructorStreamAlreadyLocked);ReadableStreamReaderGenericInitialize(this,stream);this[_readRequests]=new SimpleQueue}get closed(){if(IsReadableStreamDefaultReader(this)===false)return Promise_reject(new TypeError(streamErrors_illegalInvocation));return this[_closedPromise]}cancel(reason){if(IsReadableStreamDefaultReader(this)===false)return Promise_reject(new TypeError(streamErrors_illegalInvocation));const stream=this[_ownerReadableStream];if(stream===undefined)return Promise_reject(new TypeError(errCancelReleasedReader));
-return ReadableStreamReaderGenericCancel(this,reason)}read(){if(IsReadableStreamDefaultReader(this)===false)return Promise_reject(new TypeError(streamErrors_illegalInvocation));if(this[_ownerReadableStream]===undefined)return Promise_reject(new TypeError(errReadReleasedReader));return ReadableStreamDefaultReaderRead(this)}releaseLock(){if(IsReadableStreamDefaultReader(this)===false)throw new TypeError(streamErrors_illegalInvocation);const stream=this[_ownerReadableStream];if(stream===undefined)return undefined;
-if(this[_readRequests].length>0)throw new TypeError(errReleaseReaderWithPendingRead);ReadableStreamReaderGenericRelease(this)}}function ReadableStreamReaderGenericCancel(reader,reason){return ReadableStreamCancel(reader[_ownerReadableStream],reason)}function AcquireReadableStreamDefaultReader(stream){return new ReadableStreamDefaultReader(stream)}function ReadableStreamCancel(stream,reason){stream[_readableStreamBits]|=DISTURBED;const state=ReadableStreamGetState(stream);if(state===STATE_CLOSED)return Promise_resolve(undefined);
-if(state===STATE_ERRORED)return Promise_reject(stream[_storedError]);ReadableStreamClose(stream);const sourceCancelPromise=ReadableStreamDefaultControllerCancel(stream[_controller],reason);return thenPromise(sourceCancelPromise,()=>undefined)}function ReadableStreamDefaultControllerClose(controller){const stream=controller[_controlledReadableStream];controller[_readableStreamDefaultControllerBits]|=CLOSE_REQUESTED;if(controller[_queue].length===0)ReadableStreamClose(stream)}function ReadableStreamFulfillReadRequest(stream,
-chunk,done){const reader=stream[_reader];const readRequest=stream[_reader][_readRequests].shift();v8_resolvePromise(readRequest,CreateIterResultObject(chunk,done))}function ReadableStreamDefaultControllerEnqueue(controller,chunk){const stream=controller[_controlledReadableStream];if(IsReadableStreamLocked(stream)===true&&ReadableStreamGetNumReadRequests(stream)>0)ReadableStreamFulfillReadRequest(stream,chunk,false);else{let chunkSize=1;const strategySize=controller[_strategySize];if(strategySize!==
-undefined)try{chunkSize=strategySize(chunk)}catch(chunkSizeE){if(ReadableStreamGetState(stream)===STATE_READABLE)ReadableStreamDefaultControllerError(controller,chunkSizeE);throw chunkSizeE;}try{EnqueueValueWithSize(controller,chunk,chunkSize)}catch(enqueueE){if(ReadableStreamGetState(stream)===STATE_READABLE)ReadableStreamDefaultControllerError(controller,enqueueE);throw enqueueE;}}ReadableStreamDefaultControllerCallPullIfNeeded(controller)}function ReadableStreamGetState(stream){return(stream[_readableStreamBits]&
-STATE_MASK)>>STATE_BITS_OFFSET}function ReadableStreamSetState(stream,state){stream[_readableStreamBits]=stream[_readableStreamBits]&~STATE_MASK|state<<STATE_BITS_OFFSET}function ReadableStreamDefaultControllerError(controller,e){controller[_queue]=new SimpleQueue;const stream=controller[_controlledReadableStream];ReadableStreamError(stream,e)}function ReadableStreamError(stream,e){stream[_storedError]=e;ReadableStreamSetState(stream,STATE_ERRORED);const reader=stream[_reader];if(reader===undefined)return undefined;
-if(IsReadableStreamDefaultReader(reader)===true){reader[_readRequests].forEach((request)=>v8_rejectPromise(request,e));reader[_readRequests]=new SimpleQueue}v8_rejectPromise(reader[_closedPromise],e);v8_markPromiseAsHandled(reader[_closedPromise])}function ReadableStreamClose(stream){ReadableStreamSetState(stream,STATE_CLOSED);const reader=stream[_reader];if(reader===undefined)return undefined;if(IsReadableStreamDefaultReader(reader)===true){reader[_readRequests].forEach((request)=>v8_resolvePromise(request,
-CreateIterResultObject(undefined,true)));reader[_readRequests]=new SimpleQueue}v8_resolvePromise(reader[_closedPromise],undefined)}function ReadableStreamDefaultControllerGetDesiredSize(controller){const stream=controller[_controlledReadableStream];const state=ReadableStreamGetState(stream);if(state===STATE_CLOSED)return 0;else if(state===STATE_ERRORED)return null;const queueSize=GetTotalQueueSize(controller);return controller[_strategyHWM]-queueSize}function IsReadableStream(x){return hasOwnProperty(x,
-_controller)}function IsReadableStreamDisturbed(stream){return stream[_readableStreamBits]&DISTURBED}function IsReadableStreamLocked(stream){return stream[_reader]!==undefined}function IsReadableStreamDefaultController(x){return hasOwnProperty(x,_controlledReadableStream)}function IsReadableStreamDefaultReader(x){return hasOwnProperty(x,_readRequests)}function IsReadableStreamReadable(stream){return ReadableStreamGetState(stream)===STATE_READABLE}function IsReadableStreamClosed(stream){return ReadableStreamGetState(stream)===
-STATE_CLOSED}function IsReadableStreamErrored(stream){return ReadableStreamGetState(stream)===STATE_ERRORED}function ReadableStreamReaderGenericInitialize(reader,stream){const controller=stream[_controller];if(controller[_readableStreamDefaultControllerBits]&EXTERNALLY_CONTROLLED){const underlyingSource=controller[_underlyingSource];callFunction(underlyingSource.notifyLockAcquired,underlyingSource)}reader[_ownerReadableStream]=stream;stream[_reader]=reader;switch(ReadableStreamGetState(stream)){case STATE_READABLE:reader[_closedPromise]=
-v8_createPromise();break;case STATE_CLOSED:reader[_closedPromise]=Promise_resolve(undefined);break;case STATE_ERRORED:reader[_closedPromise]=Promise_reject(stream[_storedError]);v8_markPromiseAsHandled(reader[_closedPromise]);break}}function ReadableStreamReaderGenericRelease(reader){const controller=reader[_ownerReadableStream][_controller];if(controller[_readableStreamDefaultControllerBits]&EXTERNALLY_CONTROLLED){const underlyingSource=controller[_underlyingSource];callFunction(underlyingSource.notifyLockReleased,
-underlyingSource)}if(ReadableStreamGetState(reader[_ownerReadableStream])===STATE_READABLE)v8_rejectPromise(reader[_closedPromise],new TypeError(errReleasedReaderClosedPromise));else reader[_closedPromise]=Promise_reject(new TypeError(errReleasedReaderClosedPromise));v8_markPromiseAsHandled(reader[_closedPromise]);reader[_ownerReadableStream][_reader]=undefined;reader[_ownerReadableStream]=undefined}function ReadableStreamDefaultReaderRead(reader){const stream=reader[_ownerReadableStream];stream[_readableStreamBits]|=
-DISTURBED;if(ReadableStreamGetState(stream)===STATE_CLOSED)return Promise_resolve(CreateIterResultObject(undefined,true));if(ReadableStreamGetState(stream)===STATE_ERRORED)return Promise_reject(stream[_storedError]);return ReadableStreamDefaultControllerPull(stream[_controller])}function ReadableStreamDefaultControllerCallPullIfNeeded(controller){const shouldPull=ReadableStreamDefaultControllerShouldCallPull(controller);if(shouldPull===false)return undefined;if(controller[_readableStreamDefaultControllerBits]&
-PULLING){controller[_readableStreamDefaultControllerBits]|=PULL_AGAIN;return undefined}controller[_readableStreamDefaultControllerBits]|=PULLING;const underlyingSource=controller[_underlyingSource];const pullPromise=PromiseCallOrNoop(underlyingSource,"pull",controller,"underlyingSource.pull");thenPromise(pullPromise,()=>{controller[_readableStreamDefaultControllerBits]&=~PULLING;if(controller[_readableStreamDefaultControllerBits]&PULL_AGAIN){controller[_readableStreamDefaultControllerBits]&=~PULL_AGAIN;
-ReadableStreamDefaultControllerCallPullIfNeeded(controller)}},(e)=>{if(ReadableStreamGetState(controller[_controlledReadableStream])===STATE_READABLE)ReadableStreamDefaultControllerError(controller,e)})}function ReadableStreamDefaultControllerShouldCallPull(controller){const stream=controller[_controlledReadableStream];const state=ReadableStreamGetState(stream);if(state===STATE_CLOSED||state===STATE_ERRORED)return false;if(controller[_readableStreamDefaultControllerBits]&CLOSE_REQUESTED)return false;
-if(!(controller[_readableStreamDefaultControllerBits]&STARTED))return false;if(IsReadableStreamLocked(stream)===true&&ReadableStreamGetNumReadRequests(stream)>0)return true;const desiredSize=ReadableStreamDefaultControllerGetDesiredSize(controller);if(desiredSize>0)return true;return false}function ReadableStreamGetNumReadRequests(stream){const reader=stream[_reader];const readRequests=reader[_readRequests];return readRequests.length}function ReadableStreamTee(stream){const reader=AcquireReadableStreamDefaultReader(stream);
-let closedOrErrored=false;let canceled1=false;let canceled2=false;let reason1;let reason2;let promise=v8_createPromise();const branch1Stream=new ReadableStream({pull,cancel:cancel1});const branch2Stream=new ReadableStream({pull,cancel:cancel2});const branch1=branch1Stream[_controller];const branch2=branch2Stream[_controller];thenPromise(reader[_closedPromise],undefined,function(r){if(closedOrErrored===true)return;ReadableStreamDefaultControllerError(branch1,r);ReadableStreamDefaultControllerError(branch2,
-r);closedOrErrored=true});return[branch1Stream,branch2Stream];function pull(){return thenPromise(ReadableStreamDefaultReaderRead(reader),function(result){const value=result.value;const done=result.done;if(done===true&&closedOrErrored===false){if(canceled1===false)ReadableStreamDefaultControllerClose(branch1);if(canceled2===false)ReadableStreamDefaultControllerClose(branch2);closedOrErrored=true}if(closedOrErrored===true)return;if(canceled1===false)ReadableStreamDefaultControllerEnqueue(branch1,value);
-if(canceled2===false)ReadableStreamDefaultControllerEnqueue(branch2,value)})}function cancel1(reason){canceled1=true;reason1=reason;if(canceled2===true){const compositeReason=[reason1,reason2];const cancelResult=ReadableStreamCancel(stream,compositeReason);v8_resolvePromise(promise,cancelResult)}return promise}function cancel2(reason){canceled2=true;reason2=reason;if(canceled1===true){const compositeReason=[reason1,reason2];const cancelResult=ReadableStreamCancel(stream,compositeReason);v8_resolvePromise(promise,
-cancelResult)}return promise}}function DequeueValue(controller){const result=controller[_queue].shift();controller[_totalQueuedSize]-=result.size;return result.value}function EnqueueValueWithSize(controller,value,size){size=Number(size);if(Number_isNaN(size)||size===+Infinity||size<0)throw new RangeError(streamErrors_invalidSize);controller[_totalQueuedSize]+=size;controller[_queue].push({value,size})}function GetTotalQueueSize(controller){return controller[_totalQueuedSize]}function ValidateAndNormalizeQueuingStrategy(size,
-highWaterMark){if(size!==undefined&&typeof size!=="function")throw new TypeError(streamErrors_sizeNotAFunction);highWaterMark=Number(highWaterMark);if(Number_isNaN(highWaterMark))throw new RangeError(streamErrors_invalidHWM);if(highWaterMark<0)throw new RangeError(streamErrors_invalidHWM);return{size,highWaterMark}}function CallOrNoop(O,P,arg,nameForError){const method=O[P];if(method===undefined)return undefined;if(typeof method!=="function")throw new TypeError(errTmplMustBeFunctionOrUndefined(nameForError));
-return callFunction(method,O,arg)}function PromiseCallOrNoop(O,P,arg,nameForError){let method;try{method=O[P]}catch(methodE){return Promise_reject(methodE)}if(method===undefined)return Promise_resolve(undefined);if(typeof method!=="function")return Promise_reject(new TypeError(errTmplMustBeFunctionOrUndefined(nameForError)));try{return Promise_resolve(callFunction(method,O,arg))}catch(e){return Promise_reject(e)}}function CreateIterResultObject(value,done){return{value,done}}defineProperty(global,
-"ReadableStream",{value:ReadableStream,enumerable:false,configurable:true,writable:true});global.AcquireReadableStreamDefaultReader=AcquireReadableStreamDefaultReader;global.IsReadableStream=IsReadableStream;global.IsReadableStreamDisturbed=IsReadableStreamDisturbed;global.IsReadableStreamLocked=IsReadableStreamLocked;global.IsReadableStreamReadable=IsReadableStreamReadable;global.IsReadableStreamClosed=IsReadableStreamClosed;global.IsReadableStreamErrored=IsReadableStreamErrored;global.IsReadableStreamDefaultReader=
-IsReadableStreamDefaultReader;global.ReadableStreamDefaultReaderRead=ReadableStreamDefaultReaderRead;global.ReadableStreamTee=ReadableStreamTee;global.ReadableStreamDefaultControllerClose=ReadableStreamDefaultControllerClose;global.ReadableStreamDefaultControllerGetDesiredSize=ReadableStreamDefaultControllerGetDesiredSize;global.ReadableStreamDefaultControllerEnqueue=ReadableStreamDefaultControllerEnqueue;global.ReadableStreamDefaultControllerError=ReadableStreamDefaultControllerError})(this);
\ No newline at end of file
+'use strict';(function(d){function O(a){return function(b){return a.apply(b,la.call(arguments,1))}}function P(){var a=this;ca.call(this,function(b,m){a[E]=b;a[Q]=m})}function V(a){try{F(a,e,function(){})}catch(b){}}function q(){this.back=this.front={elements:new da,next:e};this.size=this.cursor=0}function v(a,b,m){a=a===e?{}:a;var c=b===e?{}:b;b=c.size;c=c.highWaterMark;c===e&&(c=1);this[p]=0;this[p]=this[p]&-7|0;this[u]=e;this[K]=e;this[y]=e;var d=a.type;if("bytes"===String(d))throw new G("bytes type is not yet implemented");
+if(d!==e)throw new G("Invalid type is specified");this[y]=new L(this,a,b,c,m)}function L(a,b,m,d){if(!1===B(a))throw new c("Illegal constructor");if(a[y]!==e)throw new c("Illegal constructor");this[k]=a;this[W]=b;this[z]=new q;this[H]=0;this[g]=0;m=ma(m,d);this[ea]=m.size;this[fa]=m.highWaterMark;var R=this;b=na(b,"start",this,"underlyingSource.start");F(C(b),function(){R[g]|=1;M(R)},function(b){0===h(a)&&A(R,b)})}function oa(a,b){a[z]=new q;return ga(a[W],"cancel",b,"underlyingSource.cancel")}function N(a){if(!1===
+B(a))throw new c("ReadableStreamReader constructor argument is not a readable stream");if(!0===I(a))throw new c("ReadableStreamReader constructor can only accept readable streams that are not yet locked to a reader");this[x]=a;a[u]=this;switch(h(a)){case 0:this[t]=new P;break;case 1:this[t]=C(e);break;case 2:this[t]=n(a[K]),V(this[t])}this[r]=new q}function X(a){return new N(a)}function S(a,b){a[p]|=1;var m=h(a);if(1===m)return C(e);if(2===m)return n(a[K]);Y(a);a=oa(a[y],b);return F(a,function(){return e})}
+function T(a){var b=a[k];a[g]|=2;0===a[z].length&&Y(b)}function U(a,b){var m=a[k];if(!0===I(m)&&0<m[u][r].length)m[u][r].shift()[E]({value:b,done:!1});else{var c=1,d=a[ea];if(d!==e)try{c=d(b)}catch(w){throw 0===h(m)&&A(a,w),w;}try{c=Z(c);if(ha(c)||c===+pa||0>c)throw new G("The return value of a queuing strategy's size function must be a finite, non-NaN, non-negative number");a[H]+=c;a[z].push({value:b,size:c})}catch(w){throw 0===h(m)&&A(a,w),w;}}M(a)}function h(a){return(a[p]&6)>>1}function A(a,b){a[z]=
+new q;qa(a[k],b)}function qa(a,b){a[K]=b;a[p]=a[p]&-7|4;a=a[u];if(a===e)return e;!0===D(a)&&(a[r].forEach(function(a){a[Q](b)}),a[r]=new q);a[t][Q](b);V(a[t])}function Y(a){a[p]=a[p]&-7|2;a=a[u];if(a===e)return e;!0===D(a)&&(a[r].forEach(function(a){a[E]({value:e,done:!0})}),a[r]=new q);a[t][E](e)}function aa(a){var b=h(a[k]);return 1===b?0:2===b?null:a[fa]-a[H]}function B(a){return J(a,y)}function I(a){return a[u]!==e}function D(a){return J(a,r)}function ba(a){a=a[x];a[p]|=1;if(1===h(a))return C({value:e,
+done:!0});if(2===h(a))return n(a[K]);a=a[y];var b=a[k];if(0<a[z].length){var c=a[z].shift();a[H]-=c.size;c=c.value;a[g]&2&&0===a[z].length?Y(b):M(a);a=C({value:c,done:!1})}else c=new P,b[u][r].push(c),M(a),a=c;return a}function M(a){if(!1===ra(a))return e;if(a[g]&4)return a[g]|=8,e;a[g]|=4;var b=ga(a[W],"pull",a,"underlyingSource.pull");F(b,function(){a[g]&=-5;a[g]&8&&(a[g]&=-9,M(a))},function(b){0===h(a[k])&&A(a,b)})}function ra(a){var b=a[k],c=h(b);return 1===c||2===c||a[g]&2||!(a[g]&1)?!1:!0===
+I(b)&&0<b[u][r].length||0<aa(a)?!0:!1}function ia(a){function b(){return F(ba(c),function(a){var b=a.value;!0===a.done&&!1===d&&(!1===f&&T(q),!1===g&&T(r),d=!0);!0!==d&&(!1===f&&U(q,b),!1===g&&U(r,b))})}var c=X(a),d=!1,f=!1,g=!1,h,l,k=new P,n=new v({pull:b,cancel:function(b){f=!0;h=b;!0===g&&(b=S(a,[h,l]),k[E](b));return k}}),p=new v({pull:b,cancel:function(b){g=!0;l=b;!0===f&&(b=S(a,[h,l]),k[E](b));return k}}),q=n[y],r=p[y];F(c[t],e,function(a){!0!==d&&(A(q,a),A(r,a),d=!0)});return[n,p]}function ma(a,
+b){if(a!==e&&"function"!==typeof a)throw new c("A queuing strategy's size property must be a function");b=Z(b);if(ha(b))throw new G("A queueing strategy's highWaterMark property must be a nonnegative, non-NaN number");if(0>b)throw new G("A queueing strategy's highWaterMark property must be a nonnegative, non-NaN number");return{size:a,highWaterMark:b}}function na(a,b,d,f){b=a[b];if(b===e)return e;if("function"!==typeof b)throw new c(ja(f));return ka(b,a,d)}function ga(a,b,d,f){try{var g=a[b]}catch(w){return n(w)}if(g===
+e)return C(e);if("function"!==typeof g)return n(new c(ja(f)));try{return C(ka(g,a,d))}catch(w){return n(w)}}var l=d.Object.defineProperty,da=d.Array,f=d.Symbol,la=d.Array.prototype.slice,ca=d.Promise,E=f("[[Resolve]]"),Q=f("[[Reject]]");P.prototype=ca.prototype;l(q.prototype,"length",{get:function(){return this.size}});q.prototype.push=function(a){++this.size;if(16384===this.back.elements.length){var b=this.back;this.back={elements:new da,next:e};b.next=this.back}this.back.elements.push(a)};q.prototype.shift=
+function(){--this.size;this.front.elements.length===this.cursor&&(this.front=this.front.next,this.cursor=0);var a=this.front.elements[this.cursor];this.front.elements[this.cursor]=e;++this.cursor;return a};q.prototype.forEach=function(a){for(var b=this.cursor,c=this.front,d=c.elements;b!==d.length||c.next!==e;)b===d.length&&(c=c.next,d=c.elements,b=0),a(d[b]),++b};q.prototype.peek=function(){return this.front.elements.length===this.cursor?this.front.next.elements[0]:this.front.elements[this.cursor]};
+var u=f("[[reader]]"),K=f("[[storedError]]"),y=f("[[controller]]"),t=f("[[closedPromise]]"),x=f("[[ownerReadableStream]]"),r=f("[[readRequests]]"),p=f("bit field for [[state]] and [[disturbed]]"),W=f("[[underlyingSource]]"),k=f("[[controlledReadableStream]]"),z=f("[[queue]]"),H=f("[[totalQueuedSize]]"),ea=f("[[strategySize]]"),fa=f("[[strategyHWM]]"),g=f("bit field for [[started]], [[closeRequested]], [[pulling]], [[pullAgain]]"),e=d.undefined,pa=d.Infinity,J=O(d.Object.hasOwnProperty),ka=O(d.Function.prototype.call);
+O(d.Function.prototype.apply);var c=d.TypeError,G=d.RangeError,Z=d.Number,ha=Z.isNaN,f=d.Promise,F=O(f.prototype.then),C=f.resolve.bind(f),n=f.reject.bind(f),ja=function(a){return a+" must be a function or undefined"};l(v.prototype,"locked",{enumerable:!1,configurable:!0,get:function(){if(!1===B(this))throw new c("Illegal invocation");return I(this)}});l(v.prototype,"cancel",{enumerable:!1,configurable:!0,writable:!0,value:function(a){return!1===B(this)?n(new c("Illegal invocation")):!0===I(this)?
+n(new c("Cannot cancel a readable stream that is locked to a reader")):S(this,a)}});l(v.prototype,"getReader",{enumerable:!1,configurable:!0,writable:!0,value:function(a){a=(a===e?{}:a).mode;if(!1===B(this))throw new c("Illegal invocation");if("byob"===a)throw new c("This readable stream does not support BYOB readers");if(a===e)return X(this);throw new G('Invalid reader mode given: expected undefined or "byob"');}});l(v.prototype,"pipeThrough",{enumerable:!1,configurable:!0,writable:!0,value:function(a,
+b){throw new c("pipeThrough not implemented");}});l(v.prototype,"pipeTo",{enumerable:!1,configurable:!0,writable:!0,value:function(a){throw new c("pipeTo not implemented");}});l(v.prototype,"tee",{enumerable:!1,configurable:!0,writable:!0,value:function(){if(!1===B(this))throw new c("Illegal invocation");return ia(this)}});l(L.prototype,"desiredSize",{enumerable:!1,configurable:!0,get:function(){if(!1===J(this,k))throw new c("Illegal invocation");return aa(this)}});l(L.prototype,"close",{enumerable:!1,
+configurable:!0,writable:!0,value:function(){if(!1===J(this,k))throw new c("Illegal invocation");var a=this[k];if(this[g]&2)throw new c("Cannot close a readable stream that has already been requested to be closed");a=h(a);if(2===a)throw new c("Cannot close an errored readable stream");if(1===a)throw new c("Cannot close a closed readable stream");return T(this)}});l(L.prototype,"enqueue",{enumerable:!1,configurable:!0,writable:!0,value:function(a){if(!1===J(this,k))throw new c("Illegal invocation");
+var b=this[k];if(this[g]&2)throw new c("Cannot enqueue a chunk into a readable stream that is closed or has been requested to be closed");b=h(b);if(2===b)throw new c("Cannot enqueue a chunk into an errored readable stream");if(1===b)throw new c("Cannot enqueue a chunk into a closed readable stream");return U(this,a)}});l(L.prototype,"error",{enumerable:!1,configurable:!0,writable:!0,value:function(a){if(!1===J(this,k))throw new c("Illegal invocation");var b=h(this[k]);if(2===b)throw new c("Cannot error a readable stream that is already errored");
+if(1===b)throw new c("Cannot error a close readable stream");return A(this,a)}});l(N.prototype,"closed",{enumerable:!1,configurable:!0,get:function(){return!1===D(this)?n(new c("Illegal invocation")):this[t]}});l(N.prototype,"cancel",{enumerable:!1,configurable:!0,writable:!0,value:function(a){return!1===D(this)?n(new c("Illegal invocation")):this[x]===e?n(new c("This readable stream reader has been released and cannot be used to cancel its previous owner stream")):S(this[x],a)}});l(N.prototype,"read",
+{enumerable:!1,configurable:!0,writable:!0,value:function(){return!1===D(this)?n(new c("Illegal invocation")):this[x]===e?n(new c("This readable stream reader has been released and cannot be used to read from its previous owner stream")):ba(this)}});l(N.prototype,"releaseLock",{enumerable:!1,configurable:!0,writable:!0,value:function(){if(!1===D(this))throw new c("Illegal invocation");if(this[x]===e)return e;if(0<this[r].length)throw new c("Cannot release a readable stream reader when it still has outstanding read() calls that have not yet settled");
+if(0===h(this[x])){var a=new c("This readable stream reader has been released and cannot be used to monitor the stream's state");this[t][Q](a)}else this[t]=n(new c("This readable stream reader has been released and cannot be used to monitor the stream's state"));V(this[t]);this[x][u]=e;this[x]=e}});l(d,"ReadableStream",{value:v,enumerable:!1,configurable:!0,writable:!0});d.AcquireReadableStreamDefaultReader=X;d.IsReadableStream=B;d.IsReadableStreamDisturbed=function(a){return a[p]&1};d.IsReadableStreamLocked=
+I;d.IsReadableStreamReadable=function(a){return 0===h(a)};d.IsReadableStreamClosed=function(a){return 1===h(a)};d.IsReadableStreamErrored=function(a){return 2===h(a)};d.IsReadableStreamDefaultReader=D;d.ReadableStreamDefaultReaderRead=ba;d.ReadableStreamTee=ia;d.ReadableStreamDefaultControllerClose=T;d.ReadableStreamDefaultControllerGetDesiredSize=aa;d.ReadableStreamDefaultControllerEnqueue=U;d.ReadableStreamDefaultControllerError=A})(this);
\ No newline at end of file
diff --git a/src/cobalt/streams/readable_stream.js b/src/cobalt/streams/readable_stream.js
index edeb9cd..27915ef 100644
--- a/src/cobalt/streams/readable_stream.js
+++ b/src/cobalt/streams/readable_stream.js
@@ -1,7 +1,7 @@
 // ==ClosureCompiler==
 // @output_file_name readable_stream.js
-// @compilation_level WHITESPACE_ONLY
-// @language_out ES6_STRICT
+// @compilation_level SIMPLE_OPTIMIZATIONS
+// @language_out ES5_STRICT
 // ==/ClosureCompiler==
 
 // Copyright 2015 The Chromium Authors. All rights reserved.
@@ -11,12 +11,12 @@
 (function(global) {
   'use strict';
 
+  const defineProperty = global.Object.defineProperty;
+
   // Mimic functionality provided to v8 extras.
   const v8_InternalPackedArray = global.Array;
 
-  function v8_createPrivateSymbol(x) {
-    return Symbol(x);
-  }
+  const v8_createPrivateSymbol = global.Symbol;
 
   function v8_simpleBind(fn, obj) {
     return fn.bind(obj);
@@ -71,80 +71,78 @@
   // InternalPackedArray directly by using multiple arrays in a linked list and
   // keeping the array size bounded.
   const QUEUE_MAX_ARRAY_SIZE = 16384;
-  class SimpleQueue {
-    constructor() {
-      this.front = {
+  function SimpleQueue() {
+    this.front = {
+      elements: new v8_InternalPackedArray(),
+      next: undefined,
+    };
+    this.back = this.front;
+    // The cursor is used to avoid calling InternalPackedArray.shift().
+    this.cursor = 0;
+    this.size = 0;
+  }
+
+  defineProperty(SimpleQueue.prototype, 'length', {
+    get: function() { return this.size; }
+  });
+
+  SimpleQueue.prototype.push = function(element) {
+    ++this.size;
+    if (this.back.elements.length === QUEUE_MAX_ARRAY_SIZE) {
+      const oldBack = this.back;
+      this.back = {
         elements: new v8_InternalPackedArray(),
         next: undefined,
       };
-      this.back = this.front;
-      // The cursor is used to avoid calling InternalPackedArray.shift().
+      oldBack.next = this.back;
+    }
+    this.back.elements.push(element);
+  }
+
+  SimpleQueue.prototype.shift = function() {
+    // assert(this.size > 0);
+    --this.size;
+    if (this.front.elements.length === this.cursor) {
+      // assert(this.cursor === QUEUE_MAX_ARRAY_SIZE);
+      // assert(this.front.next !== undefined);
+      this.front = this.front.next;
       this.cursor = 0;
-      this.size = 0;
     }
+    const element = this.front.elements[this.cursor];
+    // Permit shifted element to be garbage collected.
+    this.front.elements[this.cursor] = undefined;
+    ++this.cursor;
 
-    get length() {
-      return this.size;
-    }
+    return element;
+  }
 
-    push(element) {
-      ++this.size;
-      if (this.back.elements.length === QUEUE_MAX_ARRAY_SIZE) {
-        const oldBack = this.back;
-        this.back = {
-          elements: new v8_InternalPackedArray(),
-          next: undefined,
-        };
-        oldBack.next = this.back;
+  SimpleQueue.prototype.forEach = function(callback) {
+    let i = this.cursor;
+    let node = this.front;
+    let elements = node.elements;
+    while (i !== elements.length || node.next !== undefined) {
+      if (i === elements.length) {
+        // assert(node.next !== undefined);
+        // assert(i === QUEUE_MAX_ARRAY_SIZE);
+        node = node.next;
+        elements = node.elements;
+        i = 0;
       }
-      this.back.elements.push(element);
+      callback(elements[i]);
+      ++i;
     }
+  }
 
-    shift() {
-      // assert(this.size > 0);
-      --this.size;
-      if (this.front.elements.length === this.cursor) {
-        // assert(this.cursor === QUEUE_MAX_ARRAY_SIZE);
-        // assert(this.front.next !== undefined);
-        this.front = this.front.next;
-        this.cursor = 0;
-      }
-      const element = this.front.elements[this.cursor];
-      // Permit shifted element to be garbage collected.
-      this.front.elements[this.cursor] = undefined;
-      ++this.cursor;
-
-      return element;
+  // Return the element that would be returned if shift() was called now,
+  // without modifying the queue.
+  SimpleQueue.prototype.peek = function() {
+    // assert(this.size > 0);
+    if (this.front.elements.length === this.cursor) {
+      // assert(this.cursor === QUEUE_MAX_ARRAY_SIZE)
+      // assert(this.front.next !== undefined);
+      return this.front.next.elements[0];
     }
-
-    forEach(callback) {
-      let i = this.cursor;
-      let node = this.front;
-      let elements = node.elements;
-      while (i !== elements.length || node.next !== undefined) {
-        if (i === elements.length) {
-          // assert(node.next !== undefined);
-          // assert(i === QUEUE_MAX_ARRAY_SIZE);
-          node = node.next;
-          elements = node.elements;
-          i = 0;
-        }
-        callback(elements[i]);
-        ++i;
-      }
-    }
-
-    // Return the element that would be returned if shift() was called now,
-    // without modifying the queue.
-    peek() {
-      // assert(this.size > 0);
-      if (this.front.elements.length === this.cursor) {
-        // assert(this.cursor === QUEUE_MAX_ARRAY_SIZE)
-        // assert(this.front.next !== undefined);
-        return this.front.next.elements[0];
-      }
-      return this.front.elements[this.cursor];
-    }
+    return this.front.elements[this.cursor];
   }
   /* SimpleQueue.js: end */
 
@@ -167,9 +165,6 @@
 
   const _readRequests = v8_createPrivateSymbol('[[readRequests]]');
 
-  const createWithExternalControllerSentinel =
-      v8_createPrivateSymbol('flag for UA-created ReadableStream to pass');
-
   const _readableStreamBits = v8_createPrivateSymbol('bit field for [[state]] and [[disturbed]]');
   const DISTURBED = 0b1;
   // The 2nd and 3rd bit are for [[state]].
@@ -193,12 +188,10 @@
   const CLOSE_REQUESTED = 0b10;
   const PULLING = 0b100;
   const PULL_AGAIN = 0b1000;
-  const EXTERNALLY_CONTROLLED = 0b10000;
 
   const undefined = global.undefined;
   const Infinity = global.Infinity;
 
-  const defineProperty = global.Object.defineProperty;
   const hasOwnProperty = v8_uncurryThis(global.Object.hasOwnProperty);
   const callFunction = v8_uncurryThis(global.Function.prototype.call);
   const applyFunction = v8_uncurryThis(global.Function.prototype.apply);
@@ -246,51 +239,58 @@
   const errTmplMustBeFunctionOrUndefined = name =>
       `${name} must be a function or undefined`;
 
-  class ReadableStream {
-    constructor() {
-      // TODO(domenic): when V8 gets default parameters and destructuring, all
-      // this can be cleaned up.
-      const underlyingSource = arguments[0] === undefined ? {} : arguments[0];
-      const strategy = arguments[1] === undefined ? {} : arguments[1];
-      const size = strategy.size;
-      let highWaterMark = strategy.highWaterMark;
-      if (highWaterMark === undefined) {
-        highWaterMark = 1;
-      }
-
-      this[_readableStreamBits] = 0b0;
-      ReadableStreamSetState(this, STATE_READABLE);
-      this[_reader] = undefined;
-      this[_storedError] = undefined;
-
-      // Avoid allocating the controller if the stream is going to be controlled
-      // externally (i.e. from C++) anyway. All calls to underlyingSource
-      // methods will disregard their controller argument in such situations
-      // (but see below).
-
-      this[_controller] = undefined;
-
-      const type = underlyingSource.type;
-      const typeString = String(type);
-      if (typeString === 'bytes') {
-        throw new RangeError('bytes type is not yet implemented');
-      } else if (type !== undefined) {
-        throw new RangeError(streamErrors_invalidType);
-      }
-
-      this[_controller] =
-          new ReadableStreamDefaultController(this, underlyingSource, size, highWaterMark, arguments[2]);
+  function ReadableStream() {
+    // TODO(domenic): when V8 gets default parameters and destructuring, all
+    // this can be cleaned up.
+    const underlyingSource = arguments[0] === undefined ? {} : arguments[0];
+    const strategy = arguments[1] === undefined ? {} : arguments[1];
+    const size = strategy.size;
+    let highWaterMark = strategy.highWaterMark;
+    if (highWaterMark === undefined) {
+      highWaterMark = 1;
     }
 
-    get locked() {
+    this[_readableStreamBits] = 0b0;
+    ReadableStreamSetState(this, STATE_READABLE);
+    this[_reader] = undefined;
+    this[_storedError] = undefined;
+
+    // Avoid allocating the controller if the stream is going to be controlled
+    // externally (i.e. from C++) anyway. All calls to underlyingSource
+    // methods will disregard their controller argument in such situations
+    // (but see below).
+
+    this[_controller] = undefined;
+
+    const type = underlyingSource.type;
+    const typeString = String(type);
+    if (typeString === 'bytes') {
+      throw new RangeError('bytes type is not yet implemented');
+    } else if (type !== undefined) {
+      throw new RangeError(streamErrors_invalidType);
+    }
+
+    this[_controller] =
+        new ReadableStreamDefaultController(this, underlyingSource, size, highWaterMark, arguments[2]);
+  }
+
+  defineProperty(ReadableStream.prototype, 'locked', {
+    enumerable: false,
+    configurable: true,
+    get: function() {
       if (IsReadableStream(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
 
       return IsReadableStreamLocked(this);
     }
+  });
 
-    cancel(reason) {
+  defineProperty(ReadableStream.prototype, 'cancel', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function(reason) {
       if (IsReadableStream(this) === false) {
         return Promise_reject(new TypeError(streamErrors_illegalInvocation));
       }
@@ -301,8 +301,13 @@
 
       return ReadableStreamCancel(this, reason);
     }
+  });
 
-    getReader({ mode } = {}) {
+  defineProperty(ReadableStream.prototype, 'getReader', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function({ mode } = {}) {
       if (IsReadableStream(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
@@ -323,82 +328,95 @@
 
       throw new RangeError(errGetReaderBadMode);
     }
+  });
 
-    pipeThrough({writable, readable}, options) {
+  defineProperty(ReadableStream.prototype, 'pipeThrough', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function({writable, readable}, options) {
       throw new TypeError('pipeThrough not implemented');
     }
+  });
 
-    pipeTo(dest, {preventClose, preventAbort, preventCancel} = {}) {
+  defineProperty(ReadableStream.prototype, 'pipeTo', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function(dest) {
       throw new TypeError('pipeTo not implemented');
     }
+  });
 
-    tee() {
+  defineProperty(ReadableStream.prototype, 'tee', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function() {
       if (IsReadableStream(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
 
       return ReadableStreamTee(this);
     }
-  }
+  });
 
-  class ReadableStreamDefaultController {
-    // Cobalt: Constructor has a hidden argument at the end to designate
-    // whether it is externally controlled. Exposing this would fail the
-    // web platform test checking the number of parameters the constructor
-    // accepts.
-    constructor(stream, underlyingSource, size, highWaterMark) {
-      if (IsReadableStream(stream) === false) {
-        throw new TypeError(streamErrors_illegalConstructor);
-      }
-
-      if (stream[_controller] !== undefined) {
-        throw new TypeError(streamErrors_illegalConstructor);
-      }
-
-      this[_controlledReadableStream] = stream;
-
-      this[_underlyingSource] = underlyingSource;
-
-      this[_queue] = new SimpleQueue();
-      this[_totalQueuedSize] = 0;
-
-      this[_readableStreamDefaultControllerBits] = 0b0;
-      // Cobalt: Hidden constructor parameter to designate whether the
-      // is externally controlled.
-      if (arguments[4] === createWithExternalControllerSentinel) {
-        this[_readableStreamDefaultControllerBits] |= EXTERNALLY_CONTROLLED;
-      }
-
-      const normalizedStrategy =
-          ValidateAndNormalizeQueuingStrategy(size, highWaterMark);
-      this[_strategySize] = normalizedStrategy.size;
-      this[_strategyHWM] = normalizedStrategy.highWaterMark;
-
-      const controller = this;
-
-      const startResult = CallOrNoop(
-          underlyingSource, 'start', this, 'underlyingSource.start');
-      thenPromise(Promise_resolve(startResult),
-          () => {
-            controller[_readableStreamDefaultControllerBits] |= STARTED;
-            ReadableStreamDefaultControllerCallPullIfNeeded(controller);
-          },
-          r => {
-            if (ReadableStreamGetState(stream) === STATE_READABLE) {
-              ReadableStreamDefaultControllerError(controller, r);
-            }
-          });
+  function ReadableStreamDefaultController(stream, underlyingSource, size, highWaterMark) {
+    if (IsReadableStream(stream) === false) {
+      throw new TypeError(streamErrors_illegalConstructor);
     }
 
-    get desiredSize() {
+    if (stream[_controller] !== undefined) {
+      throw new TypeError(streamErrors_illegalConstructor);
+    }
+
+    this[_controlledReadableStream] = stream;
+
+    this[_underlyingSource] = underlyingSource;
+
+    this[_queue] = new SimpleQueue();
+    this[_totalQueuedSize] = 0;
+
+    this[_readableStreamDefaultControllerBits] = 0b0;
+
+    const normalizedStrategy =
+        ValidateAndNormalizeQueuingStrategy(size, highWaterMark);
+    this[_strategySize] = normalizedStrategy.size;
+    this[_strategyHWM] = normalizedStrategy.highWaterMark;
+
+    const controller = this;
+
+    const startResult = CallOrNoop(
+        underlyingSource, 'start', this, 'underlyingSource.start');
+    thenPromise(Promise_resolve(startResult),
+        () => {
+          controller[_readableStreamDefaultControllerBits] |= STARTED;
+          ReadableStreamDefaultControllerCallPullIfNeeded(controller);
+        },
+        r => {
+          if (ReadableStreamGetState(stream) === STATE_READABLE) {
+            ReadableStreamDefaultControllerError(controller, r);
+          }
+        });
+  }
+
+  defineProperty(ReadableStreamDefaultController.prototype, 'desiredSize', {
+    enumerable: false,
+    configurable: true,
+    get: function() {
       if (IsReadableStreamDefaultController(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
 
       return ReadableStreamDefaultControllerGetDesiredSize(this);
     }
+  });
 
-    close() {
+  defineProperty(ReadableStreamDefaultController.prototype, 'close', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function() {
       if (IsReadableStreamDefaultController(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
@@ -419,8 +437,13 @@
 
       return ReadableStreamDefaultControllerClose(this);
     }
+  });
 
-    enqueue(chunk) {
+  defineProperty(ReadableStreamDefaultController.prototype, 'enqueue', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function(chunk) {
       if (IsReadableStreamDefaultController(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
@@ -441,8 +464,13 @@
 
       return ReadableStreamDefaultControllerEnqueue(this, chunk);
     }
+  });
 
-    error(e) {
+  defineProperty(ReadableStreamDefaultController.prototype, 'error', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function(e) {
       if (IsReadableStreamDefaultController(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
@@ -459,7 +487,7 @@
 
       return ReadableStreamDefaultControllerError(this, e);
     }
-  }
+  });
 
   function ReadableStreamDefaultControllerCancel(controller, reason) {
     controller[_queue] = new SimpleQueue();
@@ -495,29 +523,36 @@
     return promise;
   }
 
-  class ReadableStreamDefaultReader {
-    constructor(stream) {
-      if (IsReadableStream(stream) === false) {
-        throw new TypeError(errReaderConstructorBadArgument);
-      }
-      if (IsReadableStreamLocked(stream) === true) {
-        throw new TypeError(errReaderConstructorStreamAlreadyLocked);
-      }
-
-      ReadableStreamReaderGenericInitialize(this, stream);
-
-      this[_readRequests] = new SimpleQueue();
+  function ReadableStreamDefaultReader(stream) {
+    if (IsReadableStream(stream) === false) {
+      throw new TypeError(errReaderConstructorBadArgument);
+    }
+    if (IsReadableStreamLocked(stream) === true) {
+      throw new TypeError(errReaderConstructorStreamAlreadyLocked);
     }
 
-    get closed() {
+    ReadableStreamReaderGenericInitialize(this, stream);
+
+    this[_readRequests] = new SimpleQueue();
+  }
+
+  defineProperty(ReadableStreamDefaultReader.prototype, 'closed', {
+    enumerable: false,
+    configurable: true,
+    get: function() {
       if (IsReadableStreamDefaultReader(this) === false) {
         return Promise_reject(new TypeError(streamErrors_illegalInvocation));
       }
 
       return this[_closedPromise];
     }
+  });
 
-    cancel(reason) {
+  defineProperty(ReadableStreamDefaultReader.prototype, 'cancel', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function(reason) {
       if (IsReadableStreamDefaultReader(this) === false) {
         return Promise_reject(new TypeError(streamErrors_illegalInvocation));
       }
@@ -529,8 +564,13 @@
 
       return ReadableStreamReaderGenericCancel(this, reason);
     }
+  });
 
-    read() {
+  defineProperty(ReadableStreamDefaultReader.prototype, 'read', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function() {
       if (IsReadableStreamDefaultReader(this) === false) {
         return Promise_reject(new TypeError(streamErrors_illegalInvocation));
       }
@@ -541,8 +581,13 @@
 
       return ReadableStreamDefaultReaderRead(this);
     }
+  });
 
-    releaseLock() {
+  defineProperty(ReadableStreamDefaultReader.prototype, 'releaseLock', {
+    enumerable: false,
+    configurable: true,
+    writable: true,
+    value: function() {
       if (IsReadableStreamDefaultReader(this) === false) {
         throw new TypeError(streamErrors_illegalInvocation);
       }
@@ -558,7 +603,7 @@
 
       ReadableStreamReaderGenericRelease(this);
     }
-  }
+  });
 
   function ReadableStreamReaderGenericCancel(reader, reason) {
     return ReadableStreamCancel(reader[_ownerReadableStream], reason);
@@ -739,12 +784,6 @@
     // TODO(yhirano): Remove this when we don't need hasPendingActivity in
     // blink::UnderlyingSourceBase.
     const controller = stream[_controller];
-    if (controller[_readableStreamDefaultControllerBits] & EXTERNALLY_CONTROLLED) {
-      // The stream is created with an external controller (i.e. made in
-      // Blink).
-      const underlyingSource = controller[_underlyingSource];
-      callFunction(underlyingSource.notifyLockAcquired, underlyingSource);
-    }
 
     reader[_ownerReadableStream] = stream;
     stream[_reader] = reader;
@@ -767,12 +806,6 @@
     // TODO(yhirano): Remove this when we don't need hasPendingActivity in
     // blink::UnderlyingSourceBase.
     const controller = reader[_ownerReadableStream][_controller];
-    if (controller[_readableStreamDefaultControllerBits] & EXTERNALLY_CONTROLLED) {
-      // The stream is created with an external controller (i.e. made in
-      // Blink).
-      const underlyingSource = controller[_underlyingSource];
-      callFunction(underlyingSource.notifyLockReleased, underlyingSource);
-    }
 
     if (ReadableStreamGetState(reader[_ownerReadableStream]) === STATE_READABLE) {
       v8_rejectPromise(reader[_closedPromise], new TypeError(errReleasedReaderClosedPromise));
@@ -1072,12 +1105,4 @@
   global.ReadableStreamDefaultControllerGetDesiredSize = ReadableStreamDefaultControllerGetDesiredSize;
   global.ReadableStreamDefaultControllerEnqueue = ReadableStreamDefaultControllerEnqueue;
   global.ReadableStreamDefaultControllerError = ReadableStreamDefaultControllerError;
-
-/*
-  binding.createReadableStreamWithExternalController =
-      (underlyingSource, strategy) => {
-        return new ReadableStream(
-            underlyingSource, strategy, createWithExternalControllerSentinel);
-      };
-*/
 })(this);
diff --git a/src/cobalt/system_window/application_event.h b/src/cobalt/system_window/application_event.h
index 8151472..eb0f1dd 100644
--- a/src/cobalt/system_window/application_event.h
+++ b/src/cobalt/system_window/application_event.h
@@ -23,11 +23,13 @@
 class ApplicationEvent : public base::Event {
  public:
   enum Type {
-    kQuit,
     kPause,
-    kUnpause,
-    kSuspend,
+    kPreload,
+    kQuit,
     kResume,
+    kStart,
+    kSuspend,
+    kUnpause,
   };
 
   explicit ApplicationEvent(Type type) : type_(type) {}
diff --git a/src/cobalt/system_window/input_event.h b/src/cobalt/system_window/input_event.h
index a17c02e..78c3620 100644
--- a/src/cobalt/system_window/input_event.h
+++ b/src/cobalt/system_window/input_event.h
@@ -28,6 +28,10 @@
     kKeyDown,
     kKeyUp,
     kKeyMove,
+    kPointerDown,
+    kPointerUp,
+    kPointerMove,
+    kWheel,
   };
 
   // Bit-mask of key modifiers. These correspond to the |SbKeyModifiers| values
@@ -38,30 +42,66 @@
     kCtrlKey = 1 << 1,
     kMetaKey = 1 << 2,
     kShiftKey = 1 << 3,
+    kLeftButton = 1 << 4,
+    kRightButton = 1 << 5,
+    kMiddleButton = 1 << 6,
+    kBackButton = 1 << 7,
+    kForwardButton = 1 << 8,
   };
 
-  InputEvent(Type type, int key_code, uint32 modifiers, bool is_repeat,
-             const math::PointF& position = math::PointF())
+  InputEvent(Type type, int device_id, int key_code, uint32 modifiers,
+             bool is_repeat, const math::PointF& position = math::PointF(),
+             const math::PointF& delta = math::PointF()
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+                 ,
+             float pressure = 0, const math::PointF& size = math::PointF(),
+             const math::PointF& tilt = math::PointF()
+#endif
+                 )
       : type_(type),
+        device_id_(device_id),
         key_code_(key_code),
         modifiers_(modifiers),
         is_repeat_(is_repeat),
-        position_(position) {}
+        position_(position),
+        delta_(delta)
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+        ,
+        pressure_(pressure),
+        size_(size),
+        tilt_(tilt)
+#endif
+  {
+  }
 
   Type type() const { return type_; }
   int key_code() const { return key_code_; }
+  int device_id() const { return device_id_; }
   uint32 modifiers() const { return modifiers_; }
   bool is_repeat() const { return is_repeat_; }
   const math::PointF& position() const { return position_; }
+  const math::PointF& delta() const { return delta_; }
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  float pressure() const { return pressure_; }
+  const math::PointF& size() const { return size_; }
+  const math::PointF& tilt() const { return tilt_; }
+#endif
 
   BASE_EVENT_SUBCLASS(InputEvent);
 
  private:
   Type type_;
+  int device_id_;
   int key_code_;
   uint32 modifiers_;
   bool is_repeat_;
   math::PointF position_;
+  math::PointF delta_;
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  float pressure_;
+  math::PointF size_;
+  math::PointF tilt_;
+#endif
 };
 
 // The Starboard Event handler SbHandleEvent should call this function on
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index ef9c73a..9ca6339 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -81,31 +81,80 @@
   return SbWindowGetPlatformHandle(window_);
 }
 
+void SystemWindow::DispatchInputEvent(const SbInputData& data,
+                                      InputEvent::Type type, bool is_repeat) {
+  // Starboard handily uses the Microsoft key mapping, which is also what Cobalt
+  // uses.
+  int key_code = static_cast<int>(data.key);
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  scoped_ptr<InputEvent> input_event(
+      new InputEvent(type, data.device_id, key_code, data.key_modifiers,
+                     is_repeat, math::PointF(data.position.x, data.position.y),
+                     math::PointF(data.delta.x, data.delta.y), data.pressure,
+                     math::PointF(data.size.x, data.size.y),
+                     math::PointF(data.tilt.x, data.tilt.y)));
+#else
+  scoped_ptr<InputEvent> input_event(
+      new InputEvent(type, data.device_id, key_code, data.key_modifiers,
+                     is_repeat, math::PointF(data.position.x, data.position.y),
+                     math::PointF(data.delta.x, data.delta.y)));
+#endif
+  event_dispatcher()->DispatchEvent(input_event.PassAs<base::Event>());
+}
+
+void SystemWindow::HandlePointerInputEvent(const SbInputData& data) {
+  switch (data.type) {
+    case kSbInputEventTypePress:
+    case kSbInputEventTypeUnpress: {
+      InputEvent::Type input_event_type = data.type == kSbInputEventTypePress
+                                              ? InputEvent::kPointerDown
+                                              : InputEvent::kPointerUp;
+      DispatchInputEvent(data, input_event_type, false /* is_repeat */);
+      break;
+    }
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+    case kSbInputEventTypeWheel: {
+      DispatchInputEvent(data, InputEvent::kWheel, false /* is_repeat */);
+      break;
+    }
+#endif
+    case kSbInputEventTypeMove:
+      DispatchInputEvent(data, InputEvent::kPointerMove, false /* is_repeat */);
+      break;
+    default:
+      SB_NOTREACHED();
+      break;
+  }
+}
+
 void SystemWindow::HandleInputEvent(const SbInputData& data) {
   DCHECK_EQ(window_, data.window);
 
-  if (data.type == kSbInputEventTypePress) {
-    // Starboard handily uses the Microsoft key mapping, which is also what
-    // Cobalt uses.
-    int key_code = static_cast<int>(data.key);
-    scoped_ptr<InputEvent> input_event(
-        new InputEvent(InputEvent::kKeyDown, key_code, data.key_modifiers,
-                       key_down_ /* is_repeat */));
-    key_down_ = true;
-    event_dispatcher()->DispatchEvent(input_event.PassAs<base::Event>());
-  } else if (data.type == kSbInputEventTypeUnpress) {
-    key_down_ = false;
-    int key_code = static_cast<int>(data.key);
-    scoped_ptr<InputEvent> input_event(
-        new InputEvent(InputEvent::kKeyUp, key_code, data.key_modifiers,
-                       false /* is_repeat */));
-    event_dispatcher()->DispatchEvent(input_event.PassAs<base::Event>());
-  } else if (data.type == kSbInputEventTypeMove) {
-    int key_code = static_cast<int>(data.key);
-    scoped_ptr<InputEvent> input_event(new InputEvent(
-        InputEvent::kKeyMove, key_code, data.key_modifiers,
-        false /* is_repeat */, math::PointF(data.position.x, data.position.y)));
-    event_dispatcher()->DispatchEvent(input_event.PassAs<base::Event>());
+  // Handle supported pointer device types.
+  if ((kSbInputDeviceTypeMouse == data.device_type) ||
+      (kSbInputDeviceTypeTouchScreen == data.device_type)) {
+    HandlePointerInputEvent(data);
+    return;
+  }
+
+  // Handle all other input device types.
+  switch (data.type) {
+    case kSbInputEventTypePress: {
+      DispatchInputEvent(data, InputEvent::kKeyDown, key_down_);
+      key_down_ = true;
+      break;
+    }
+    case kSbInputEventTypeUnpress: {
+      DispatchInputEvent(data, InputEvent::kKeyUp, false /* is_repeat */);
+      key_down_ = false;
+      break;
+    }
+    case kSbInputEventTypeMove: {
+      DispatchInputEvent(data, InputEvent::kKeyMove, false /* is_repeat */);
+      break;
+    }
+    default:
+      break;
   }
 }
 
diff --git a/src/cobalt/system_window/system_window.gyp b/src/cobalt/system_window/system_window.gyp
index c746f72..50f3b8b 100644
--- a/src/cobalt/system_window/system_window.gyp
+++ b/src/cobalt/system_window/system_window.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/system_window/system_window.h b/src/cobalt/system_window/system_window.h
index 1f76815..08aee67 100644
--- a/src/cobalt/system_window/system_window.h
+++ b/src/cobalt/system_window/system_window.h
@@ -93,6 +93,9 @@
  private:
   void UpdateModifiers(SbKey key, bool pressed);
   InputEvent::Modifiers GetModifiers();
+  void DispatchInputEvent(const SbInputData& data, InputEvent::Type type,
+                          bool is_repeat);
+  void HandlePointerInputEvent(const SbInputData& data);
 
   base::EventDispatcher* event_dispatcher_;
 
diff --git a/src/cobalt/test/document_loader.h b/src/cobalt/test/document_loader.h
index df0ecdd..f6eb285 100644
--- a/src/cobalt/test/document_loader.h
+++ b/src/cobalt/test/document_loader.h
@@ -63,7 +63,8 @@
             &resource_provider_, NULL /* animated_image_tracker */,
             image_cache_.get(), NULL /* reduced_image_cache_capacity_manager */,
             NULL /* remote_font_cache */, NULL /* mesh_cache */,
-            dom_stat_tracker_.get(), "" /* language */) {}
+            dom_stat_tracker_.get(), "" /* language */,
+            base::kApplicationStateStarted) {}
   void Load(const GURL& url) {
     // Load the document in a nested message loop.
     dom::Document::Options options(url);
diff --git a/src/cobalt/test/empty_document.h b/src/cobalt/test/empty_document.h
index 9b6ca37..bb36626 100644
--- a/src/cobalt/test/empty_document.h
+++ b/src/cobalt/test/empty_document.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_TEST_EMPTY_DOCUMENT_H_
 #define COBALT_TEST_EMPTY_DOCUMENT_H_
 
+#include "cobalt/base/application_state.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_stat_tracker.h"
@@ -32,7 +33,8 @@
         dom_stat_tracker_(new dom::DomStatTracker("EmptyDocument")),
         html_element_context_(NULL, css_parser_.get(), NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              dom_stat_tracker_.get(), ""),
+                              dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted),
         document_(new dom::Document(&html_element_context_)) {}
 
   dom::Document* document() { return document_.get(); }
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 09c5600..e9daa78 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -15,6 +15,6 @@
 #define COBALT_VERSION_H_
 
 // Cobalt release number.
-#define COBALT_VERSION "11"
+#define COBALT_VERSION "12"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/web_animations/web_animations.gyp b/src/cobalt/web_animations/web_animations.gyp
index 495b0f2..c600a0f 100644
--- a/src/cobalt/web_animations/web_animations.gyp
+++ b/src/cobalt/web_animations/web_animations.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/webdriver/algorithms.cc b/src/cobalt/webdriver/algorithms.cc
index 8fbce6e..83379cf 100644
--- a/src/cobalt/webdriver/algorithms.cc
+++ b/src/cobalt/webdriver/algorithms.cc
@@ -318,8 +318,7 @@
   for (uint32 i = 0; i < child_nodes->length(); ++i) {
     scoped_refptr<dom::Node> child = child_nodes->Item(i);
     if (child->IsText() ||
-        (child->IsElement() &&
-         HasPositiveSizeDimensions(child->AsElement().get()))) {
+        (child->IsElement() && HasPositiveSizeDimensions(child->AsElement()))) {
       return true;
     }
   }
@@ -339,7 +338,7 @@
   // Check each ancestor of this element and if the ancestor's overflow style
   // is set to hidden, check if this element completely overflows or underflows
   // the element and is thus not visible.
-  dom::Element* parent = element->parent_element().get();
+  dom::Element* parent = element->parent_element();
   while (parent) {
     // Only block level elements will hide children due to overflow.
     if (IsBlockLevelElement(parent)) {
@@ -356,7 +355,7 @@
         }
       }
     }
-    parent = parent->parent_element().get();
+    parent = parent->parent_element();
   }
   return false;
 }
diff --git a/src/cobalt/webdriver/element_driver.cc b/src/cobalt/webdriver/element_driver.cc
index 139738a..56c1450 100644
--- a/src/cobalt/webdriver/element_driver.cc
+++ b/src/cobalt/webdriver/element_driver.cc
@@ -63,11 +63,15 @@
     const protocol::ElementId& element_id,
     const base::WeakPtr<dom::Element>& element, ElementMapping* element_mapping,
     KeyboardEventInjector keyboard_event_injector,
+    PointerEventInjector pointer_event_injector,
+    WheelEventInjector wheel_event_injector,
     const scoped_refptr<base::MessageLoopProxy>& message_loop)
     : element_id_(element_id),
       element_(element),
       element_mapping_(element_mapping),
-      keyboard_injector_(keyboard_event_injector),
+      keyboard_event_injector_(keyboard_event_injector),
+      pointer_event_injector_(pointer_event_injector),
+      wheel_event_injector_(wheel_event_injector),
       element_message_loop_(message_loop) {}
 
 util::CommandResult<std::string> ElementDriver::GetTagName() {
@@ -179,7 +183,8 @@
       return CommandResult(protocol::Response::kStaleElementReference);
     }
 
-    keyboard_injector_.Run(element_.get(), (*events)[i]);
+    keyboard_event_injector_.Run(element_.get(), (*events)[i].first,
+                                 (*events)[i].second);
   }
   return CommandResult(protocol::Response::kSuccess);
 }
diff --git a/src/cobalt/webdriver/element_driver.h b/src/cobalt/webdriver/element_driver.h
index 4ba2725..5cdad82 100644
--- a/src/cobalt/webdriver/element_driver.h
+++ b/src/cobalt/webdriver/element_driver.h
@@ -23,8 +23,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop_proxy.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/base/token.h"
 #include "cobalt/dom/element.h"
-#include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/wheel_event_init.h"
 #include "cobalt/webdriver/element_mapping.h"
 #include "cobalt/webdriver/keyboard.h"
 #include "cobalt/webdriver/protocol/element_id.h"
@@ -45,14 +48,22 @@
 // will map to a method on this class.
 class ElementDriver {
  public:
-  typedef base::Callback<void(scoped_refptr<dom::Element>,
-                              const dom::KeyboardEvent::Data&)>
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::KeyboardEventInit&)>
       KeyboardEventInjector;
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::PointerEventInit&)>
+      PointerEventInjector;
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::WheelEventInit&)>
+      WheelEventInjector;
 
   ElementDriver(const protocol::ElementId& element_id,
                 const base::WeakPtr<dom::Element>& element,
                 ElementMapping* element_mapping,
-                KeyboardEventInjector keyboard_injector,
+                KeyboardEventInjector keyboard_event_injector,
+                PointerEventInjector pointer_event_injector,
+                WheelEventInjector wheel_event_injector,
                 const scoped_refptr<base::MessageLoopProxy>& message_loop);
   const protocol::ElementId& element_id() { return element_id_; }
 
@@ -93,7 +104,9 @@
   // These should only be accessed from |element_message_loop_|.
   base::WeakPtr<dom::Element> element_;
   ElementMapping* element_mapping_;
-  KeyboardEventInjector keyboard_injector_;
+  KeyboardEventInjector keyboard_event_injector_;
+  PointerEventInjector pointer_event_injector_;
+  WheelEventInjector wheel_event_injector_;
   scoped_refptr<base::MessageLoopProxy> element_message_loop_;
 
   friend class WindowDriver;
diff --git a/src/cobalt/webdriver/get_element_text_test.cc b/src/cobalt/webdriver/get_element_text_test.cc
index 29d70f3..a25c693 100644
--- a/src/cobalt/webdriver/get_element_text_test.cc
+++ b/src/cobalt/webdriver/get_element_text_test.cc
@@ -42,7 +42,8 @@
         dom_stat_tracker_(new dom::DomStatTracker("GetElementTextTest")),
         html_element_context_(NULL, css_parser_.get(), NULL, NULL, NULL, NULL,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              dom_stat_tracker_.get(), "") {}
+                              dom_stat_tracker_.get(), "",
+                              base::kApplicationStateStarted) {}
 
   void SetUp() OVERRIDE {
     dom::Document::Options options;
diff --git a/src/cobalt/webdriver/keyboard.cc b/src/cobalt/webdriver/keyboard.cc
index 215d5a2..576c83a 100644
--- a/src/cobalt/webdriver/keyboard.cc
+++ b/src/cobalt/webdriver/keyboard.cc
@@ -17,6 +17,8 @@
 #include <limits>
 
 #include "base/i18n/char_iterator.h"
+#include "cobalt/base/token.h"
+#include "cobalt/base/tokens.h"
 #include "cobalt/dom/keycode.h"
 
 using cobalt::dom::KeyboardEvent;
@@ -404,37 +406,28 @@
   }
 
   void AddKeyDownEvent(int key_code, int char_code, KeyLocationCode location) {
-    AddKeyEvent(KeyboardEvent::kTypeKeyDown, key_code, char_code, location);
+    AddKeyEvent(base::Tokens::keydown(), key_code, char_code, location);
   }
 
   void AddKeyPressEvent(int key_code, int char_code, KeyLocationCode location) {
-    AddKeyEvent(KeyboardEvent::kTypeKeyPress, key_code, char_code, location);
+    AddKeyEvent(base::Tokens::keypress(), key_code, char_code, location);
   }
 
   void AddKeyUpEvent(int key_code, int char_code, KeyLocationCode location) {
-    AddKeyEvent(KeyboardEvent::kTypeKeyUp, key_code, char_code, location);
+    AddKeyEvent(base::Tokens::keyup(), key_code, char_code, location);
   }
 
-  void AddKeyEvent(KeyboardEvent::Type type, int key_code, int char_code,
+  void AddKeyEvent(base::Token type, int key_code, int char_code,
                    KeyLocationCode location) {
-    const bool kIsRepeat = false;
-    uint32 modifiers = GetModifierStateBitfield();
-    event_vector_->push_back(dom::KeyboardEvent::Data(
-        type, location, modifiers, key_code, char_code, kIsRepeat));
-  }
-
-  uint32 GetModifierStateBitfield() const {
-    uint32 modifier_state = 0;
-    if (shift_pressed_) {
-      modifier_state |= KeyboardEvent::kShiftKey;
-    }
-    if (ctrl_pressed_) {
-      modifier_state |= KeyboardEvent::kCtrlKey;
-    }
-    if (alt_pressed_) {
-      modifier_state |= KeyboardEvent::kAltKey;
-    }
-    return modifier_state;
+    dom::KeyboardEventInit event;
+    event.set_location(location);
+    event.set_shift_key(shift_pressed_);
+    event.set_ctrl_key(ctrl_pressed_);
+    event.set_alt_key(alt_pressed_);
+    event.set_key_code(key_code);
+    event.set_char_code(char_code);
+    event.set_repeat(false);
+    event_vector_->push_back(std::make_pair(type, event));
   }
 
   bool shift_pressed_;
diff --git a/src/cobalt/webdriver/keyboard.h b/src/cobalt/webdriver/keyboard.h
index 1acbc74..356f40b 100644
--- a/src/cobalt/webdriver/keyboard.h
+++ b/src/cobalt/webdriver/keyboard.h
@@ -16,6 +16,7 @@
 #define COBALT_WEBDRIVER_KEYBOARD_H_
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/memory/ref_counted.h"
@@ -30,7 +31,7 @@
     kReleaseModifiers,
     kKeepModifiers,
   };
-  typedef std::vector<dom::KeyboardEvent::Data>
+  typedef std::vector<std::pair<base::Token, dom::KeyboardEventInit> >
       KeyboardEventVector;
   static void TranslateToKeyEvents(const std::string& utf8_keys,
                                    TerminationBehaviour termination_behaviour,
diff --git a/src/cobalt/webdriver/keyboard_test.cc b/src/cobalt/webdriver/keyboard_test.cc
index f85679c..b961587 100644
--- a/src/cobalt/webdriver/keyboard_test.cc
+++ b/src/cobalt/webdriver/keyboard_test.cc
@@ -15,7 +15,9 @@
 #include <algorithm>
 #include <vector>
 
+#include "cobalt/base/tokens.h"
 #include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/keycode.h"
 #include "cobalt/webdriver/keyboard.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -29,42 +31,54 @@
 namespace webdriver {
 namespace {
 
-int32 GetKeyCode(const dom::KeyboardEvent::Data& event) {
+int32 GetKeyCode(std::pair<base::Token, const dom::KeyboardEventInit&> event) {
   scoped_refptr<dom::KeyboardEvent> keyboard_event(
-            new dom::KeyboardEvent(event));
+      new dom::KeyboardEvent(event.first.c_str(), event.second));
   return keyboard_event->key_code();
 }
 
-int32 GetCharCode(const dom::KeyboardEvent::Data& event) {
+int32 GetCharCode(std::pair<base::Token, const dom::KeyboardEventInit&> event) {
   scoped_refptr<dom::KeyboardEvent> keyboard_event(
-            new dom::KeyboardEvent(event));
+      new dom::KeyboardEvent(event.first.c_str(), event.second));
   return keyboard_event->char_code();
 }
 
-uint32 GetModifierBitfield(const dom::KeyboardEvent::Data& event) {
+const uint32 kNoModifier = 0;
+const uint32 kShift = 1;
+const uint32 kAlt = 2;
+const uint32 kOtherModifier = 4;
+const uint32 kAltAndShift = kAlt | kShift;
+
+uint32 GetModifierBitfield(
+    std::pair<base::Token, const dom::KeyboardEventInit&> event) {
   scoped_refptr<dom::KeyboardEvent> keyboard_event(
-            new dom::KeyboardEvent(event));
-  return keyboard_event->modifiers();
+      new dom::KeyboardEvent(event.first.c_str(), event.second));
+  uint32 modifiers = kNoModifier;
+  if (keyboard_event->shift_key()) {
+    modifiers |= kShift;
+  }
+  if (keyboard_event->alt_key()) {
+    modifiers |= kAlt;
+  }
+  if (keyboard_event->ctrl_key() || keyboard_event->meta_key()) {
+    modifiers |= kOtherModifier;
+  }
+  return modifiers;
 }
 
-std::string GetType(const dom::KeyboardEvent::Data& event) {
+std::string GetType(
+    std::pair<base::Token, const dom::KeyboardEventInit&> event) {
   scoped_refptr<dom::KeyboardEvent> keyboard_event(
-            new dom::KeyboardEvent(event));
+      new dom::KeyboardEvent(event.first.c_str(), event.second));
   return keyboard_event->type().c_str();
 }
 
-int GetLocation(const dom::KeyboardEvent::Data& event) {
+int GetLocation(std::pair<base::Token, const dom::KeyboardEventInit&> event) {
   scoped_refptr<dom::KeyboardEvent> keyboard_event(
-            new dom::KeyboardEvent(event));
+      new dom::KeyboardEvent(event.first.c_str(), event.second));
   return keyboard_event->location();
 }
 
-// Less verbose redefinitions.
-const int kNoModifier = dom::UIEventWithKeyState::kNoModifier;
-const int kShift = dom::UIEventWithKeyState::kShiftKey;
-const int kAlt = dom::UIEventWithKeyState::kAltKey;
-const int kAltAndShift = kAlt | kShift;
-
 const int kLocationStandard =
     dom::KeyboardEvent::kDomKeyLocationStandard;
 const int kLocationLeft = dom::KeyboardEvent::kDomKeyLocationLeft;
diff --git a/src/cobalt/webdriver/window_driver.cc b/src/cobalt/webdriver/window_driver.cc
index 92721b7..45fcfc2 100644
--- a/src/cobalt/webdriver/window_driver.cc
+++ b/src/cobalt/webdriver/window_driver.cc
@@ -108,12 +108,16 @@
     const protocol::WindowId& window_id,
     const base::WeakPtr<dom::Window>& window,
     const GetGlobalEnvironmentFunction& get_global_environment_function,
-    KeyboardEventInjector keyboard_injector,
+    KeyboardEventInjector keyboard_event_injector,
+    PointerEventInjector pointer_event_injector,
+    WheelEventInjector wheel_event_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),
+      keyboard_event_injector_(keyboard_event_injector),
+      pointer_event_injector_(pointer_event_injector),
+      wheel_event_injector_(wheel_event_injector),
       window_message_loop_(message_loop),
       element_driver_map_deleter_(&element_drivers_),
       next_element_id_(0) {
@@ -354,9 +358,10 @@
       base::StringPrintf("element-%d", next_element_id_++));
   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_)));
+          element_id.id(),
+          new ElementDriver(element_id, weak_element, this,
+                            keyboard_event_injector_, pointer_event_injector_,
+                            wheel_event_injector_, window_message_loop_)));
   DCHECK(pair_it.second)
       << "An ElementDriver was already mapped to the element id: "
       << element_id.id();
@@ -429,7 +434,8 @@
   }
 
   for (size_t i = 0; i < events->size(); ++i) {
-    keyboard_injector_.Run(scoped_refptr<dom::Element>(), (*events)[i]);
+    keyboard_event_injector_.Run(scoped_refptr<dom::Element>(),
+                                 (*events)[i].first, (*events)[i].second);
   }
   return CommandResult(protocol::Response::kSuccess);
 }
diff --git a/src/cobalt/webdriver/window_driver.h b/src/cobalt/webdriver/window_driver.h
index 4f4973b..3e2b47c 100644
--- a/src/cobalt/webdriver/window_driver.h
+++ b/src/cobalt/webdriver/window_driver.h
@@ -29,6 +29,8 @@
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/dom/keyboard_event.h"
+#include "cobalt/dom/pointer_event.h"
+#include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/webdriver/element_driver.h"
 #include "cobalt/webdriver/element_mapping.h"
@@ -53,15 +55,24 @@
 // 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&)>
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::KeyboardEventInit&)>
       KeyboardEventInjector;
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::PointerEventInit&)>
+      PointerEventInjector;
+  typedef base::Callback<void(scoped_refptr<dom::Element>, const base::Token,
+                              const dom::WheelEventInit&)>
+      WheelEventInjector;
+
   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,
+               KeyboardEventInjector keyboard_event_injector,
+               PointerEventInjector pointer_event_injector,
+               WheelEventInjector wheel_event_injector,
                const scoped_refptr<base::MessageLoopProxy>& message_loop);
   ~WindowDriver();
   const protocol::WindowId& window_id() { return window_id_; }
@@ -135,7 +146,9 @@
   // Bound to the WebDriver thread.
   base::ThreadChecker thread_checker_;
 
-  KeyboardEventInjector keyboard_injector_;
+  KeyboardEventInjector keyboard_event_injector_;
+  PointerEventInjector pointer_event_injector_;
+  WheelEventInjector wheel_event_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/c_val_names.py b/src/cobalt/webdriver_benchmarks/c_val_names.py
index 3256e48..6ce4f4d 100644
--- a/src/cobalt/webdriver_benchmarks/c_val_names.py
+++ b/src/cobalt/webdriver_benchmarks/c_val_names.py
@@ -5,18 +5,46 @@
 from __future__ import print_function
 
 
-def count_dom_active_dispatch_events():
-  return "Count.DOM.ActiveDispatchEvents"
+def count_dom_active_java_script_events():
+  return "Count.DOM.ActiveJavaScriptEvents"
+
+
+def count_dom_html_elements_document():
+  return "Count.MainWebModule.DOM.HtmlElement.Document"
+
+
+def count_dom_html_elements_total():
+  return "Count.MainWebModule.DOM.HtmlElement.Total"
+
+
+def count_dom_html_script_element_execute():
+  return "Count.MainWebModule.DOM.HtmlScriptElement.Execute"
+
+
+def count_layout_boxes():
+  return "Count.MainWebModule.Layout.Box"
 
 
 def count_image_cache_loading_resources():
   return "Count.MainWebModule.ImageCache.LoadingResources"
 
 
+def count_image_cache_requested_resources():
+  return "Count.MainWebModule.ImageCache.RequestedResources"
+
+
+def count_rasterize_new_render_tree():
+  return "Count.Renderer.Rasterize.NewRenderTree"
+
+
 def event_duration_dom_video_start_delay():
   return "Event.Duration.MainWebModule.DOM.VideoStartDelay"
 
 
+def event_is_processing():
+  return "Event.MainWebModule.IsProcessing"
+
+
 def event_value_dictionary(event_type):
   return "Event.MainWebModule.{}.ValueDictionary".format(event_type)
 
@@ -31,3 +59,31 @@
 
 def renderer_has_active_animations():
   return "Renderer.HasActiveAnimations"
+
+
+def time_browser_navigate():
+  return "Time.Browser.Navigate"
+
+
+def time_browser_on_load_event():
+  return "Time.Browser.OnLoadEvent"
+
+
+def time_cobalt_start():
+  return "Time.Cobalt.Start"
+
+
+def time_dom_html_script_element_execute():
+  return "Time.MainWebModule.DOM.HtmlScriptElement.Execute"
+
+
+def time_rasterize_animations_start():
+  return "Time.Renderer.Rasterize.Animations.Start"
+
+
+def time_rasterize_animations_end():
+  return "Time.Renderer.Rasterize.Animations.End"
+
+
+def time_rasterize_new_render_tree():
+  return "Time.Renderer.Rasterize.NewRenderTree"
diff --git a/src/cobalt/webdriver_benchmarks/container_util.py b/src/cobalt/webdriver_benchmarks/container_util.py
index 56e948a..2544b11 100644
--- a/src/cobalt/webdriver_benchmarks/container_util.py
+++ b/src/cobalt/webdriver_benchmarks/container_util.py
@@ -52,3 +52,39 @@
 
   return sorted_values[index] * (
       1 - fractional) + sorted_values[index + 1] * fractional
+
+
+def sample_variance(list_values):
+  """Returns the variance of a list of numeric values.
+
+  Args:
+      list_values: A list of numeric values.
+  Returns:
+      Appropriate value.
+  """
+  if not list_values:
+    return None
+  if len(list_values) <= 1:
+    return 0
+
+  mean_of_values = mean(list_values)
+  dif_squared_sum = 0
+  for value in list_values:
+    dif = value - mean_of_values
+    dif_squared_sum += dif * dif
+
+  return dif_squared_sum / (len(list_values) - 1)
+
+
+def sample_standard_deviation(list_values):
+  """Returns the standard deviation of a list of numeric values.
+
+  Args:
+      list_values: A list of numeric values.
+  Returns:
+      Appropriate value.
+  """
+  if not list_values:
+    return None
+
+  return math.sqrt(sample_variance(list_values))
diff --git a/src/cobalt/webdriver_benchmarks/default_query_param_constants.py b/src/cobalt/webdriver_benchmarks/default_query_param_constants.py
new file mode 100644
index 0000000..c2347f2
--- /dev/null
+++ b/src/cobalt/webdriver_benchmarks/default_query_param_constants.py
@@ -0,0 +1,10 @@
+"""Default query params to use when loading URLs."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+BASE_QUERY_PARAMS = {}
+
+INIT_QUERY_PARAMS = {}
+INIT_QUERY_PARAMS_TRIGGER_RELOAD = False
diff --git a/src/cobalt/webdriver_benchmarks/tests/README.md b/src/cobalt/webdriver_benchmarks/tests/README.md
index bd9f865..81fde36 100644
--- a/src/cobalt/webdriver_benchmarks/tests/README.md
+++ b/src/cobalt/webdriver_benchmarks/tests/README.md
@@ -80,24 +80,73 @@
 
 Note that most time-based measurements are in microseconds.
 
-### Interesting benchmarks
-
-#### Timing-related
+### Interesting Timing-Related Benchmarks
 Some particularly interesting timing-related benchmark results are:
 
- - `wbStartupDurBlankToBrowseUs*`: Measures the startup time, until all images
-   finish loading.
- - `wbBrowseToWatchDurVideoStartDelay*`: Measures the browse-to-watch time.
- - `wbBrowseVerticalDurTotalUs*`: Measures the input latency (i.e. JavaScript
-   execution time + layout time) during vertical scroll events.
+#### Startup
+ - `wbStartupDurLaunchToBrowseUs`: Measures the time it takes to launch Cobalt
+   and load the desired URL. The measurement ends when all images finish loading
+   and the final render tree is produced. This is only run once so it will be
+   noiser than other values but provides the most accurate measurement of the
+   requirement startup time.
+ - `wbStartupDurLaunchToUsableUs`: Measures the time it takes to launch Cobalt,
+   and fully load the desired URL, including loading all scripts. The
+   measurement ends when the Player JavaScript code finishes loading on the
+   Browse page, which is the point when Cobalt is fully usable. This is only run
+   once so it will be noiser than other values but provides the most accurate
+   measurement of the time when Cobalt is usable.
+ - `wbStartupDurNavigateToBrowseUs*`: Measures the time it takes to navigate to
+   the desired URL when Cobalt is already loaded. The measurement ends when all
+   images finish loading and the final render tree is produced. This is run
+   multiple times, so it will be less noisy than `wbStartupDurLaunchToBrowseUs`,
+   but it does not include Cobalt initialization so it is not as accurate of a
+   measurement.
+ - `wbStartupDurNavigateToUsableUs`: Measures the time it takes to navigate to
+   the desired URL when Cobalt is already loaded, including loading all scripts.
+   The measurement ends when the Player JavaScript code finishes loading on the
+   Browse page, which is the point when Cobalt is fully usable. This is run
+   multiple times, so it will be less noisy than `wbStartupDurLaunchToUsableUs`,
+   but it does not include Cobalt initialization so it is not as accurate of a
+   measurement.
+
+#### Browse Horizontal Scroll Events
+ - `wbBrowseHorizontalDurTotalUs*`: Measures the latency (i.e. JavaScript
+   execution time + layout time) during horizontal scroll events from keypress
+   until the render tree is submitted to the rasterize thread. It does not
+   include the time taken to rasterize the render tree so it will be smaller
+   than the observed latency.
+ - `wbBrowseHorizontalDurAnimationsStartDelayUs*`: Measures the input latency
+   during horizontal scroll events from the keypress until the animation starts.
+   This is the most accurate measure of input latency.
+ - `wbBrowseHorizontalDurAnimationsEndDelayUs*`: Measures the latency during
+   horizontal scroll events from the keypress until the animation ends.
+ - `wbBrowseHorizontalDurFinalRenderTreeDelayUs*`: Measures the latency during
+   horizontal scroll events from the keypress until the final render tree is
+   rendered and processing stops.
+ - `wbBrowseHorizontalDurRasterizeAnimationsUs*`: Measures the time it takes to
+   render each frame of the animation triggered by a horizontal scroll event.
+   The inverse of this number is the framerate.
+
+#### Browse Vertical Scroll Events
+ - `wbBrowseVerticalDurTotalUs*`: Measures the latency (i.e. JavaScript
+   execution time + layout time) during vertical scroll events from keypress
+   until the render tree is submitted to the rasterize thread. It does not
+   include the time taken to rasterize the render tree so it will be smaller
+   than the observed latency.
+ - `wbBrowseVerticalDurAnimationsStartDelayUs*`: Measures the input latency
+   during vertical scroll events from the keypress until the animation starts.
+   This is the most accurate measure of input latency.
+ - `wbBrowseVerticalDurAnimationsEndDelayUs*`: Measures the latency during
+   vertical scroll events from the keypress until the animation ends.
+ - `wbBrowseVerticalDurFinalRenderTreeDelayUs*`: Measures the latency during
+   vertical scroll events from the keypress until the final render tree is
+   rendered and processing stops.
  - `wbBrowseVerticalDurRasterizeAnimationsUs*`: Measures the time it takes to
    render each frame of the animation triggered by a vertical scroll event.
    The inverse of this number is the framerate.
- - `wbBrowseHorizontalDurTotalUs*`: Same as `wbBrowseVerticalDurTotalUs*` except
-   for horizontal scroll events.
- - `wbBrowseHorizontalDurRasterizeAnimationsUs*`: Same as
-   `wbBrowseVerticalDurRasterizeAnimationsUs*` except for horizontal scroll
-   events.
+
+#### Browse-to-Watch
+ - `wbBrowseToWatchDurVideoStartDelay*`: Measures the browse-to-watch time.
 
 In each case above, the `*` symbol can be one of either `Mean`, `Pct25`,
 `Pct50`, `Pct75` or `Pct95`.  For example, `wbStartupDurBlankToBrowseUsMean` or
@@ -106,22 +155,51 @@
 can drill into the data by exploring either the mean, or the various
 percentiles.
 
-#### Object count-related
+### Interesting Count-Related Benchmarks
 Some particularly interesting count-related benchmark results are:
 
+#### Startup
+ - `wbStartupCntDomHtmlElements*`: Lists the number of HTML elements in
+   existence after startup completes. This includes HTML elements that are no
+   longer in the DOM but have not been garbage collected yet.
+ - `wbStartupCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after startup completes.
+ - `wbStartupCntLayoutBoxes*`: Lists the number of layout boxes within
+   the layout tree after startup completes.
+ - `wbStartupCntRenderTrees*`: Lists the number of render trees that were
+   generated during startup.
+ - `wbStartupCntRequestedImages*`: Lists the number of images that were
+   requested during startup.
+
+#### Browse Horizontal Scroll Events
+ - `wbBrowseHorizontalCntDomHtmlElements*`: Lists the number of HTML elements in
+   existence after the event. This includes HTML elements that are no longer in
+   the DOM but have not been garbage collected yet.
+ - `wbBrowseHorizontalCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after the event.
+ - `wbBrowseHorizontalCntLayoutBoxes*`: Lists the number of layout boxes within
+   the layout tree after the event.
+ - `wbBrowseHorizontalCntLayoutBoxesCreated*`: Lists the number of new layout
+   boxes that were created during the event.
+ - `wbBrowseHorizontalCntRenderTrees*`: Lists the number of render trees that
+   were generated by the event.
+ - `wbBrowseHorizontalCntRequestedImages*`: Lists the number of images that were
+   requested as a result of the event.
+
+#### Browse Vertical Scroll Events
  - `wbBrowseVerticalCntDomHtmlElements*`: Lists the number of HTML elements in
    existence after the event. This includes HTML elements that are no longer in
    the DOM but have not been garbage collected yet.
+ - `wbBrowseVerticalCntDocumentDomHtmlElements*`: Lists the number of HTML
+   elements within the DOM tree after the event.
  - `wbBrowseVerticalCntLayoutBoxes*`: Lists the number of layout boxes within
    the layout tree after the event.
  - `wbBrowseVerticalCntLayoutBoxesCreated*`: Lists the number of new layout
    boxes that were created during the event.
- - `wbBrowseHorizontalCntDomHtmlElements*`: Same as
-   `wbBrowseVerticalCntDomHtmlElements*` except for horizontal scroll events.
- - `wbBrowseHorizontalCntLayoutBoxes*`: Same as
-   `wbBrowseVerticalCntLayoutBoxes*` except for horizontal scroll events.
- - `wbBrowseHorizontalCntLayoutBoxesCreated*`: Same as
-   `wbBrowseVerticalCntLayoutBoxesCreated*` except for horizontal scroll events.
+ - `wbBrowseVerticalCntRenderTrees*`: Lists the number of render trees that
+   were generated by the event.
+ - `wbBrowseVerticalCntRequestedImages*`: Lists the number of images that were
+   requested as a result of the event.
 
 In each case above,  the `*` symbol can be one of either `Max`, `Median`, or
 `Mean`. For example, `wbBrowseVerticalCntDomHtmlElementsMax` or
@@ -139,16 +217,76 @@
 ```
 python performance.py -p raspi-2 -c qa -d $RASPI_ADDR > results.txt
 echo "" > filtered_results.txt
-grep -o "wbStartupDurBlankToBrowseUs.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseToWatchDurVideoStartDelay.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseVerticalDurTotalUs.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseVerticalDurRasterizeAnimationsUs.*$" results.txt >> filtered_results.txt
+printf "=================================TIMING-RELATED=================================\n" >> filtered_results.txt
+printf "\nSTARTUP\n" >> filtered_results.txt
+grep -o "wbStartupDurLaunchToBrowseUs.*$" results.txt >> filtered_results.txt
+grep -o "wbStartupDurLaunchToUsableUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupDurNavigateToBrowseUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupDurNavigateToUsableUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE HORIZONTAL SCROLL EVENTS\n" >> filtered_results.txt
 grep -o "wbBrowseHorizontalDurTotalUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurAnimationsStartDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurAnimationsEndDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalDurFinalRenderTreeDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
 grep -o "wbBrowseHorizontalDurRasterizeAnimationsUs.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseVerticalCntDomHtmlElements.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseVerticalCntLayoutBoxes.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseHorizontalCntDomHtmlElements.*$" results.txt >> filtered_results.txt
-grep -o "wbBrowseHorizontalCntLayoutBoxes.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE VERTICAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurTotalUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurAnimationsStartDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurAnimationsEndDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurFinalRenderTreeDelayUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalDurRasterizeAnimationsUs.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE TO WATCH\n" >> filtered_results.txt
+grep -o "wbBrowseToWatchDurVideoStartDelay.*$" results.txt >> filtered_results.txt
+printf "\n\n=================================COUNT-RELATED==================================\n" >> filtered_results.txt
+printf "\nSTARTUP\n" >> filtered_results.txt
+grep -o "wbStartupCntDomHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntLayoutBoxes.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbStartupCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE HORIZONTAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntDomHtmlElementsM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntLayoutBoxesM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntLayoutBoxesCreated.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseHorizontalCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+printf "\nBROWSE VERTICAL SCROLL EVENTS\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntDomHtmlElementsM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntDomDocumentHtmlElements.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntLayoutBoxesM.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntLayoutBoxesCreated.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntRenderTrees.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
+grep -o "wbBrowseVerticalCntRequestedImages.*$" results.txt >> filtered_results.txt
+printf "\n" >> filtered_results.txt
 cat filtered_results.txt
 ```
-
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
index b04e8ab..a2d82de 100755
--- a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/browse_to_search.py
@@ -38,13 +38,13 @@
   def test_simple(self):
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, BROWSE_TO_SEARCH_EVENT_NAME, BROWSE_TO_SEARCH_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
+    recorder_options.record_animations = False
     browse_to_search_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, SEARCH_TO_BROWSE_EVENT_NAME, SEARCH_TO_BROWSE_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
+    recorder_options.record_animations = False
     search_to_browse_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py
index 997a0fc..c37008e 100755
--- a/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/non_video/startup.py
@@ -15,14 +15,13 @@
                         os.path.dirname(os.path.realpath(__file__)))))))
 
 # pylint: disable=C6204,C6203
-import timer
+import c_val_names
 import tv_testcase
 import tv_testcase_util
 
-NUM_BLANK_TO_BROWSE_ITERATIONS = 10
+NUM_LOAD_TV_ITERATIONS = 20
 
-LAUNCH_TO_BLANK = "wbStartupDurLaunchToBlankUs"
-BLANK_TO_BROWSE = "wbStartupDurBlankToBrowseUs"
+STARTUP_RECORD_NAME = "wbStartup"
 
 
 class StartupTest(tv_testcase.TvTestCase):
@@ -32,45 +31,133 @@
     pass
 
   def test_simple(self):
-    """This test tries to measure the startup time for the YouTube TV page.
+    """This test tries to measure the startup time for the YouTube TV page."""
+    self.wait_for_processing_complete()
+    self.wait_for_html_script_element_execute_count(2)
 
-    Specifically, this test uses the Cobalt CVal Cobalt.Lifetime, which gets
-    updated ~60Hz on a best effort basis and is in microseconds, to determine
-    "wbStartupDurLaunchToBlankUs" and uses Timer to determine
-    "wbStartupDurBlankToBrowseUs".
-    """
+    # Measure durations for the intial launch.
+    launch_time = self.get_cval(c_val_names.time_cobalt_start())
+    navigate_time = self.get_cval(c_val_names.time_browser_navigate())
+    on_load_event_time = self.get_cval(c_val_names.time_browser_on_load_event())
+    browse_time = self.get_cval(c_val_names.time_rasterize_new_render_tree())
+    usable_time = self.get_cval(
+        c_val_names.time_dom_html_script_element_execute())
 
-    dur_launch_to_blank_us = self.get_cval("Cobalt.Lifetime")
+    dur_launch_to_navigate_us = navigate_time - launch_time
+    dur_launch_to_on_load_event_us = on_load_event_time - launch_time
+    dur_launch_to_browse_us = browse_time - launch_time
+    dur_launch_to_usable_us = usable_time - launch_time
 
-    # Call TvTestCase's setUp() now that the blank startup time has been
-    # measured.
+    # Call TvTestCase's setUp() now that the launch times have been measured.
     super(StartupTest, self).setUp()
 
-    # Blank to browser record strategies
-    blank_to_browse_record_strategies = []
-    blank_to_browse_record_strategies.append(
-        tv_testcase_util.RecordStrategyMean())
-    blank_to_browse_record_strategies.append(
+    # Count record strategies
+    count_record_strategies = []
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
+
+    # Duration record strategies
+    duration_record_strategies = []
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    duration_record_strategies.append(
         tv_testcase_util.RecordStrategyPercentile(25))
-    blank_to_browse_record_strategies.append(
+    duration_record_strategies.append(
         tv_testcase_util.RecordStrategyPercentile(50))
-    blank_to_browse_record_strategies.append(
+    duration_record_strategies.append(
         tv_testcase_util.RecordStrategyPercentile(75))
-    blank_to_browse_record_strategies.append(
+    duration_record_strategies.append(
         tv_testcase_util.RecordStrategyPercentile(95))
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMax())
 
-    # Blank to browser recorder
-    blank_to_browse_recorder = tv_testcase_util.ResultsRecorder(
-        BLANK_TO_BROWSE, blank_to_browse_record_strategies)
+    # Count recorders
+    count_total_html_element_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntDomHtmlElements", count_record_strategies)
+    count_document_html_element_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntDomDocumentHtmlElements",
+        count_record_strategies)
+    count_layout_box_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntLayoutBoxes", count_record_strategies)
+    count_render_trees_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntRenderTrees", count_record_strategies)
+    count_requested_images_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "CntRequestedImages", count_record_strategies)
 
-    for _ in range(NUM_BLANK_TO_BROWSE_ITERATIONS):
-      self.load_blank()
-      with timer.Timer(BLANK_TO_BROWSE) as t:
-        self.load_tv()
-      blank_to_browse_recorder.collect_value(int(t.seconds_elapsed * 1000000))
+    # Duration recorders
+    duration_navigate_to_on_load_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToOnLoadUs",
+        duration_record_strategies)
+    duration_navigate_to_browse_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToBrowseUs",
+        duration_record_strategies)
+    duration_navigate_to_usable_recorder = tv_testcase_util.ResultsRecorder(
+        STARTUP_RECORD_NAME + "DurNavigateToUsableUs",
+        duration_record_strategies)
 
-    tv_testcase_util.record_test_result(LAUNCH_TO_BLANK, dur_launch_to_blank_us)
-    blank_to_browse_recorder.on_end_test()
+    # Now measure counts and durations from reloading the URL.
+    for _ in range(NUM_LOAD_TV_ITERATIONS):
+      count_render_trees_start = self.get_cval(
+          c_val_names.count_rasterize_new_render_tree())
+
+      self.load_tv()
+
+      count_render_trees_end = self.get_cval(
+          c_val_names.count_rasterize_new_render_tree())
+
+      count_total_html_elements = self.get_cval(
+          c_val_names.count_dom_html_elements_total())
+      count_document_html_elements = self.get_cval(
+          c_val_names.count_dom_html_elements_document())
+      count_layout_boxes = self.get_cval(c_val_names.count_layout_boxes())
+      count_requested_images = self.get_cval(
+          c_val_names.count_image_cache_requested_resources())
+
+      navigate_time = self.get_cval(c_val_names.time_browser_navigate())
+      on_load_event_time = self.get_cval(
+          c_val_names.time_browser_on_load_event())
+      browse_time = self.get_cval(c_val_names.time_rasterize_new_render_tree())
+      usable_time = self.get_cval(
+          c_val_names.time_dom_html_script_element_execute())
+
+      count_total_html_element_recorder.collect_value(count_total_html_elements)
+      count_document_html_element_recorder.collect_value(
+          count_document_html_elements)
+      count_layout_box_recorder.collect_value(count_layout_boxes)
+      count_render_trees_recorder.collect_value(count_render_trees_end -
+                                                count_render_trees_start)
+      count_requested_images_recorder.collect_value(count_requested_images)
+
+      duration_navigate_to_on_load_recorder.collect_value(
+          on_load_event_time - navigate_time)
+      duration_navigate_to_browse_recorder.collect_value(
+          browse_time - navigate_time)
+      duration_navigate_to_usable_recorder.collect_value(
+          usable_time - navigate_time)
+
+    # Record the counts
+    count_total_html_element_recorder.on_end_test()
+    count_document_html_element_recorder.on_end_test()
+    count_layout_box_recorder.on_end_test()
+    count_render_trees_recorder.on_end_test()
+    count_requested_images_recorder.on_end_test()
+
+    # Record the durations
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToNavigateUs",
+        dur_launch_to_navigate_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToOnLoadUs",
+        dur_launch_to_on_load_event_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToBrowseUs", dur_launch_to_browse_us)
+    tv_testcase_util.record_test_result(
+        STARTUP_RECORD_NAME + "DurLaunchToUsableUs", dur_launch_to_usable_us)
+
+    duration_navigate_to_on_load_recorder.on_end_test()
+    duration_navigate_to_browse_recorder.on_end_test()
+    duration_navigate_to_usable_recorder.on_end_test()
 
 
 if __name__ == "__main__":
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py b/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py
index 0c958fb..248e73c 100755
--- a/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance/video/browse_to_watch.py
@@ -22,8 +22,11 @@
 # selenium imports
 keys = tv_testcase_util.import_selenium_module("webdriver.common.keys")
 
-NUM_LOAD_TV_CALLS = 1
-NUM_ITERATIONS_PER_LOAD_TV_CALL = 10
+MAX_VIDEO_FAILURE_COUNT = 10
+MAX_SKIPPABLE_AD_COUNT = 8
+
+NUM_LOAD_TV_CALLS = 4
+NUM_ITERATIONS_PER_LOAD_TV_CALL = 25
 
 BROWSE_TO_WATCH_EVENT_NAME = "wbBrowseToWatch"
 BROWSE_TO_WATCH_EVENT_TYPE = tv_testcase_util.EVENT_TYPE_KEY_UP
@@ -34,20 +37,29 @@
 
 class BrowseToWatchTest(tv_testcase.TvTestCase):
 
+  class VideoFailureException(BaseException):
+    """Exception thrown when MAX_VIDEO_FAILURE_COUNT is exceeded."""
+
+  class AdvertisementFailureException(BaseException):
+    """Exception thrown when MAX_SKIPPABLE_AD_COUNT is exceeded."""
+
   def test_simple(self):
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, BROWSE_TO_WATCH_EVENT_NAME, BROWSE_TO_WATCH_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
-    recorder_options.record_video_start_delay = True
+    recorder_options.record_animations = False
+    recorder_options.record_video = True
     browse_to_watch_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
     recorder_options = tv_testcase_event_recorder.EventRecorderOptions(
         self, WATCH_TO_BROWSE_EVENT_NAME, WATCH_TO_BROWSE_EVENT_TYPE)
-    recorder_options.record_rasterize_animations = False
+    recorder_options.record_animations = False
     watch_to_browse_recorder = tv_testcase_event_recorder.EventRecorder(
         recorder_options)
 
+    failure_count = 0
+    skippable_ad_count = 0
+
     for _ in xrange(NUM_LOAD_TV_CALLS):
       self.load_tv()
 
@@ -57,7 +69,29 @@
 
         browse_to_watch_recorder.on_start_event()
         self.send_keys(keys.Keys.ENTER)
-        self.wait_for_media_element_playing()
+
+        if not self.wait_for_media_element_playing():
+          failure_count += 1
+          print("Video failed to play! {} events failed.".format(failure_count))
+          if failure_count > MAX_VIDEO_FAILURE_COUNT:
+            raise BrowseToWatchTest.VideoFailureException()
+
+          self.send_keys(keys.Keys.ESCAPE)
+          self.wait_for_processing_complete_after_focused_shelf()
+          continue
+
+        if self.skip_advertisement_if_playing():
+          skippable_ad_count += 1
+          print(
+              "Encountered skippable ad! {} total.".format(skippable_ad_count))
+          if skippable_ad_count > MAX_SKIPPABLE_AD_COUNT:
+            raise BrowseToWatchTest.AdvertisementFailureException()
+
+          self.wait_for_title_card_hidden()
+          self.send_keys(keys.Keys.ESCAPE)
+          self.wait_for_processing_complete_after_focused_shelf()
+          continue
+
         browse_to_watch_recorder.on_end_event()
 
         # Wait for the title card hidden before sending the escape. Otherwise,
diff --git a/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py b/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
index d385177..f71a433 100644
--- a/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
+++ b/src/cobalt/webdriver_benchmarks/tests/performance_non_video.py
@@ -38,7 +38,6 @@
   _add_test(test_suite, dir_path, "browse_vertical")
   _add_test(test_suite, dir_path, "browse_to_guide")
   _add_test(test_suite, dir_path, "browse_to_search")
-  _add_test(test_suite, dir_path, "csi")
 
   return test_suite
 
diff --git a/src/cobalt/webdriver_benchmarks/timer.py b/src/cobalt/webdriver_benchmarks/timer.py
deleted file mode 100644
index 0ee08dc..0000000
--- a/src/cobalt/webdriver_benchmarks/timer.py
+++ /dev/null
@@ -1,94 +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.
-# ==============================================================================
-"""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
index b3e6d29..332b3c9 100644
--- a/src/cobalt/webdriver_benchmarks/tv.py
+++ b/src/cobalt/webdriver_benchmarks/tv.py
@@ -8,4 +8,6 @@
 FOCUSED_SEARCH = '.focused.search'
 FOCUSED_SHELF = '.focused.selected.shelf'
 FOCUSED_SHELF_TITLE = '.focused.selected.shelf .shelf-header-title .main'
+SKIP_AD_BUTTON_CAN_SKIP = '.skip-ad-button.canskip'
+SKIP_AD_BUTTON_HIDDEN = '.skip-ad-button.hidden'
 TITLE_CARD_HIDDEN = '.title-card.hidden'
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py
index 9840228..dc49278 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -19,16 +19,24 @@
 import tv_testcase_runner
 import tv_testcase_util
 
+try:
+  import custom_query_param_constants as query_param_constants
+except ImportError:
+  import default_query_param_constants as query_param_constants
+
 # selenium imports
 # pylint: disable=C0103
 ActionChains = tv_testcase_util.import_selenium_module(
     submodule="webdriver.common.action_chains").ActionChains
+keys = tv_testcase_util.import_selenium_module("webdriver.common.keys")
 
 WINDOWDRIVER_CREATED_TIMEOUT_SECONDS = 30
 WEBMODULE_LOADED_TIMEOUT_SECONDS = 30
 PAGE_LOAD_WAIT_SECONDS = 30
-PROCESSING_TIMEOUT_SECONDS = 15
+PROCESSING_TIMEOUT_SECONDS = 60
+HTML_SCRIPT_ELEMENT_EXECUTE_TIMEOUT_SECONDS = 30
 MEDIA_TIMEOUT_SECONDS = 30
+SKIP_AD_TIMEOUT_SECONDS = 30
 TITLE_CARD_HIDDEN_TIMEOUT_SECONDS = 30
 
 _is_initialized = False
@@ -50,8 +58,8 @@
   class ProcessingTimeoutException(BaseException):
     """Exception thrown when processing did not complete in time."""
 
-  class MediaTimeoutException(BaseException):
-    """Exception thrown when media did not complete in time."""
+  class HtmlScriptElementExecuteTimeoutException(BaseException):
+    """Exception thrown when processing did not complete in time."""
 
   class TitleCardHiddenTimeoutException(BaseException):
     """Exception thrown when title card did not disappear in time."""
@@ -67,11 +75,9 @@
   def setUp(self):
     global _is_initialized
     if not _is_initialized:
-      # Initialize the tests. This involves loading a URL which applies the
-      # forcedOffAllExperiments cookies, ensuring that no subsequent loads
-      # include experiments. Additionally, loading this URL triggers a reload.
-      query_params = {"env_forcedOffAllExperiments": True}
-      triggers_reload = True
+      # Initialize the tests.
+      query_params = query_param_constants.INIT_QUERY_PARAMS
+      triggers_reload = query_param_constants.INIT_QUERY_PARAMS_TRIGGER_RELOAD
       self.load_tv(query_params, triggers_reload)
       _is_initialized = True
 
@@ -115,6 +121,7 @@
     Raises:
       Underlying WebDriver exceptions
     """
+    self.get_webdriver().execute_script("h5vcc.storage.clearCookies()")
     self.clear_url_loaded_events()
     self.get_webdriver().get(
         tv_testcase_util.generate_url(self.get_default_url(), query_params))
@@ -186,16 +193,16 @@
       self.assertEqual(len(elements), expected_num)
     return elements
 
-  def send_keys(self, keys):
+  def send_keys(self, key_events):
     """Sends keys to whichever element currently has focus.
 
     Args:
-      keys: key events
+      key_events: key events
 
     Raises:
       Underlying WebDriver exceptions
     """
-    ActionChains(self.get_webdriver()).send_keys(keys).perform()
+    ActionChains(self.get_webdriver()).send_keys(key_events).perform()
 
   def clear_url_loaded_events(self):
     """Clear the events that indicate that Cobalt finished loading a URL."""
@@ -235,6 +242,18 @@
       required time.
     """
     start_time = time.time()
+
+    # First simply check for whether or not the event is still processing.
+    # There's no need to check anything else while the event is still going on.
+    # Once it is done processing, it won't get re-set, so there's no need to
+    # re-check it.
+    while self.get_cval(c_val_names.event_is_processing()):
+      if time.time() - start_time > PROCESSING_TIMEOUT_SECONDS:
+        raise TvTestCase.ProcessingTimeoutException()
+
+      time.sleep(0.1)
+
+    # Now wait for all processing to complete in Cobalt.
     count = 0
     while count < 2:
       if self.is_processing(check_animations):
@@ -249,26 +268,62 @@
 
   def is_processing(self, check_animations):
     """Checks to see if Cobalt is currently processing."""
-    return (self.get_cval(c_val_names.count_dom_active_dispatch_events()) or
+    return (self.get_cval(c_val_names.count_dom_active_java_script_events()) or
             self.get_cval(c_val_names.layout_is_dirty()) or
             (check_animations and
              self.get_cval(c_val_names.renderer_has_active_animations())) or
             self.get_cval(c_val_names.count_image_cache_loading_resources()))
 
+  def wait_for_html_script_element_execute_count(self, required_count):
+    """Waits for specified number of html script element Execute() calls.
+
+    Args:
+      required_count: the number of executions that must occur
+
+    Raises:
+      HtmlScriptElementExecuteTimeoutException: The required html script element
+      executions did not occur within the required time.
+    """
+    start_time = time.time()
+    while self.get_cval(
+        c_val_names.count_dom_html_script_element_execute()) < required_count:
+      if time.time() - start_time > HTML_SCRIPT_ELEMENT_EXECUTE_TIMEOUT_SECONDS:
+        raise TvTestCase.HtmlScriptElementExecuteTimeoutException()
+      time.sleep(0.1)
+
   def wait_for_media_element_playing(self):
     """Waits for a video to begin playing.
 
-    Raises:
-      MediaTimeoutException: The video does not start playing within the
-      required time.
+    Returns:
+      Whether or not the video started.
     """
     start_time = time.time()
     while self.get_cval(
         c_val_names.event_duration_dom_video_start_delay()) == 0:
       if time.time() - start_time > MEDIA_TIMEOUT_SECONDS:
-        raise TvTestCase.MediaTimeoutException()
+        return False
       time.sleep(0.1)
 
+    return True
+
+  def skip_advertisement_if_playing(self):
+    """Waits to skip an ad if it is encountered.
+
+    Returns:
+      True if a skippable advertisement was encountered
+    """
+    start_time = time.time()
+    if not self.find_elements(tv.SKIP_AD_BUTTON_HIDDEN):
+      while not self.find_elements(tv.SKIP_AD_BUTTON_CAN_SKIP):
+        if time.time() - start_time > SKIP_AD_TIMEOUT_SECONDS:
+          return True
+        time.sleep(0.1)
+      self.send_keys(keys.Keys.ENTER)
+      self.wait_for_processing_complete(False)
+      return True
+
+    return False
+
   def wait_for_title_card_hidden(self):
     """Waits for the title to disappear while a video is playing.
 
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py b/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
index cb49624..363bde4 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_event_recorder.py
@@ -24,8 +24,8 @@
     self.event_name = event_name
     self.event_type = event_type
 
-    self.record_rasterize_animations = True
-    self.record_video_start_delay = False
+    self.record_animations = True
+    self.record_video = False
 
 
 class EventRecorder(object):
@@ -37,8 +37,8 @@
   tv_testcase_util.py.
 
   Both rasterize animations and video start delay data can potentially be
-  recorded, depending on the |record_rasterize_animations| and
-  |record_video_start_delay| option flags.
+  recorded, depending on the |record_animations| and |record_video| option
+  flags.
   """
 
   def __init__(self, options):
@@ -59,21 +59,56 @@
     # Each entry in the list contains a tuple with a key and value recorder.
     self.value_dictionary_recorders = []
 
+    # Optional animations recorders
     self.animations_recorder = None
+    self.animations_start_delay_recorder = None
+    self.animations_end_delay_recorder = None
+
+    # Optional video recorders
     self.video_delay_recorder = None
 
     # Count record strategies
     count_record_strategies = []
-    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
-    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
     count_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMedian())
+    count_record_strategies.append(tv_testcase_util.RecordStrategyMax())
 
-    # Count recorders
+    # Duration record strategies
+    duration_record_strategies = []
+    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(25))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(50))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(75))
+    duration_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(95))
+
+    # Video delay record strategies
+    video_delay_record_strategies = []
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMean())
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMin())
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(25))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(50))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(75))
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyPercentile(95))
+    video_delay_record_strategies.append(tv_testcase_util.RecordStrategyMax())
+    video_delay_record_strategies.append(
+        tv_testcase_util.RecordStrategyStandardDeviation())
+
+    # Dictionary count recorders
     self._add_value_dictionary_recorder("CntDomEventListeners",
                                         count_record_strategies)
     self._add_value_dictionary_recorder("CntDomNodes", count_record_strategies)
     self._add_value_dictionary_recorder("CntDomHtmlElements",
                                         count_record_strategies)
+    self._add_value_dictionary_recorder("CntDomDocumentHtmlElements",
+                                        count_record_strategies)
     self._add_value_dictionary_recorder("CntDomHtmlElementsCreated",
                                         count_record_strategies)
     self._add_value_dictionary_recorder("CntDomUpdateMatchingRules",
@@ -95,19 +130,7 @@
     self._add_value_dictionary_recorder("CntLayoutUpdateCrossReferences",
                                         count_record_strategies)
 
-    # Duration record strategies
-    duration_record_strategies = []
-    duration_record_strategies.append(tv_testcase_util.RecordStrategyMean())
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(25))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(50))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(75))
-    duration_record_strategies.append(
-        tv_testcase_util.RecordStrategyPercentile(95))
-
-    # Duration recorders
+    # Dictionary duration recorders
     self._add_value_dictionary_recorder("DurTotalUs",
                                         duration_record_strategies)
     self._add_value_dictionary_recorder("DurDomInjectEventUs",
@@ -125,16 +148,32 @@
     self._add_value_dictionary_recorder("DurLayoutRenderAndAnimateUs",
                                         duration_record_strategies)
 
-    # Optional rasterize animations recorders
-    if options.record_rasterize_animations:
+    self.count_render_trees_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "CntRenderTrees", count_record_strategies)
+    self.count_requested_images_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "CntRequestedImages", count_record_strategies)
+
+    # Optional animations recorder
+    if options.record_animations:
       self.animations_recorder = tv_testcase_util.ResultsRecorder(
           self.event_name + "DurRasterizeAnimationsUs",
           duration_record_strategies)
+      self.animations_start_delay_recorder = tv_testcase_util.ResultsRecorder(
+          self.event_name + "DurAnimationsStartDelayUs",
+          duration_record_strategies)
+      self.animations_end_delay_recorder = tv_testcase_util.ResultsRecorder(
+          self.event_name + "DurAnimationsEndDelayUs",
+          duration_record_strategies)
 
-    # Optional video start delay recorder
-    if options.record_video_start_delay:
+    self.final_render_tree_delay_recorder = tv_testcase_util.ResultsRecorder(
+        self.event_name + "DurFinalRenderTreeDelayUs",
+        duration_record_strategies)
+
+    # Optional video recorder
+    if options.record_video:
       self.video_delay_recorder = tv_testcase_util.ResultsRecorder(
-          self.event_name + "DurVideoStartDelayUs", duration_record_strategies)
+          self.event_name + "DurVideoStartDelayUs",
+          video_delay_record_strategies)
 
   def _add_value_dictionary_recorder(self, key, record_strategies):
     recorder = tv_testcase_util.ResultsRecorder(self.event_name + key,
@@ -143,7 +182,10 @@
 
   def on_start_event(self):
     """Handles logic related to the start of the event instance."""
-    pass
+    self.count_render_trees_start = self.test.get_cval(
+        c_val_names.count_rasterize_new_render_tree())
+    self.count_requested_images_start = self.test.get_cval(
+        c_val_names.count_image_cache_requested_resources())
 
   def on_end_event(self):
     """Handles logic related to the end of the event instance."""
@@ -159,18 +201,44 @@
           self.event_name, self.render_tree_failure_count))
       return
 
+    event_start_time = value_dictionary.get("StartTime")
+
     # Record all of the values from the event.
     for value_dictionary_recorder in self.value_dictionary_recorders:
       value = value_dictionary.get(value_dictionary_recorder[0])
       if value is not None:
         value_dictionary_recorder[1].collect_value(value)
 
+    self.count_render_trees_end = self.test.get_cval(
+        c_val_names.count_rasterize_new_render_tree())
+    self.count_render_trees_recorder.collect_value(
+        self.count_render_trees_end - self.count_render_trees_start)
+
+    self.count_requested_images_end = self.test.get_cval(
+        c_val_names.count_image_cache_requested_resources())
+    self.count_requested_images_recorder.collect_value(
+        self.count_requested_images_end - self.count_requested_images_start)
+
     if self.animations_recorder:
       animation_entries = self.test.get_cval(
           c_val_names.rasterize_animations_entry_list())
       for value in animation_entries:
         self.animations_recorder.collect_value(value)
 
+      animations_start_time = self.test.get_cval(
+          c_val_names.time_rasterize_animations_start())
+      self.animations_start_delay_recorder.collect_value(
+          animations_start_time - event_start_time)
+      animations_end_time = self.test.get_cval(
+          c_val_names.time_rasterize_animations_end())
+      self.animations_end_delay_recorder.collect_value(animations_end_time -
+                                                       event_start_time)
+
+    final_render_tree_time = self.test.get_cval(
+        c_val_names.time_rasterize_new_render_tree())
+    self.final_render_tree_delay_recorder.collect_value(final_render_tree_time -
+                                                        event_start_time)
+
     if self.video_delay_recorder:
       self.video_delay_recorder.collect_value(
           self.test.get_cval(
@@ -182,8 +250,15 @@
     for value_dictionary_recorder in self.value_dictionary_recorders:
       value_dictionary_recorder[1].on_end_test()
 
+    self.count_render_trees_recorder.on_end_test()
+    self.count_requested_images_recorder.on_end_test()
+
     if self.animations_recorder:
       self.animations_recorder.on_end_test()
+      self.animations_start_delay_recorder.on_end_test()
+      self.animations_end_delay_recorder.on_end_test()
+
+    self.final_render_tree_delay_recorder.on_end_test()
 
     if self.video_delay_recorder:
       self.video_delay_recorder.on_end_test()
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
index 53ccbd4..6f9981c 100755
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_runner.py
@@ -114,6 +114,9 @@
   def __init__(self, platform, executable, devkit_name, command_line_args,
                default_url, log_file_path):
     global _default_url
+    if default_url is not None:
+      _default_url = default_url
+
     self.selenium_webdriver_module = tv_testcase_util.import_selenium_module(
         "webdriver")
 
@@ -133,10 +136,7 @@
     args.append("--enable_webdriver")
     args.append("--null_savegame")
     args.append("--debug_console=off")
-    args.append("--url=about:blank")
-
-    if default_url is not None:
-      _default_url = default_url
+    args.append("--url=" + _default_url)
 
     self.launcher.SetArgs(args)
     self.launcher.SetOutputCallback(self._HandleLine)
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase_util.py b/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
index eb1154d..5d9f87f 100644
--- a/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
+++ b/src/cobalt/webdriver_benchmarks/tv_testcase_util.py
@@ -14,6 +14,11 @@
 
 import container_util
 
+try:
+  import custom_query_param_constants as query_param_constants
+except ImportError:
+  import default_query_param_constants as query_param_constants
+
 # These are watched for in webdriver_benchmark_test.py
 TEST_RESULT = "webdriver_benchmark TEST RESULT"
 TEST_COMPLETE = "webdriver_benchmark TEST COMPLETE"
@@ -56,12 +61,24 @@
   return module
 
 
-def generate_url(default_url, query_params):
-  """Returns the URL indicated by the path and query parameters."""
-  if not query_params:
-    return default_url
+def generate_url(default_url, query_params_override=None):
+  """Returns the URL indicated by the path and query parameters.
 
+  Args:
+    default_url: the default url to use; its query params may be overridden
+    query_params_override: optional query params that override the ones
+                           contained within the default URL
+  Returns:
+    the url generated from the parameters
+  """
   parsed_url = list(urlparse.urlparse(default_url))
+
+  query_params = query_param_constants.BASE_QUERY_PARAMS
+  if query_params_override:
+    query_params.update(query_params_override)
+  else:
+    query_params.update(urlparse.parse_qsl(parsed_url[4]))
+
   parsed_url[4] = urlencode(query_params, doseq=True)
   return urlparse.urlunparse(parsed_url)
 
@@ -157,6 +174,22 @@
                        container_util.percentile(values, self.percentile))
 
 
+class RecordStrategyStandardDeviation(object):
+  """Records the standard deviation of an array of values."""
+
+  def run(self, name, values):
+    """Records the standard deviation of an array of values.
+
+    Args:
+      name: string name of test case
+      values: must be array of JSON encodable scalar
+    Raises:
+      RuntimeError: Raised on invalid args.
+    """
+    record_test_result("{}StdDev".format(name),
+                       container_util.sample_standard_deviation(values))
+
+
 class ResultsRecorder(object):
   """"Collects values and records results after a benchmark test ends."""
 
diff --git a/src/cobalt/websocket/close_event.h b/src/cobalt/websocket/close_event.h
index 43b7a79..97a499d 100644
--- a/src/cobalt/websocket/close_event.h
+++ b/src/cobalt/websocket/close_event.h
@@ -30,13 +30,25 @@
       : Event(type), was_clean_(true), code_(net::kWebSocketNormalClosure) {}
   explicit CloseEvent(const std::string& type)
       : Event(type), was_clean_(true), code_(net::kWebSocketNormalClosure) {}
-  CloseEvent(const base::Token type, const CloseEventInit& eventInitDict)
-      : Event(type), was_clean_(true), code_(net::kWebSocketNormalClosure) {
-    InitializeFromCloseEventInit(eventInitDict);
+  CloseEvent(const base::Token type, const CloseEventInit& init_dict)
+      : Event(type, init_dict),
+        was_clean_(true),
+        code_(net::kWebSocketNormalClosure) {
+    InitializeFromCloseEventInit(init_dict);
   }
-  CloseEvent(const std::string& type, const CloseEventInit& eventInitDict)
-      : Event(type), was_clean_(true), code_(net::kWebSocketNormalClosure) {
-    InitializeFromCloseEventInit(eventInitDict);
+  CloseEvent(const std::string& type, const CloseEventInit& init_dict)
+      : Event(type, init_dict),
+        was_clean_(true),
+        code_(net::kWebSocketNormalClosure) {
+    InitializeFromCloseEventInit(init_dict);
+  }
+
+  void InitCloseEvent(const std::string& type, bool can_bubble, bool cancelable,
+                      bool was_clean, uint16 code, const std::string& reason) {
+    InitEvent(type, can_bubble, cancelable);
+    was_clean_ = was_clean;
+    code_ = code;
+    reason_ = reason;
   }
 
   // Readonly Attributes.
@@ -47,15 +59,15 @@
   DEFINE_WRAPPABLE_TYPE(CloseEvent)
 
  private:
-  void InitializeFromCloseEventInit(const CloseEventInit& eventInitDict) {
-    if (eventInitDict.has_was_clean()) {
-      was_clean_ = eventInitDict.was_clean();
+  void InitializeFromCloseEventInit(const CloseEventInit& init_dict) {
+    if (init_dict.has_was_clean()) {
+      was_clean_ = init_dict.was_clean();
     }
-    if (eventInitDict.has_code()) {
-      code_ = eventInitDict.code();
+    if (init_dict.has_code()) {
+      code_ = init_dict.code();
     }
-    if (eventInitDict.has_reason()) {
-      reason_ = eventInitDict.reason();
+    if (init_dict.has_reason()) {
+      reason_ = init_dict.reason();
     }
   }
   bool was_clean_;
diff --git a/src/cobalt/websocket/close_event.idl b/src/cobalt/websocket/close_event.idl
index 425e0d2..f99dade 100644
--- a/src/cobalt/websocket/close_event.idl
+++ b/src/cobalt/websocket/close_event.idl
@@ -19,5 +19,12 @@
   readonly attribute boolean wasClean;
   readonly attribute unsigned short code;
   readonly attribute DOMString reason;
+
+  void initCloseEvent(DOMString type,
+                      boolean canBubble,
+                      boolean cancelable,
+                      boolean wasClean,
+                      unsigned short code,
+                      DOMString reason);
 };
 
diff --git a/src/cobalt/websocket/websocket.gyp b/src/cobalt/websocket/websocket.gyp
index 1c377ef..1fe61a7 100644
--- a/src/cobalt/websocket/websocket.gyp
+++ b/src/cobalt/websocket/websocket.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
diff --git a/src/cobalt/xhr/xhr.gyp b/src/cobalt/xhr/xhr.gyp
index 8d4d574..34203b8 100644
--- a/src/cobalt/xhr/xhr.gyp
+++ b/src/cobalt/xhr/xhr.gyp
@@ -14,7 +14,7 @@
 
 {
   'variables': {
-    'cobalt_code': 1,
+    'sb_pedantic_warnings': 1,
   },
   'targets': [
     {
@@ -33,6 +33,24 @@
         '<(DEPTH)/cobalt/speech/speech.gyp:speech',
         '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
       ],
+      'conditions': [
+        ['enable_xhr_header_filtering == 1', {
+          'sources': [
+            'xhr_modify_headers.h',
+          ],
+          'dependencies': [
+            '<@(cobalt_platform_dependencies)',
+          ],
+          'defines': [
+            'COBALT_ENABLE_XHR_HEADER_FILTERING',
+          ],
+          'direct_dependent_settings': {
+            'defines': [
+              'COBALT_ENABLE_XHR_HEADER_FILTERING',
+            ],
+          },
+        }],
+      ],
     },
     {
       'target_name': 'xhr_test',
diff --git a/src/cobalt/xhr/xhr_modify_headers.h b/src/cobalt/xhr/xhr_modify_headers.h
new file mode 100644
index 0000000..a1a1afc
--- /dev/null
+++ b/src/cobalt/xhr/xhr_modify_headers.h
@@ -0,0 +1,28 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_XHR_XHR_MODIFY_HEADERS_H_
+#define COBALT_XHR_XHR_MODIFY_HEADERS_H_
+
+#include <net/http/http_request_headers.h>
+
+namespace cobalt {
+namespace xhr {
+
+void CobaltXhrModifyHeader(net::HttpRequestHeaders* headers);
+
+}  // namespace xhr
+}  // namespace cobalt
+
+#endif  // COBALT_XHR_XHR_MODIFY_HEADERS_H_
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index 3a9c8e3..fb2b97d 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -33,6 +33,7 @@
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
+#include "cobalt/xhr/xhr_modify_headers.h"
 #include "nb/memory_scope.h"
 #include "net/http/http_util.h"
 
@@ -324,6 +325,10 @@
   error_ = false;
   upload_complete_ = false;
 
+#if defined(COBALT_ENABLE_XHR_HEADER_FILTERING)
+  CobaltXhrModifyHeader(&request_headers_);
+#endif
+
   std::string request_body_text;
   // Add request body, if appropriate.
   if ((method_ == net::URLFetcher::POST || method_ == net::URLFetcher::PUT) &&
diff --git a/src/media/base/video_resolution.h b/src/media/base/video_resolution.h
index 714cca1..29ae65f 100644
--- a/src/media/base/video_resolution.h
+++ b/src/media/base/video_resolution.h
@@ -17,6 +17,7 @@
 #ifndef MEDIA_BASE_VIDEO_RESOLUTION_H_
 #define MEDIA_BASE_VIDEO_RESOLUTION_H_
 
+#include "base/logging.h"
 #include "media/base/media_export.h"
 #include "ui/gfx/size.h"
 
@@ -24,11 +25,15 @@
 
 // Enumerates the various representations of the resolution of videos.  Note
 // that except |kVideoResolutionInvalid|, all other values are guaranteed to be
-// in the same order as its (width, height) pair.
+// in the same order as its (width, height) pair. Note, unlike the other valid
+// resolution levels, |kVideoResolutionHighRes| is not a 16:9 resolution.
 enum VideoResolution {
-  kVideoResolution1080p,  // 1920 x 1080
-  kVideoResolution2k,     // 2560 x 1440
-  kVideoResolution4k,     // 3840 x 2160
+  kVideoResolution1080p,    // 1920 x 1080
+  kVideoResolution2k,       // 2560 x 1440
+  kVideoResolution4k,       // 3840 x 2160
+  kVideoResolution5k,       // 5120 × 2880
+  kVideoResolution8k,       // 7680 x 4320
+  kVideoResolutionHighRes,  // 8192 x 8192
   kVideoResolutionInvalid
 };
 
@@ -42,6 +47,17 @@
   if (width <= 3840 && height <= 2160) {
     return kVideoResolution4k;
   }
+  if (width <= 5120 && height <= 2880) {
+    return kVideoResolution5k;
+  }
+  if (width <= 7680 && height <= 4320) {
+    return kVideoResolution8k;
+  }
+  if (width <= 8192 && height <= 8192) {
+    return kVideoResolutionHighRes;
+  }
+  DLOG(FATAL) << "Invalid VideoResolution: width: " << width
+              << " height: " << height;
   return kVideoResolutionInvalid;
 }
 
diff --git a/src/nb/bidirectional_fit_reuse_allocator.cc b/src/nb/bidirectional_fit_reuse_allocator.cc
new file mode 100644
index 0000000..6e0ad7a
--- /dev/null
+++ b/src/nb/bidirectional_fit_reuse_allocator.cc
@@ -0,0 +1,67 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/bidirectional_fit_reuse_allocator.h"
+
+#include <algorithm>
+
+#include "nb/pointer_arithmetic.h"
+#include "starboard/log.h"
+#include "starboard/types.h"
+
+namespace nb {
+
+BidirectionalFitReuseAllocator::BidirectionalFitReuseAllocator(
+    Allocator* fallback_allocator,
+    std::size_t initial_capacity,
+    std::size_t small_allocation_threshold,
+    std::size_t allocation_increment /*= 0*/
+    )
+    : ReuseAllocatorBase(fallback_allocator,
+                         initial_capacity,
+                         allocation_increment),
+      small_allocation_threshold_(small_allocation_threshold) {}
+
+ReuseAllocatorBase::FreeBlockSet::iterator
+BidirectionalFitReuseAllocator::FindFreeBlock(std::size_t size,
+                                              std::size_t alignment,
+                                              FreeBlockSet::iterator begin,
+                                              FreeBlockSet::iterator end,
+                                              bool* allocate_from_front) {
+  SB_DCHECK(allocate_from_front);
+
+  *allocate_from_front = size > small_allocation_threshold_;
+
+  if (*allocate_from_front) {
+    // Start looking through the free list from the front.
+    for (FreeBlockSet::iterator it = begin; it != end; ++it) {
+      if (it->CanFullfill(size, alignment)) {
+        return it;
+      }
+    }
+  }
+
+  // Start looking through the free list from the back.
+  FreeBlockSet::reverse_iterator rbegin(end);
+  FreeBlockSet::reverse_iterator rend(begin);
+  for (FreeBlockSet::reverse_iterator it = rbegin; it != rend; ++it) {
+    if (it->CanFullfill(size, alignment)) {
+      return --it.base();
+    }
+  }
+
+  return end;
+}
+
+}  // namespace nb
diff --git a/src/nb/bidirectional_fit_reuse_allocator.h b/src/nb/bidirectional_fit_reuse_allocator.h
new file mode 100644
index 0000000..29662a3
--- /dev/null
+++ b/src/nb/bidirectional_fit_reuse_allocator.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_BIDIRECTIONAL_FIT_REUSE_ALLOCATOR_H_
+#define NB_BIDIRECTIONAL_FIT_REUSE_ALLOCATOR_H_
+
+#include "nb/reuse_allocator_base.h"
+#include "starboard/configuration.h"
+
+namespace nb {
+
+// This class uses first-fit allocation strategy to allocate memory block whose
+// size is greater than the |small_allocation_threshold|.  It uses last-fit
+// strategy to allocate memory block whose size is less than or equal to the
+// |small_allocation_threshold|.  This allows better fragmentation management.
+// If fragmentation is not an issue, FirstFitReuseAllocator might be a simpler
+// alternative.
+// Note that when using this class a significant |initial_capacity| should be
+// set as otherwise new allocations will almost always allocate from the front
+// of the fallback allocator.
+class BidirectionalFitReuseAllocator : public ReuseAllocatorBase {
+ public:
+  BidirectionalFitReuseAllocator(Allocator* fallback_allocator,
+                                 std::size_t initial_capacity,
+                                 std::size_t small_allocation_threshold,
+                                 std::size_t allocation_increment = 0);
+
+  FreeBlockSet::iterator FindFreeBlock(std::size_t size,
+                                       std::size_t alignment,
+                                       FreeBlockSet::iterator begin,
+                                       FreeBlockSet::iterator end,
+                                       bool* allocate_from_front) SB_OVERRIDE;
+
+ private:
+  std::size_t small_allocation_threshold_;
+};
+
+}  // namespace nb
+
+#endif  // NB_BIDIRECTIONAL_FIT_REUSE_ALLOCATOR_H_
diff --git a/src/nb/bidirectional_fit_reuse_allocator_test.cc b/src/nb/bidirectional_fit_reuse_allocator_test.cc
new file mode 100644
index 0000000..6c08b9b
--- /dev/null
+++ b/src/nb/bidirectional_fit_reuse_allocator_test.cc
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/bidirectional_fit_reuse_allocator.h"
+
+#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"
+
+namespace {
+
+inline bool IsAligned(void* ptr, std::size_t boundary) {
+  uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr);
+  return ptr_as_int % boundary == 0;
+}
+
+class BidirectionalFitReuseAllocatorTest : public ::testing::Test {
+ public:
+  static const int kBufferSize = 1 * 1024 * 1024;
+
+  BidirectionalFitReuseAllocatorTest() { ResetAllocator(); }
+
+ protected:
+  void ResetAllocator(std::size_t initial_capacity = 0,
+                      std::size_t small_allocation_threshold = 0,
+                      std::size_t allocation_increment = 0) {
+    nb::scoped_array<uint8_t> buffer(new uint8_t[kBufferSize]);
+    nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator(
+        new nb::FixedNoFreeAllocator(buffer.get(), kBufferSize));
+    allocator_.reset(new nb::BidirectionalFitReuseAllocator(
+        fallback_allocator.get(), initial_capacity, small_allocation_threshold,
+        allocation_increment));
+
+    fallback_allocator_.swap(fallback_allocator);
+    buffer_.swap(buffer);
+  }
+
+  nb::scoped_array<uint8_t> buffer_;
+  nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
+  nb::scoped_ptr<nb::BidirectionalFitReuseAllocator> allocator_;
+};
+
+}  // namespace
+
+TEST_F(BidirectionalFitReuseAllocatorTest, AlignmentCheck) {
+  const std::size_t kAlignments[] = {4, 16, 256, 32768};
+  const std::size_t kBlockSizes[] = {4, 97, 256, 65201};
+  for (int i = 0; i < SB_ARRAY_SIZE(kAlignments); ++i) {
+    for (int j = 0; j < SB_ARRAY_SIZE(kBlockSizes); ++j) {
+      void* p = allocator_->Allocate(kBlockSizes[j], kAlignments[i]);
+      EXPECT_TRUE(p != NULL);
+      EXPECT_EQ(IsAligned(p, kAlignments[i]), true);
+      allocator_->Free(p);
+    }
+  }
+}
+
+// Check that the reuse allocator actually merges adjacent free blocks.
+TEST_F(BidirectionalFitReuseAllocatorTest, FreeBlockMergingLeft) {
+  const std::size_t kBlockSizes[] = {156, 16475};
+  const std::size_t kAlignment = 4;
+  void* blocks[] = {NULL, NULL};
+  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
+  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
+  // In an empty allocator we expect first alloc to be < second.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[0]),
+            reinterpret_cast<uintptr_t>(blocks[1]));
+  allocator_->Free(blocks[0]);
+  allocator_->Free(blocks[1]);
+  // Should have merged blocks 1 with block 0.
+  void* test_p =
+      allocator_->Allocate(kBlockSizes[0] + kBlockSizes[1], kAlignment);
+  EXPECT_EQ(test_p, blocks[0]);
+  allocator_->Free(test_p);
+}
+
+TEST_F(BidirectionalFitReuseAllocatorTest, FreeBlockMergingRight) {
+  const std::size_t kBlockSizes[] = {156, 202, 354};
+  const std::size_t kAlignment = 4;
+  void* blocks[] = {NULL, NULL, NULL};
+  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
+  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
+  blocks[2] = allocator_->Allocate(kBlockSizes[2], kAlignment);
+  // In an empty allocator we expect first alloc to be < second.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[1]),
+            reinterpret_cast<uintptr_t>(blocks[2]));
+  allocator_->Free(blocks[2]);
+  allocator_->Free(blocks[1]);
+  // Should have merged block 1 with block 2.
+  void* test_p =
+      allocator_->Allocate(kBlockSizes[1] + kBlockSizes[2], kAlignment);
+  EXPECT_EQ(test_p, blocks[1]);
+  allocator_->Free(test_p);
+  allocator_->Free(blocks[0]);
+}
+
+TEST_F(BidirectionalFitReuseAllocatorTest, InitialCapacity) {
+  const std::size_t kInitialCapacity = kBufferSize / 2;
+  ResetAllocator(kInitialCapacity);
+  EXPECT_GE(fallback_allocator_->GetAllocated(), kInitialCapacity);
+}
+
+TEST_F(BidirectionalFitReuseAllocatorTest, AllocationIncrement) {
+  const std::size_t kAllocationIncrement = kBufferSize / 2;
+  ResetAllocator(0, 0, kAllocationIncrement);
+  void* p = allocator_->Allocate(1, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+  EXPECT_GE(fallback_allocator_->GetAllocated(), kAllocationIncrement);
+}
+
+TEST_F(BidirectionalFitReuseAllocatorTest, FallbackBlockMerge) {
+  void* p = allocator_->Allocate(kBufferSize, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+
+  ResetAllocator();
+
+  p = allocator_->Allocate(kBufferSize / 2, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+
+  p = allocator_->Allocate(kBufferSize, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+}
+
+TEST_F(BidirectionalFitReuseAllocatorTest, AllocationsWithThreshold) {
+  const std::size_t kSmallAllocationThreshold = 1024;
+
+  ResetAllocator(kBufferSize, kSmallAllocationThreshold, 0);
+
+  void* small_allocation_1 =
+      allocator_->Allocate(kSmallAllocationThreshold - 1, 1);
+  EXPECT_TRUE(small_allocation_1 != NULL);
+
+  void* large_allocation_1 =
+      allocator_->Allocate(kSmallAllocationThreshold + 1, 1);
+  EXPECT_TRUE(large_allocation_1 != NULL);
+
+  // According to the spec of BidirectionalFitReuseAllocator, any memory block
+  // whose size is equal to the threshold is allocated from the back.
+  void* small_allocation_2 = allocator_->Allocate(kSmallAllocationThreshold, 1);
+  EXPECT_TRUE(small_allocation_2 != NULL);
+
+  void* small_allocation_3 = allocator_->Allocate(1, 1);
+  EXPECT_TRUE(small_allocation_3 != NULL);
+
+  // Large allocations are allocated from the front, small allocations are
+  // allocated from the back.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(large_allocation_1),
+            reinterpret_cast<uintptr_t>(small_allocation_3));
+  EXPECT_LT(reinterpret_cast<uintptr_t>(small_allocation_3),
+            reinterpret_cast<uintptr_t>(small_allocation_2));
+  EXPECT_LT(reinterpret_cast<uintptr_t>(small_allocation_2),
+            reinterpret_cast<uintptr_t>(small_allocation_1));
+
+  allocator_->Free(small_allocation_1);
+  allocator_->Free(small_allocation_2);
+  allocator_->Free(large_allocation_1);
+}
diff --git a/src/nb/first_fit_reuse_allocator.cc b/src/nb/first_fit_reuse_allocator.cc
new file mode 100644
index 0000000..29f93a6
--- /dev/null
+++ b/src/nb/first_fit_reuse_allocator.cc
@@ -0,0 +1,53 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/first_fit_reuse_allocator.h"
+
+#include <algorithm>
+
+#include "nb/pointer_arithmetic.h"
+#include "starboard/log.h"
+#include "starboard/types.h"
+
+namespace nb {
+
+FirstFitReuseAllocator::FirstFitReuseAllocator(
+    Allocator* fallback_allocator,
+    std::size_t initial_capacity,
+    std::size_t allocation_increment /*= 0*/)
+    : ReuseAllocatorBase(fallback_allocator,
+                         initial_capacity,
+                         allocation_increment) {}
+
+ReuseAllocatorBase::FreeBlockSet::iterator
+FirstFitReuseAllocator::FindFreeBlock(std::size_t size,
+                                      std::size_t alignment,
+                                      FreeBlockSet::iterator begin,
+                                      FreeBlockSet::iterator end,
+                                      bool* allocate_from_front) {
+  SB_DCHECK(allocate_from_front);
+
+  *allocate_from_front = true;
+
+  // Start looking through the free list from the front.
+  for (FreeBlockSet::iterator it = begin; it != end; ++it) {
+    if (it->CanFullfill(size, alignment)) {
+      return it;
+    }
+  }
+
+  return end;
+}
+
+}  // namespace nb
diff --git a/src/nb/first_fit_reuse_allocator.h b/src/nb/first_fit_reuse_allocator.h
new file mode 100644
index 0000000..ae4b599
--- /dev/null
+++ b/src/nb/first_fit_reuse_allocator.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_FIRST_FIT_REUSE_ALLOCATOR_H_
+#define NB_FIRST_FIT_REUSE_ALLOCATOR_H_
+
+#include "nb/reuse_allocator_base.h"
+#include "starboard/configuration.h"
+
+namespace nb {
+
+// This class uses first-fit allocation strategy to allocate memory.
+class FirstFitReuseAllocator : public ReuseAllocatorBase {
+ public:
+  FirstFitReuseAllocator(Allocator* fallback_allocator,
+                         std::size_t initial_capacity,
+                         std::size_t allocation_increment = 0);
+
+  FreeBlockSet::iterator FindFreeBlock(std::size_t size,
+                                       std::size_t alignment,
+                                       FreeBlockSet::iterator begin,
+                                       FreeBlockSet::iterator end,
+                                       bool* allocate_from_front) SB_OVERRIDE;
+};
+
+}  // namespace nb
+
+#endif  // NB_FIRST_FIT_REUSE_ALLOCATOR_H_
diff --git a/src/nb/first_fit_reuse_allocator_test.cc b/src/nb/first_fit_reuse_allocator_test.cc
new file mode 100644
index 0000000..2e51bd2
--- /dev/null
+++ b/src/nb/first_fit_reuse_allocator_test.cc
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/first_fit_reuse_allocator.h"
+
+#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"
+
+namespace {
+
+inline bool IsAligned(void* ptr, std::size_t boundary) {
+  uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr);
+  return ptr_as_int % boundary == 0;
+}
+
+class FirstFitReuseAllocatorTest : public ::testing::Test {
+ public:
+  static const int kBufferSize = 1 * 1024 * 1024;
+
+  FirstFitReuseAllocatorTest() { ResetAllocator(); }
+
+ protected:
+  void ResetAllocator(std::size_t initial_capacity = 0,
+                      std::size_t allocation_increment = 0) {
+    nb::scoped_array<uint8_t> buffer(new uint8_t[kBufferSize]);
+    nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator(
+        new nb::FixedNoFreeAllocator(buffer.get(), kBufferSize));
+    allocator_.reset(new nb::FirstFitReuseAllocator(
+        fallback_allocator.get(), initial_capacity, allocation_increment));
+
+    fallback_allocator_.swap(fallback_allocator);
+    buffer_.swap(buffer);
+  }
+
+  nb::scoped_array<uint8_t> buffer_;
+  nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
+  nb::scoped_ptr<nb::FirstFitReuseAllocator> allocator_;
+};
+
+}  // namespace
+
+TEST_F(FirstFitReuseAllocatorTest, AlignmentCheck) {
+  const std::size_t kAlignments[] = {4, 16, 256, 32768};
+  const std::size_t kBlockSizes[] = {4, 97, 256, 65201};
+  for (int i = 0; i < SB_ARRAY_SIZE(kAlignments); ++i) {
+    for (int j = 0; j < SB_ARRAY_SIZE(kBlockSizes); ++j) {
+      void* p = allocator_->Allocate(kBlockSizes[j], kAlignments[i]);
+      EXPECT_TRUE(p != NULL);
+      EXPECT_EQ(IsAligned(p, kAlignments[i]), true);
+      allocator_->Free(p);
+    }
+  }
+}
+
+// Check that the reuse allocator actually merges adjacent free blocks.
+TEST_F(FirstFitReuseAllocatorTest, FreeBlockMergingLeft) {
+  const std::size_t kBlockSizes[] = {156, 202};
+  const std::size_t kAlignment = 4;
+  void* blocks[] = {NULL, NULL};
+  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
+  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
+  // In an empty allocator we expect first alloc to be < second.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[0]),
+            reinterpret_cast<uintptr_t>(blocks[1]));
+  allocator_->Free(blocks[0]);
+  allocator_->Free(blocks[1]);
+  // Should have merged blocks 1 with block 0.
+  void* test_p =
+      allocator_->Allocate(kBlockSizes[0] + kBlockSizes[1], kAlignment);
+  EXPECT_EQ(test_p, blocks[0]);
+  allocator_->Free(test_p);
+}
+
+TEST_F(FirstFitReuseAllocatorTest, FreeBlockMergingRight) {
+  const std::size_t kBlockSizes[] = {156, 202, 354};
+  const std::size_t kAlignment = 4;
+  void* blocks[] = {NULL, NULL, NULL};
+  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
+  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
+  blocks[2] = allocator_->Allocate(kBlockSizes[2], kAlignment);
+  // In an empty allocator we expect first alloc to be < second.
+  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[1]),
+            reinterpret_cast<uintptr_t>(blocks[2]));
+  allocator_->Free(blocks[2]);
+  allocator_->Free(blocks[1]);
+  // Should have merged block 1 with block 2.
+  void* test_p =
+      allocator_->Allocate(kBlockSizes[1] + kBlockSizes[2], kAlignment);
+  EXPECT_EQ(test_p, blocks[1]);
+  allocator_->Free(test_p);
+  allocator_->Free(blocks[0]);
+}
+
+TEST_F(FirstFitReuseAllocatorTest, InitialCapacity) {
+  const std::size_t kInitialCapacity = kBufferSize / 2;
+  ResetAllocator(kInitialCapacity);
+  EXPECT_GE(fallback_allocator_->GetAllocated(), kInitialCapacity);
+}
+
+TEST_F(FirstFitReuseAllocatorTest, AllocationIncrement) {
+  const std::size_t kAllocationIncrement = kBufferSize / 2;
+  ResetAllocator(0, kAllocationIncrement);
+  void* p = allocator_->Allocate(1, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+  EXPECT_GE(fallback_allocator_->GetAllocated(), kAllocationIncrement);
+}
+
+TEST_F(FirstFitReuseAllocatorTest, FallbackBlockMerge) {
+  void* p = allocator_->Allocate(kBufferSize, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+
+  ResetAllocator();
+
+  p = allocator_->Allocate(kBufferSize / 2, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+
+  p = allocator_->Allocate(kBufferSize, 1);
+  EXPECT_TRUE(p != NULL);
+  allocator_->Free(p);
+}
diff --git a/src/nb/memory_pool.cc b/src/nb/memory_pool.cc
deleted file mode 100644
index 6cdef60..0000000
--- a/src/nb/memory_pool.cc
+++ /dev/null
@@ -1,46 +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 "nb/memory_pool.h"
-
-#include "starboard/log.h"
-
-namespace nb {
-
-MemoryPool::MemoryPool(void* buffer,
-                       std::size_t size,
-                       bool verify_full_capacity,
-                       std::size_t small_allocation_threshold)
-    : no_free_allocator_(buffer, size),
-      reuse_allocator_(&no_free_allocator_, size, small_allocation_threshold) {
-  SB_DCHECK(buffer);
-  SB_DCHECK(size != 0U);
-
-  // 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);
-  }
-}
-
-void MemoryPool::PrintAllocations() const {
-  reuse_allocator_.PrintAllocations();
-}
-
-}  // namespace nb
diff --git a/src/nb/memory_pool.h b/src/nb/memory_pool.h
index 8dff09e..b42f4e5 100644
--- a/src/nb/memory_pool.h
+++ b/src/nb/memory_pool.h
@@ -18,20 +18,33 @@
 #define NB_MEMORY_POOL_H_
 
 #include "nb/allocator.h"
+#include "nb/bidirectional_fit_reuse_allocator.h"
+#include "nb/first_fit_reuse_allocator.h"
 #include "nb/fixed_no_free_allocator.h"
-#include "nb/reuse_allocator.h"
+#include "starboard/log.h"
 
 namespace nb {
 
 // The MemoryPool class can be used to wrap a range of memory with allocators
-// such that the memory can be allocated out of and free'd memory re-used
-// as necessary.
+// such that the memory can be allocated out of and free'd memory re-used as
+// necessary.
+template <typename ReuseAllocator>
 class MemoryPool : public Allocator {
  public:
-  MemoryPool(void* buffer,
-             std::size_t size,
-             bool verify_full_capacity = false,
-             std::size_t small_allocation_threshold = 0);
+  MemoryPool(void* buffer, std::size_t size)
+      : no_free_allocator_(buffer, size),
+        reuse_allocator_(&no_free_allocator_, size) {
+    SB_DCHECK(buffer);
+    SB_DCHECK(size > 0U);
+  }
+
+  template <typename ParameterType>
+  MemoryPool(void* buffer, std::size_t size, ParameterType parameter1)
+      : no_free_allocator_(buffer, size),
+        reuse_allocator_(&no_free_allocator_, size, parameter1) {
+    SB_DCHECK(buffer);
+    SB_DCHECK(size > 0U);
+  }
 
   void* Allocate(std::size_t size) { return reuse_allocator_.Allocate(size); }
   void* Allocate(std::size_t size, std::size_t alignment) {
@@ -45,7 +58,7 @@
     return no_free_allocator_.GetAllocated();
   }
 
-  void PrintAllocations() const;
+  void PrintAllocations() const { reuse_allocator_.PrintAllocations(); }
 
  private:
   // A budget of memory to be used by the memory pool.
@@ -57,6 +70,9 @@
   ReuseAllocator reuse_allocator_;
 };
 
+typedef MemoryPool<BidirectionalFitReuseAllocator> BidirectionalFitMemoryPool;
+typedef MemoryPool<FirstFitReuseAllocator> FirstFitMemoryPool;
+
 }  // namespace nb
 
 #endif  // NB_MEMORY_POOL_H_
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp
index db80e43..f778a3c 100644
--- a/src/nb/nb.gyp
+++ b/src/nb/nb.gyp
@@ -32,13 +32,16 @@
             'analytics/memory_tracker_helpers.h',
             'atomic.h',
             'bit_cast.h',
+            'bidirectional_fit_reuse_allocator.h',
+            'bidirectional_fit_reuse_allocator.cc',
             'concurrent_map.h',
+            'first_fit_reuse_allocator.h',
+            'first_fit_reuse_allocator.cc',
             'fixed_no_free_allocator.cc',
             'fixed_no_free_allocator.h',
             'hash.cc',
             'hash.h',
             'lexical_cast.h',
-            'memory_pool.cc',
             'memory_pool.h',
             'memory_scope.cc',
             'memory_scope.h',
@@ -47,8 +50,8 @@
             'rect.h',
             'ref_counted.cc',
             'ref_counted.h',
-            'reuse_allocator.cc',
-            'reuse_allocator.h',
+            'reuse_allocator_base.cc',
+            'reuse_allocator_base.h',
             'rewindable_vector.h',
             'scoped_ptr.h',
             'simple_thread.cc',
@@ -87,11 +90,12 @@
             'analytics/memory_tracker_impl_test.cc',
             'analytics/memory_tracker_test.cc',
             'atomic_test.cc',
+            'bidirectional_fit_reuse_allocator_test.cc',
             'concurrent_map_test.cc',
+            'first_fit_reuse_allocator_test.cc',
             'fixed_no_free_allocator_test.cc',
             'lexical_cast_test.cc',
             'memory_scope_test.cc',
-            'reuse_allocator_test.cc',
             'rewindable_vector_test.cc',
             'run_all_unittests.cc',
             'std_allocator_test.cc',
diff --git a/src/nb/reuse_allocator.h b/src/nb/reuse_allocator.h
deleted file mode 100644
index 71d43d8..0000000
--- a/src/nb/reuse_allocator.h
+++ /dev/null
@@ -1,143 +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.
- */
-
-#ifndef NB_REUSE_ALLOCATOR_H_
-#define NB_REUSE_ALLOCATOR_H_
-
-#include <map>
-#include <set>
-#include <vector>
-
-#include "nb/allocator.h"
-
-namespace nb {
-
-// An allocator designed to accomodate cases where the memory allocated may not
-// be efficient or safe to access via the CPU.  It solves this problem by
-// 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);
-
-  // Marks the memory block as being free and it will then become recyclable
-  void Free(void* memory);
-
-  std::size_t GetCapacity() const { return capacity_; }
-  std::size_t GetAllocated() const { return total_allocated_; }
-
-  void PrintAllocations() const;
-
-  bool TryFree(void* memory);
-
- 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_;
-    }
-    // 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;
-
-  FreeBlockSet::iterator AddFreeBlock(MemoryBlock block_to_add);
-  void RemoveFreeBlock(FreeBlockSet::iterator it);
-
-  FreeBlockSet free_blocks_;
-  AllocatedBlockMap allocated_blocks_;
-
-  // 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.
-  std::size_t capacity_;
-
-  // How much has been allocated from us.
-  std::size_t total_allocated_;
-};
-
-}  // namespace nb
-
-#endif  // NB_REUSE_ALLOCATOR_H_
diff --git a/src/nb/reuse_allocator.cc b/src/nb/reuse_allocator_base.cc
similarity index 64%
rename from src/nb/reuse_allocator.cc
rename to src/nb/reuse_allocator_base.cc
index c12375d..75c0394 100644
--- a/src/nb/reuse_allocator.cc
+++ b/src/nb/reuse_allocator_base.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "nb/reuse_allocator.h"
+#include "nb/reuse_allocator_base.h"
 
 #include <algorithm>
 
@@ -28,7 +28,7 @@
 // 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) {
+bool ReuseAllocatorBase::MemoryBlock::Merge(const MemoryBlock& other) {
   if (AsInteger(address_) + size_ == AsInteger(other.address_)) {
     size_ += other.size_;
     return true;
@@ -41,19 +41,19 @@
   return false;
 }
 
-bool ReuseAllocator::MemoryBlock::CanFullfill(std::size_t request_size,
-                                              std::size_t alignment) const {
+bool ReuseAllocatorBase::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 {
+void ReuseAllocatorBase::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));
@@ -101,30 +101,107 @@
   }
 }
 
-ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator)
-    : fallback_allocator_(fallback_allocator),
-      small_allocation_threshold_(0),
-      capacity_(0),
-      total_allocated_(0) {}
+void* ReuseAllocatorBase::Allocate(std::size_t size) {
+  return Allocate(size, 1);
+}
 
-ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator,
-                               std::size_t capacity,
-                               std::size_t small_allocation_threshold)
+void* ReuseAllocatorBase::Allocate(std::size_t size, std::size_t alignment) {
+  // 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 = AlignUp(std::max(size, kMinAlignment), kMinAlignment);
+  alignment = AlignUp(std::max<std::size_t>(alignment, 1), kMinAlignment);
+
+  MemoryBlock allocated_block;
+  bool allocate_from_front;
+  FreeBlockSet::iterator free_block_iter =
+      FindFreeBlock(size, alignment, free_blocks_.begin(), free_blocks_.end(),
+                    &allocate_from_front);
+
+  if (free_block_iter == free_blocks_.end()) {
+    free_block_iter = ExpandToFit(size, alignment);
+    if (free_block_iter == free_blocks_.end()) {
+      return NULL;
+    }
+  }
+
+  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, allocate_from_front, &allocated_block,
+                 &free_block);
+  if (free_block.size() > 0) {
+    SB_DCHECK(free_block.address());
+    AddFreeBlock(free_block);
+  }
+  void* user_address = AlignUp(allocated_block.address(), alignment);
+  AddAllocatedBlock(user_address, allocated_block);
+
+  return user_address;
+}
+
+void ReuseAllocatorBase::Free(void* memory) {
+  bool result = TryFree(memory);
+  SB_DCHECK(result);
+}
+
+void ReuseAllocatorBase::PrintAllocations() const {
+  typedef std::map<std::size_t, std::size_t> SizesHistogram;
+  SizesHistogram sizes_histogram;
+  for (AllocatedBlockMap::const_iterator iter = allocated_blocks_.begin();
+       iter != allocated_blocks_.end(); ++iter) {
+    std::size_t block_size = iter->second.size();
+    if (sizes_histogram.find(block_size) == sizes_histogram.end()) {
+      sizes_histogram[block_size] = 0;
+    }
+    sizes_histogram[block_size] = sizes_histogram[block_size] + 1;
+  }
+
+  for (SizesHistogram::const_iterator iter = sizes_histogram.begin();
+       iter != sizes_histogram.end(); ++iter) {
+    SB_LOG(INFO) << iter->first << " : " << iter->second;
+  }
+  SB_LOG(INFO) << "Total allocations: " << allocated_blocks_.size();
+}
+
+bool ReuseAllocatorBase::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;
+}
+
+ReuseAllocatorBase::ReuseAllocatorBase(Allocator* fallback_allocator,
+                                       std::size_t initial_capacity,
+                                       std::size_t allocation_increment)
     : fallback_allocator_(fallback_allocator),
-      small_allocation_threshold_(small_allocation_threshold),
+      allocation_increment_(allocation_increment),
       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);
+  if (initial_capacity > 0) {
+    FreeBlockSet::iterator iter = ExpandToFit(initial_capacity, 1);
+    SB_DCHECK(iter != free_blocks_.end());
   }
 }
 
-ReuseAllocator::~ReuseAllocator() {
+ReuseAllocatorBase::~ReuseAllocatorBase() {
   // Assert that everything was freed.
   // Note that in some unit tests this may
   // not be the case.
@@ -138,7 +215,55 @@
   }
 }
 
-ReuseAllocator::FreeBlockSet::iterator ReuseAllocator::AddFreeBlock(
+ReuseAllocatorBase::FreeBlockSet::iterator ReuseAllocatorBase::ExpandToFit(
+    std::size_t size,
+    std::size_t alignment) {
+  void* ptr = NULL;
+  std::size_t size_to_try = 0;
+  if (allocation_increment_ > size) {
+    size_to_try = std::max(size, allocation_increment_);
+    ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
+  }
+  if (ptr == NULL) {
+    size_to_try = size;
+    ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
+  }
+  if (ptr != NULL) {
+    fallback_allocations_.push_back(ptr);
+    capacity_ += size_to_try;
+    return AddFreeBlock(MemoryBlock(ptr, size_to_try));
+  }
+
+  if (free_blocks_.empty()) {
+    return free_blocks_.end();
+  }
+
+  // We failed to allocate for |size| from the fallback allocator, try to
+  // allocate the difference between |size| and the size of the right most block
+  // in the hope that they are continuous and can be connect to a block that is
+  // large enough to fulfill |size|.
+  size_t size_difference = size - free_blocks_.rbegin()->size();
+  ptr = fallback_allocator_->AllocateForAlignment(&size_difference, alignment);
+  if (ptr == NULL) {
+    return free_blocks_.end();
+  }
+
+  fallback_allocations_.push_back(ptr);
+  capacity_ += size_difference;
+  AddFreeBlock(MemoryBlock(ptr, size_difference));
+  FreeBlockSet::iterator iter = free_blocks_.end();
+  --iter;
+  return iter->CanFullfill(size, alignment) ? iter : free_blocks_.end();
+}
+
+void ReuseAllocatorBase::AddAllocatedBlock(void* address,
+                                           const MemoryBlock& block) {
+  SB_DCHECK(allocated_blocks_.find(address) == allocated_blocks_.end());
+  allocated_blocks_[address] = block;
+  total_allocated_ += block.size();
+}
+
+ReuseAllocatorBase::FreeBlockSet::iterator ReuseAllocatorBase::AddFreeBlock(
     MemoryBlock block_to_add) {
   if (free_blocks_.size() == 0) {
     return free_blocks_.insert(block_to_add).first;
@@ -178,136 +303,8 @@
   return free_blocks_.insert(block_to_add).first;
 }
 
-void ReuseAllocator::RemoveFreeBlock(FreeBlockSet::iterator it) {
+void ReuseAllocatorBase::RemoveFreeBlock(FreeBlockSet::iterator it) {
   free_blocks_.erase(it);
 }
 
-void* ReuseAllocator::Allocate(std::size_t size) {
-  return Allocate(size, 1);
-}
-
-void* ReuseAllocator::Allocate(std::size_t size, std::size_t alignment) {
-  if (alignment == 0) {
-    alignment = 1;
-  }
-
-  // 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 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 kMinAlignment = 16;
-  size = std::max(size, kMinBlockSizeBytes);
-  size = AlignUp(size, kMinBlockSizeBytes);
-  alignment = AlignUp(alignment, kMinAlignment);
-
-  // Worst case how much memory we need.
-  MemoryBlock allocated_block;
-
-  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;
-      }
-    }
-  } 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 (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;
-    }
-    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();
-  return user_address;
-}
-
-void ReuseAllocator::Free(void* memory) {
-  bool result = TryFree(memory);
-  SB_DCHECK(result);
-}
-
-void ReuseAllocator::PrintAllocations() const {
-  typedef std::map<std::size_t, std::size_t> SizesHistogram;
-  SizesHistogram sizes_histogram;
-  for (AllocatedBlockMap::const_iterator iter = allocated_blocks_.begin();
-       iter != allocated_blocks_.end(); ++iter) {
-    std::size_t block_size = iter->second.size();
-    if (sizes_histogram.find(block_size) == sizes_histogram.end()) {
-      sizes_histogram[block_size] = 0;
-    }
-    sizes_histogram[block_size] = sizes_histogram[block_size] + 1;
-  }
-
-  for (SizesHistogram::const_iterator iter = sizes_histogram.begin();
-       iter != sizes_histogram.end(); ++iter) {
-    SB_LOG(INFO) << iter->first << " : " << iter->second;
-  }
-  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_base.h b/src/nb/reuse_allocator_base.h
new file mode 100644
index 0000000..e5782fd
--- /dev/null
+++ b/src/nb/reuse_allocator_base.h
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+#ifndef NB_REUSE_ALLOCATOR_BASE_H_
+#define NB_REUSE_ALLOCATOR_BASE_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "nb/allocator.h"
+#include "starboard/configuration.h"
+
+namespace nb {
+
+// The base class of allocators designed to accommodate cases where the memory
+// allocated may not be efficient or safe to access via the CPU.  It solves
+// this problem by 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.
+class ReuseAllocatorBase : public Allocator {
+ public:
+  void* Allocate(std::size_t size) SB_OVERRIDE;
+  void* Allocate(std::size_t size, std::size_t alignment) SB_OVERRIDE;
+
+  // Marks the memory block as being free and it will then become recyclable
+  void Free(void* memory) SB_OVERRIDE;
+
+  std::size_t GetCapacity() const SB_OVERRIDE { return capacity_; }
+  std::size_t GetAllocated() const SB_OVERRIDE { return total_allocated_; }
+
+  void PrintAllocations() const SB_OVERRIDE;
+
+  bool TryFree(void* memory);
+
+ protected:
+  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_;
+    }
+    // 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;
+
+  ReuseAllocatorBase(Allocator* fallback_allocator,
+                     std::size_t initial_capacity,
+                     std::size_t allocation_increment);
+  ~ReuseAllocatorBase() SB_OVERRIDE;
+
+  // The inherited class should implement this function to inform the base
+  // class which free block to take.  It returns |end| if no suitable free
+  // block is found.  When |allocate_from_front| is set to true, the allocation
+  // will take place in the front of a free block if the free block is big
+  // enough to fulfill this allocation and produce another free block.
+  // Otherwise the allocation will take place from the back.
+  virtual FreeBlockSet::iterator FindFreeBlock(std::size_t size,
+                                               std::size_t alignment,
+                                               FreeBlockSet::iterator begin,
+                                               FreeBlockSet::iterator end,
+                                               bool* allocate_from_front) = 0;
+
+ private:
+  // Map from pointers we returned to the user, back to memory blocks.
+  typedef std::map<void*, MemoryBlock> AllocatedBlockMap;
+
+  FreeBlockSet::iterator ExpandToFit(std::size_t size, std::size_t alignment);
+
+  void AddAllocatedBlock(void* address, const MemoryBlock& block);
+  FreeBlockSet::iterator AddFreeBlock(MemoryBlock block_to_add);
+  void RemoveFreeBlock(FreeBlockSet::iterator it);
+
+  AllocatedBlockMap allocated_blocks_;
+  FreeBlockSet free_blocks_;
+
+  // We will allocate from the given allocator whenever we can't find pre-used
+  // memory to allocate.
+  Allocator* fallback_allocator_;
+  std::size_t allocation_increment_;
+
+  // 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.
+  std::size_t capacity_;
+
+  // How much has been allocated from us.
+  std::size_t total_allocated_;
+};
+
+}  // namespace nb
+
+#endif  // NB_REUSE_ALLOCATOR_BASE_H_
diff --git a/src/nb/reuse_allocator_benchmark.cc b/src/nb/reuse_allocator_benchmark.cc
index 7fe94d4..786e400 100644
--- a/src/nb/reuse_allocator_benchmark.cc
+++ b/src/nb/reuse_allocator_benchmark.cc
@@ -21,8 +21,8 @@
 #include <vector>
 
 #include "nb/allocator.h"
+#include "nb/first_fit_reuse_allocator.h"
 #include "nb/fixed_no_free_allocator.h"
-#include "nb/reuse_allocator.h"
 #include "starboard/client_porting/wrap_main/wrap_main.h"
 #include "starboard/event.h"
 #include "starboard/file.h"
@@ -187,12 +187,13 @@
   void PrintAllocations() const SB_OVERRIDE {}
 };
 
+// TODO: Make this work with other ReuseAllocator types.
 void MemoryPlaybackTest(const std::string& filename) {
   const std::size_t kFixedNoFreeMemorySize = 512 * 1024 * 1024;
   void* fixed_no_free_memory = SbMemoryAllocate(kFixedNoFreeMemorySize);
   nb::FixedNoFreeAllocator fallback_allocator(fixed_no_free_memory,
                                               kFixedNoFreeMemorySize);
-  nb::ReuseAllocator reuse_allocator(&fallback_allocator);
+  nb::FirstFitReuseAllocator reuse_allocator(&fallback_allocator, 0);
 
   std::vector<AllocationCommand> commands;
   LoadAllocationPlayback(&commands, filename);
diff --git a/src/nb/reuse_allocator_test.cc b/src/nb/reuse_allocator_test.cc
deleted file mode 100644
index 4d42361..0000000
--- a/src/nb/reuse_allocator_test.cc
+++ /dev/null
@@ -1,138 +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 "nb/reuse_allocator.h"
-
-#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"
-
-namespace {
-
-inline bool IsAligned(void* ptr, std::size_t boundary) {
-  uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr);
-  return ptr_as_int % boundary == 0;
-}
-
-class ReuseAllocatorTest : public ::testing::Test {
- public:
-  static const int kBufferSize = 1 * 1024 * 1024;
-
-  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));
-    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));
-    }
-  }
-
-  nb::scoped_array<uint8_t> buffer_;
-  nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_;
-  nb::scoped_ptr<nb::ReuseAllocator> allocator_;
-};
-
-}  // namespace
-
-TEST_F(ReuseAllocatorTest, AlignmentCheck) {
-  const std::size_t kAlignments[] = {4, 16, 256, 32768};
-  const std::size_t kBlockSizes[] = {4, 97, 256, 65201};
-  for (int i = 0; i < SB_ARRAY_SIZE(kAlignments); ++i) {
-    for (int j = 0; j < SB_ARRAY_SIZE(kBlockSizes); ++j) {
-      void* p = allocator_->Allocate(kBlockSizes[j], kAlignments[i]);
-      // NOTE: Don't dereference p- this doesn't point anywhere valid.
-      EXPECT_TRUE(p != NULL);
-      EXPECT_EQ(IsAligned(p, kAlignments[i]), true);
-      allocator_->Free(p);
-    }
-  }
-}
-
-// Check that the reuse allocator actually merges adjacent free blocks.
-TEST_F(ReuseAllocatorTest, FreeBlockMergingLeft) {
-  const std::size_t kBlockSizes[] = {156, 202};
-  const std::size_t kAlignment = 4;
-  void* blocks[] = {NULL, NULL};
-  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
-  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
-  // In an empty allocator we expect first alloc to be < second.
-  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[0]),
-            reinterpret_cast<uintptr_t>(blocks[1]));
-  allocator_->Free(blocks[0]);
-  allocator_->Free(blocks[1]);
-  // Should have merged blocks 1 with block 0.
-  void* test_p =
-      allocator_->Allocate(kBlockSizes[0] + kBlockSizes[1], kAlignment);
-  EXPECT_EQ(test_p, blocks[0]);
-  allocator_->Free(test_p);
-}
-
-TEST_F(ReuseAllocatorTest, FreeBlockMergingRight) {
-  const std::size_t kBlockSizes[] = {156, 202, 354};
-  const std::size_t kAlignment = 4;
-  void* blocks[] = {NULL, NULL, NULL};
-  blocks[0] = allocator_->Allocate(kBlockSizes[0], kAlignment);
-  blocks[1] = allocator_->Allocate(kBlockSizes[1], kAlignment);
-  blocks[2] = allocator_->Allocate(kBlockSizes[2], kAlignment);
-  // In an empty allocator we expect first alloc to be < second.
-  EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[1]),
-            reinterpret_cast<uintptr_t>(blocks[2]));
-  allocator_->Free(blocks[2]);
-  allocator_->Free(blocks[1]);
-  // Should have merged block 1 with block 2.
-  void* test_p =
-      allocator_->Allocate(kBlockSizes[1] + kBlockSizes[2], kAlignment);
-  EXPECT_EQ(test_p, blocks[1]);
-  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/net/quic/test_tools/test_task_runner.h b/src/net/quic/test_tools/test_task_runner.h
index 47e4685..80e9b51 100644
--- a/src/net/quic/test_tools/test_task_runner.h
+++ b/src/net/quic/test_tools/test_task_runner.h
@@ -11,9 +11,9 @@
 
 #include <vector>
 
-#include "base/time.h"
-
+#include "base/logging.h"
 #include "base/stl_util.h"
+#include "base/time.h"
 #include "net/base/net_errors.h"
 #include "net/quic/test_tools/mock_clock.h"
 #include "net/quic/test_tools/quic_test_utils.h"
@@ -46,6 +46,11 @@
   virtual bool PostDelayedTask(const tracked_objects::Location& location,
                                const base::Closure& closure,
                                base::TimeDelta delta) OVERRIDE;
+  virtual bool PostBlockingTask(const tracked_objects::Location& from_here,
+                                const base::Closure& task) OVERRIDE {
+    NOTREACHED() << "Unsupported.";
+    return false;
+  }
 
   std::vector<PostedTask>::iterator FindNextTask();
 
diff --git a/src/starboard/CHANGELOG.md b/src/starboard/CHANGELOG.md
index 1446709..7ce54d9 100644
--- a/src/starboard/CHANGELOG.md
+++ b/src/starboard/CHANGELOG.md
@@ -6,6 +6,56 @@
 this file describes the changes made to the Starboard interface since the
 version previous to it.
 
+## Version 6
+
+### Introduce pointer (mouse) input support.
+
+This extends the `SbInput` interface with some enum values and data members to
+allow mouse, wheel, and more generic pointer input.
+
+### Flexible audio specific config.
+
+`SbMediaAudioHeader::audio_specific_config` will be a pointer instead of an
+array.
+
+### Time Zone API Cleanup
+
+Removes `SbTimeZoneGetDstName()` -- The Daylight Savings Time version of the
+time zone.
+
+Changes `SbTimeZoneGetName()` to be more flexible in what it is allowed to
+return.
+
+### SbDecodeTargetNumberOfPlanesForFormat
+
+Adds the convenience inline function, SbDecodeTargetNumberOfPlanesForFormat() to
+`starboard/decode_target.h`.
+
+### Preload Support
+
+Adds the `kSbEventTypePreload` event, and modifies the application state machine
+to utilize it.
+
+### Platform Error Cleanup
+
+Removes `SbSystemPlatformErrorType` values specific to user status.
+
+### SbDecodeTarget support for the UYVY (i.e. YUV 422) format
+
+Add support for UYVY decode targets (e.g. YUV 422) via the
+`kSbDecodeTargetFormat1PlaneUYVY` enum.
+
+### Add Color Remote Keys
+
+This adds SbKey codes for the colored keys found on most contemporary TV
+remotes.
+
+### kSbEventTypeLowMemory
+
+Adds a new event type -- `kSbEventTypeLowMemory` -- to allow a platform to
+signal that the application may soon be terminated due to low memory
+availability.
+
 ## Version 5
 
 ### Add Speech Recognizer API
diff --git a/src/starboard/atomic.h b/src/starboard/atomic.h
index 447958b..f0fa6d8 100644
--- a/src/starboard/atomic.h
+++ b/src/starboard/atomic.h
@@ -370,4 +370,342 @@
 // as inlined. This macro is defined on the command line by gyp.
 #include STARBOARD_ATOMIC_INCLUDE
 
+#ifdef __cplusplus
+
+#include "starboard/mutex.h"
+
+namespace starboard {
+
+// 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_base<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_base<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_base;
+
+// Subtype of atomic_base<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_base<T> may be instantiated with any TriviallyCopyable type T.
+// atomic_base<T> is neither copyable nor movable.
+template <typename T>
+class atomic_base {
+ public:
+  typedef T ValueType;
+
+  // C++11 forbids a copy constructor for std::atomic<T>, it also forbids
+  // a move operation.
+  atomic_base() : value_() {}
+  explicit atomic_base(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_base<T> that adds fetch_add(...) and fetch_sub(...).
+template <typename T>
+class atomic_number : public atomic_base<T> {
+ public:
+  typedef atomic_base<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_base<T> {
+ public:
+  typedef atomic_base<T> Super;
+  atomic_pointer() : Super() {}
+  explicit atomic_pointer(T initial_val) : Super(initial_val) {}
+};
+
+// Simple atomic bool class.
+class atomic_bool {
+ public:
+  typedef bool ValueType;
+
+  // C++11 forbids a copy constructor for std::atomic<T>, it also forbids
+  // a move operation.
+  atomic_bool() : value_(false) {}
+  explicit atomic_bool(bool v) : value_(v) {}
+
+  bool is_lock_free() const { return true; }
+  bool is_lock_free() const volatile { return true; }
+
+  bool exchange(bool new_val) {
+    return SbAtomicNoBarrier_Exchange(volatile_ptr(), new_val);
+  }
+
+  bool load() const { return SbAtomicAcquire_Load(volatile_const_ptr()) != 0; }
+
+  void store(bool val) { SbAtomicRelease_Store(volatile_ptr(), val); }
+
+ private:
+  volatile int32_t* volatile_ptr() { return &value_; }
+  volatile const int32_t* volatile_const_ptr() const { return &value_; }
+  int32_t value_;
+};
+
+// Lockfree atomic int class.
+class atomic_int32_t {
+ public:
+  typedef int32_t ValueType;
+  atomic_int32_t() : value_(0) {}
+  explicit atomic_int32_t(SbAtomic32 value) : value_(value) {}
+
+  bool is_lock_free() const { return true; }
+  bool is_lock_free() const volatile { return true; }
+
+  int32_t increment() { return fetch_add(1); }
+  int32_t decrement() { return fetch_add(-1); }
+
+  int32_t fetch_add(int32_t val) {
+    // fetch_add is a post-increment operation, while SbAtomicBarrier_Increment
+    // is a pre-increment operation. Therefore subtract the value to match
+    // the expected interface.
+    return SbAtomicBarrier_Increment(volatile_ptr(), val) - val;
+  }
+
+  int32_t fetch_sub(int32_t val) { return fetch_add(-val); }
+
+  // Atomically replaces the value of the atomic object
+  // and returns the value held previously.
+  // See also std::atomic<T>::exchange().
+  int32_t exchange(int32_t new_val) {
+    return SbAtomicNoBarrier_Exchange(volatile_ptr(), new_val);
+  }
+
+  // Atomically obtains the value of the atomic object.
+  // See also std::atomic<T>::load().
+  int32_t load() const { return SbAtomicAcquire_Load(volatile_const_ptr()); }
+
+  // Stores the value. See std::atomic<T>::store(...)
+  void store(int32_t val) { SbAtomicRelease_Store(volatile_ptr(), val); }
+
+  bool compare_exchange_strong(int32_t* expected_value, int32_t new_value) {
+    int32_t prev_value = *expected_value;
+    SbAtomicMemoryBarrier();
+    int32_t value_written =
+        SbAtomicRelease_CompareAndSwap(volatile_ptr(), prev_value, new_value);
+    const bool write_ok = (prev_value == value_written);
+    if (!write_ok) {
+      *expected_value = value_written;
+    }
+    return write_ok;
+  }
+
+  // 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(int32_t* expected_value, int32_t new_value) {
+    return compare_exchange_strong(expected_value, new_value);
+  }
+
+ private:
+  volatile int32_t* volatile_ptr() { return &value_; }
+  volatile const int32_t* volatile_const_ptr() const { return &value_; }
+  int32_t value_;
+};
+
+// 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 starboard
+
+#endif  // __cplusplus
+
 #endif  // STARBOARD_ATOMIC_H_
diff --git a/src/starboard/build/default_no_deploy.gypi b/src/starboard/build/default_no_deploy.gypi
new file mode 100644
index 0000000..da58d6b
--- /dev/null
+++ b/src/starboard/build/default_no_deploy.gypi
@@ -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.
+
+# This file is meant to be included into a target to provide a rule
+# to deploy a target on a target platform.
+#
+# The platform_deploy target should be defined in
+# starboard/<port_path>/platform_deploy.gyp. This target should perform
+# any per-executable logic that is specific to the platform. For example,
+# copying per-executable metadata files to the output directory.
+
+{
+  # This space intentionally left blank.
+}
diff --git a/src/starboard/build/deploy.gypi b/src/starboard/build/deploy.gypi
index ce7c9ce..048591b 100644
--- a/src/starboard/build/deploy.gypi
+++ b/src/starboard/build/deploy.gypi
@@ -22,33 +22,48 @@
 #
 # To use this, create a gyp target with the following form:
 # 'targets': [
-#   {
-#     'target_name': 'target_deploy',
-#     'type': 'none',
-#     'dependencies': [
-#       'target',
-#     ],
-#     'variables': {
-#       'executable_name': 'target',
-#     },
-#     'includes': [
-#       '../build/deploy.gypi',
-#     ],
-#   },
+#    {
+#      'target_name': 'target_deploy',
+#      'type': 'none',
+#      'dependencies': [
+#        'target',
+#      ],
+#      'variables': {
+#        'executable_name': 'target',
+#      },
+#      'includes': [ '../build/deploy.gypi' ],
+#    },
+#    ...
 #
+# For example:
+#  'targets': [
+#    {
+#      'target_name': 'nplb',
+#      'type': '<(gtest_target_type)',
+#      'sources': [...]
+#    }
+#    {
+#      'target_name': 'nplb_deploy',
+#      'type': 'none',
+#      'dependencies': [
+#        'nplb',
+#      ],
+#      'variables': {
+#        'executable_name': 'nplb',
+#      },
+#      'includes': [ '../build/deploy.gypi' ],
+#    },
 
 {
+
   # Flag that will instruct gyp to create a special target in IDEs such as
   # Visual Studio that can be used for launching a target.
   'variables' : {
     'ide_deploy_target': 1,
   },
 
-  'conditions': [
-    ['OS=="starboard" and sb_has_deploy_step==1', {
-      'dependencies': [
-        '<(DEPTH)/<(starboard_path)/platform_deploy.gyp:platform_deploy',
-      ],
-    }],
-  ],
+  # Include the platform specific gypi file include. Note that the
+  # expanded value will default to
+  # "starboard/build/default_no_deploy.gypi"
+  'includes': [ '<(DEPTH)/<(include_path_platform_deploy_gypi)' ],
 }
diff --git a/src/starboard/common/flat_map.h b/src/starboard/common/flat_map.h
index a232b24..6d3a3a9 100644
--- a/src/starboard/common/flat_map.h
+++ b/src/starboard/common/flat_map.h
@@ -197,6 +197,8 @@
     }
   }
 
+  void erase(iterator it) { vector_.erase(it); }
+
   void erase(iterator begin_it, iterator end_it) {
     vector_.erase(begin_it, end_it);
   }
diff --git a/src/starboard/common/locked_ptr.h b/src/starboard/common/locked_ptr.h
index 6f308a0..d1821c2 100644
--- a/src/starboard/common/locked_ptr.h
+++ b/src/starboard/common/locked_ptr.h
@@ -37,16 +37,16 @@
       }
     }
 
-    ImplType* operator->() { return ptr_; }
+    ImplType* operator->() {
+      SB_DCHECK(ptr_);
+      return ptr_;
+    }
 
    private:
     friend class LockedPtr<Type>;
 
     Impl(Mutex* mutex, ImplType* ptr) : mutex_(mutex), ptr_(ptr) {
       SB_DCHECK(mutex);
-      SB_DCHECK(ptr);
-
-      mutex_->Acquire();
     }
 
     // The copy ctor transfers the ownership.  So only the last one holds the
@@ -78,17 +78,24 @@
     ptr_ = ptr.Pass();
   }
 
+  void reset() {
+    starboard::ScopedLock scoped_lock(mutex_);
+    ptr_.reset();
+  }
+
   bool is_valid() const {
     starboard::ScopedLock scoped_lock(mutex_);
     return ptr_ != NULL;
   }
 
   Impl<Type> operator->() {
+    mutex_.Acquire();
     Impl<Type> impl(&mutex_, ptr_.get());
     return impl;
   }
 
   Impl<const Type> operator->() const {
+    mutex_.Acquire();
     Impl<const Type> impl(&mutex_, ptr_.get());
     return impl;
   }
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index dcfe06f..00dc565 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -39,19 +39,18 @@
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MAXIMUM_API_VERSION 6
+#define SB_MAXIMUM_API_VERSION 7
 
 // 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 6
+#define SB_EXPERIMENTAL_API_VERSION 7
 
 // The next API version to be frozen, but is still subject to emergency
 // changes. It is reasonable to base a port on the Release Candidate API
 // version, but be aware that small incompatible changes may still be made to
 // it.
-// #undef SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_RELEASE_CANDIDATE_API_VERSION 5
+#define SB_RELEASE_CANDIDATE_API_VERSION 6
 
 // --- Experimental Feature Defines ------------------------------------------
 
@@ -65,29 +64,18 @@
 //   //   exposes functionality for my new feature.
 //   #define SB_MY_EXPERIMENTAL_FEATURE_VERSION SB_EXPERIMENTAL_API_VERSION
 
-// Introduce pointer (mouse) input support. This extends the SbInput interface
-// with some enum values and data members to allow mouse, wheel, and more
-// generic pointer input.
-#define SB_POINTER_INPUT_API_VERSION SB_EXPERIMENTAL_API_VERSION
-
-// SbMediaAudioHeader::audio_specific_config will be a pointer instead of an
-// array.
-#define SB_AUDIO_SPECIFIC_CONFIG_AS_POINTER SB_EXPERIMENTAL_API_VERSION
-
-// Removes SbTimeZoneGetDstName() - Daylight saving time version of time zone.
-// Changes SbTimeZoneGetName() is more flexible now in what it is allowed to
-// return.
-#define SB_TIME_ZONE_FLEXIBLE_API_VERSION SB_EXPERIMENTAL_API_VERSION
-
-// Adds the convenience inline function, SbDecodeTargetNumberOfPlanesForFormat()
-// to starboard/decode_target.h.
-#define SB_DECODE_TARGET_PLANES_FOR_FORMAT SB_EXPERIMENTAL_API_VERSION
-
 // --- Release Candidate Feature Defines -------------------------------------
 
-#define SB_USER_AGENT_AUX_SYSTEM_PROPERTY_API_VERSION \
+#define SB_POINTER_INPUT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_AUDIO_SPECIFIC_CONFIG_AS_POINTER SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_TIME_ZONE_FLEXIBLE_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_DECODE_TARGET_PLANES_FOR_FORMAT SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_PRELOAD_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_PLATFORM_ERROR_CLEANUP_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION \
   SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_SPEECH_RECOGNIZER_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_COLOR_KEYCODES_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_LOW_MEMORY_EVENT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
 
 // --- Common Detected Features ----------------------------------------------
 
@@ -117,6 +105,7 @@
 
 // Determines at compile-time if this platform implements a given Starboard API
 // version number (or above).
+// This macro is deprecated, please instead use the expanded form directly.
 #define SB_VERSION(SB_API) (SB_API_VERSION >= SB_API)
 
 // A constant expression that evaluates to the size_t size of a statically-sized
@@ -450,11 +439,11 @@
 #error "Your platform must define SB_MAX_THREAD_NAME_LENGTH."
 #endif
 
-#if SB_VERSION(2) && !defined(SB_HAS_MICROPHONE)
+#if SB_API_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)
+#if SB_API_VERSION >= 3 && !defined(SB_HAS_TIME_THREAD_NOW)
 #error "Your platform must define SB_HAS_TIME_THREAD_NOW in API 3 or later."
 #endif
 
@@ -574,11 +563,11 @@
 
 #endif  // SB_API_VERSION >= 4
 
-#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_API_VERSION >= 5
 #if !defined(SB_HAS_SPEECH_RECOGNIZER)
 #error "Your platform must define SB_HAS_SPEECH_RECOGNIZER."
 #endif  // !defined(SB_HAS_SPEECH_RECOGNIZER)
-#endif  // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_API_VERSION >= 5
 
 // --- Derived Configuration -------------------------------------------------
 
diff --git a/src/starboard/creator/shared/gyp_configuration.gypi b/src/starboard/creator/shared/gyp_configuration.gypi
index bdf65af..2d7e40d 100644
--- a/src/starboard/creator/shared/gyp_configuration.gypi
+++ b/src/starboard/creator/shared/gyp_configuration.gypi
@@ -133,7 +133,7 @@
       '-Wno-literal-suffix',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h
index afe2e46..e109b6a 100644
--- a/src/starboard/decode_target.h
+++ b/src/starboard/decode_target.h
@@ -142,6 +142,19 @@
   // A decoder target format consisting of Y, U, and V planes, in that order.
   kSbDecodeTargetFormat3PlaneYUVI420,
 
+#if SB_API_VERSION >= SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION
+  // A decoder target format consisting of a single plane with pixels layed out
+  // in the format UYVY.  Since there are two Y values per sample, but only one
+  // U value and only one V value, horizontally the Y resolution is twice the
+  // size of both the U and V resolutions.  Vertically, they Y, U and V all
+  // have the same resolution.  This is a YUV 422 format.  When using this
+  // format with GL platforms, it is expected that the underlying texture will
+  // be set to the GL_RGBA format, and the width of the texture will be equal to
+  // the number of UYVY tuples per row (e.g. the u/v width resolution).
+  // Content region left/right should be specified in u/v width resolution.
+  kSbDecodeTargetFormat1PlaneUYVY,
+#endif  // SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION
+
   // An invalid decode target format.
   kSbDecodeTargetFormatInvalid,
 } SbDecodeTargetFormat;
@@ -344,6 +357,9 @@
     SbDecodeTargetFormat format) {
   switch (format) {
     case kSbDecodeTargetFormat1PlaneRGBA:
+#if SB_API_VERSION >= SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION
+    case kSbDecodeTargetFormat1PlaneUYVY:
+#endif  // SB_API_VERSION >= SB_DECODE_TARGET_UYVY_SUPPORT_API_VERSION
       return 1;
     case kSbDecodeTargetFormat1PlaneBGRA:
       return 1;
diff --git a/src/starboard/doc/howto_decode_to_texture.md b/src/starboard/doc/howto_decode_to_texture.md
index 093deb4..3bb46bc 100644
--- a/src/starboard/doc/howto_decode_to_texture.md
+++ b/src/starboard/doc/howto_decode_to_texture.md
@@ -27,7 +27,6 @@
 
 From [`starboard/decode_target.h`](../decode_target.h),
 
-* `SbDecodeTargetCreate()`
 * `SbDecodeTargetRelease()`
 * `SbDecodeTargetGetInfo()`
 
diff --git a/src/starboard/event.h b/src/starboard/event.h
index 9ab88c7..e854b27 100644
--- a/src/starboard/event.h
+++ b/src/starboard/event.h
@@ -18,23 +18,48 @@
 //
 // ## The Starboard Application life cycle
 //
-// |          *
-// |          |                      _________________________
-// |        Start                   |                         |
-// |          |                     |                       Resume
-// |          V                     V                         |
-// |     [ STARTED ] --Pause--> [ PAUSED ] --Suspend--> [ SUSPENDED ]
-// |          ^                     |                         |
-// |          |                  Unpause                     Stop
-// |          |_____________________|                         |
-// |                                                          V
-// |                                                     [ STOPPED ]
+// |     ---------- *
+// |    |           |
+// |    |        Preload
+// |    |           |
+// |    |           V
+// |  Start   [ PRELOADING ] ------------
+// |    |           |                    |
+// |    |         Start                  |
+// |    |           |                    |
+// |    |           V                    |
+// |     ----> [ STARTED ] <----         |
+// |                |           |        |
+// |              Pause       Unpause    |
+// |                |           |     Suspend
+// |                V           |        |
+// |     -----> [ PAUSED ] -----         |
+// |    |           |                    |
+// | Resume      Suspend                 |
+// |    |           |                    |
+// |    |           V                    |
+// |     ---- [ SUSPENDED ] <------------
+// |                |
+// |               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.
+// The first event that a Starboard application receives is either |Start|
+// (kSbEventTypeStart) or |Preload| (kSbEventTypePreload). |Start| puts the
+// application application in the |STARTED| state, whereas |Preload| puts the
+// application in the |PRELOADING| state.
+//
+// |PRELOADING| can only happen as the first application state. In this state,
+// the application should start and run as normal, but will not receive any
+// input, and should not try to initialize graphics resources (via GL or
+// SbBlitter). In |PRELOADING|, the application can receive |Start| or |Suspend|
+// events. |Start| will receive the same data that was passed into |Preload|.
+//
+// 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
@@ -70,11 +95,33 @@
 // 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.
 typedef enum SbEventType {
-  // The first event that an application receives on startup. Applications
-  // should perform initialization and prepare to react to subsequent events.
-  // Applications that wish to run and then exit must call SbSystemRequestStop()
-  // to terminate. This event will only be sent once for a given process launch.
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  // Applications should perform initialization and prepare to react to
+  // subsequent events, but must not initialize any graphics resources (through
+  // GL or SbBlitter). The intent of this event is to allow the application to
+  // do as much work as possible ahead of time, so that when the application is
+  // first brought to the foreground, it's as fast as a resume.
+  //
+  // The |kSbEventTypeStart| event may be sent at any time, regardless of
+  // initialization state. Input events will not be sent in the |PRELOADING|
+  // state. This event will only be sent once for a given process launch.
   // SbEventStartData is passed as the data argument.
+  //
+  // The system may send |kSbEventTypeSuspend| in |PRELOADING| if it wants to
+  // push the app into a lower resource consumption state. Applications can alo
+  // call SbSystemRequestSuspend() when they are done preloading to request
+  // this.
+  kSbEventTypePreload,
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+
+  // The first event that an application receives on startup when starting
+  // normally (i.e. not being preloaded). Applications should perform
+  // initialization, start running, and prepare to react to subsequent
+  // events. Applications that wish to run and then exit must call
+  // |SbSystemRequestStop()| to terminate. This event will only be sent once for
+  // a given process launch. |SbEventStartData| is passed as the data
+  // argument. In case of preload, the |SbEventStartData| will be the same as
+  // what was passed to |kSbEventTypePreload|.
   kSbEventTypeStart,
 
   // A dialog will be raised or the application will otherwise be put into a
@@ -158,6 +205,15 @@
   // new settings.
   kSbEventTypeAccessiblitySettingsChanged,
 #endif
+
+#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
+  // An optional event that platforms may send to indicate that the application
+  // may soon be terminated (or crash) due to low memory availability. The
+  // application may respond by reducing memory consumption by running a Garbage
+  // Collection, flushing caches, or something similar. There is no requirement
+  // to respond to or handle this event, it is only advisory.
+  kSbEventTypeLowMemory,
+#endif  // SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
 } SbEventType;
 
 // Structure representing a Starboard event and its data.
diff --git a/src/starboard/examples/window/main.cc b/src/starboard/examples/window/main.cc
index fb47cdb..c944564 100644
--- a/src/starboard/examples/window/main.cc
+++ b/src/starboard/examples/window/main.cc
@@ -32,9 +32,18 @@
 
 void SbEventHandle(const SbEvent* event) {
   switch (event->type) {
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+    case kSbEventTypePreload: {
+      SB_LOG(INFO) << "PRELOAD";
+      SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
+      SB_DCHECK(data);
+      break;
+    }
+#endif
     case kSbEventTypeStart: {
       SB_LOG(INFO) << "START";
       SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
+      SB_DCHECK(data);
       g_window = SbWindowCreate(NULL);
       SB_CHECK(SbWindowIsValid(g_window));
 
diff --git a/src/starboard/image.h b/src/starboard/image.h
index 82d93be..721067c 100644
--- a/src/starboard/image.h
+++ b/src/starboard/image.h
@@ -52,7 +52,7 @@
 #include "starboard/export.h"
 #include "starboard/types.h"
 
-#if SB_VERSION(3)
+#if SB_API_VERSION >= 3
 
 #ifdef __cplusplus
 extern "C" {
@@ -102,6 +102,6 @@
 }  // extern "C"
 #endif
 
-#endif  // SB_VERSION(3)
+#endif  // SB_API_VERSION >= 3
 
 #endif  // STARBOARD_IMAGE_H_
diff --git a/src/starboard/key.h b/src/starboard/key.h
index 8191acd..e44b05f 100644
--- a/src/starboard/key.h
+++ b/src/starboard/key.h
@@ -202,12 +202,20 @@
   kSbKeyPlay = 0xFA,
 
   // Beyond this point are non-Windows key codes that are provided as
-  // extensions, as they otherwise have no analogs
+  // extensions, as they otherwise have no analogs.
 
   // Some media keys that are used around the industry.
   kSbKeyMediaRewind = 0xE3,
   kSbKeyMediaFastForward = 0xE4,
 
+#if SB_API_VESRION >= SB_COLOR_KEYCODES_API_VERSION
+  // The colored keys found on most contemporary TV remotes.
+  kSbKeyRed = 0x193,
+  kSbKeyGreen = 0x194,
+  kSbKeyYellow = 0x195,
+  kSbKeyBlue = 0x196,
+#endif  // SB_API_VESRION >= SB_COLOR_KEYCODES_API_VERSION
+
   // Mouse buttons, starting with the left mouse button.
   kSbKeyMouse1 = 0x7000,
   kSbKeyMouse2 = 0x7001,
diff --git a/src/starboard/linux/shared/compiler_flags.gypi b/src/starboard/linux/shared/compiler_flags.gypi
index 762dfa7..2223b30 100644
--- a/src/starboard/linux/shared/compiler_flags.gypi
+++ b/src/starboard/linux/shared/compiler_flags.gypi
@@ -108,7 +108,7 @@
       '-std=gnu++11',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index b7c9e2f..6688b18 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -24,7 +24,7 @@
 #define STARBOARD_LINUX_SHARED_CONFIGURATION_PUBLIC_H_
 
 #ifndef SB_API_VERSION
-#define SB_API_VERSION 4
+#define SB_API_VERSION 6
 #endif
 
 // --- System Header Configuration -------------------------------------------
diff --git a/src/starboard/linux/shared/decode_target_get_info.cc b/src/starboard/linux/shared/decode_target_get_info.cc
new file mode 100644
index 0000000..c63f587
--- /dev/null
+++ b/src/starboard/linux/shared/decode_target_get_info.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/linux/shared/decode_target_internal.h"
+#include "starboard/memory.h"
+
+// TODO: Consider unifying this with that of raspi/open_max and android, since
+// the only part that changes is the info struct size.
+bool SbDecodeTargetGetInfo(SbDecodeTarget decode_target,
+                           SbDecodeTargetInfo* out_info) {
+  if (!SbMemoryIsZero(out_info, sizeof(*out_info))) {
+    SB_DCHECK(false) << "out_info must be zeroed out.";
+    return false;
+  }
+
+  SbMemoryCopy(out_info, &decode_target->data->info, sizeof(*out_info));
+
+  return true;
+}
diff --git a/src/starboard/linux/shared/decode_target_internal.cc b/src/starboard/linux/shared/decode_target_internal.cc
new file mode 100644
index 0000000..1b2613b
--- /dev/null
+++ b/src/starboard/linux/shared/decode_target_internal.cc
@@ -0,0 +1,157 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/linux/shared/decode_target_internal.h"
+
+#if SB_HAS(GLES2)
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "starboard/shared/gles/gl_call.h"
+#endif
+
+#include "starboard/decode_target.h"
+
+SbDecodeTargetPrivate::Data::~Data() {
+#if SB_HAS(GLES2)
+  glDeleteTextures(1, &info.planes[0].texture);
+  SB_DCHECK(glGetError() == GL_NO_ERROR);
+#endif
+}
+
+namespace starboard {
+namespace shared {
+
+namespace {
+
+struct CreateParams {
+  SbDecodeTarget decode_target_out;
+  scoped_refptr<starboard::player::VideoFrame> frame;
+};
+
+#if SB_HAS(GLES2)
+void CreateWithContextRunner(void* context) {
+  CreateParams* params = static_cast<CreateParams*>(context);
+
+  SB_DCHECK(params->frame);
+  SB_DCHECK(params->frame->format() == starboard::player::VideoFrame::kYV12);
+  static const SbDecodeTargetFormat format = kSbDecodeTargetFormat3PlaneYUVI420;
+  static const int plane_count = 3;
+
+  if (!SbDecodeTargetIsValid(params->decode_target_out)) {
+    params->decode_target_out = new SbDecodeTargetPrivate;
+    params->decode_target_out->data = new SbDecodeTargetPrivate::Data;
+    params->decode_target_out->data->info.format = format;
+
+    GLuint textures[3];
+    GL_CALL(glGenTextures(plane_count, textures));
+    for (int plane_index = 0; plane_index < plane_count; plane_index++) {
+      params->decode_target_out->data->info.is_opaque = true;
+
+      SbDecodeTargetInfoPlane& plane =
+          params->decode_target_out->data->info.planes[plane_index];
+      plane.texture = textures[plane_index];
+      plane.gl_texture_target = GL_TEXTURE_2D;
+      plane.content_region.left = 0.0f;
+      plane.content_region.top = 0.0f;
+      plane.width = 0;
+      plane.height = 0;
+    }
+  }
+
+  SbDecodeTargetInfo& target_info = params->decode_target_out->data->info;
+
+  for (int plane_index = 0; plane_index < plane_count; plane_index++) {
+    const starboard::player::VideoFrame::Plane& video_frame_plane =
+        params->frame->GetPlane(plane_index);
+
+    GL_CALL(glBindTexture(
+        GL_TEXTURE_2D,
+        params->decode_target_out->data->info.planes[plane_index].texture));
+    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT,
+                          video_frame_plane.pitch_in_bytes));
+    if (target_info.planes[plane_index].width == video_frame_plane.width &&
+        target_info.planes[plane_index].height == video_frame_plane.height) {
+      // No need to reallocate texture object, only update pixels.
+      GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_frame_plane.width,
+                              video_frame_plane.height, GL_ALPHA,
+                              GL_UNSIGNED_BYTE, video_frame_plane.data));
+
+    } else {
+      // As part of texture initialization, explicitly specify all parameters.
+      GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+      GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+      GL_CALL(
+          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+      GL_CALL(
+          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+
+      GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, video_frame_plane.width,
+                           video_frame_plane.height, 0, GL_ALPHA,
+                           GL_UNSIGNED_BYTE, video_frame_plane.data));
+    }
+
+    GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0));
+    GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
+
+    // Set sizes and regions.
+    target_info.planes[plane_index].content_region.right =
+        static_cast<float>(video_frame_plane.width);
+    target_info.planes[plane_index].content_region.bottom =
+        static_cast<float>(video_frame_plane.height);
+    target_info.planes[plane_index].width = video_frame_plane.width;
+    target_info.planes[plane_index].height = video_frame_plane.height;
+  }
+
+  target_info.width = params->frame->width();
+  target_info.height = params->frame->height();
+}
+#endif
+
+}  // namespace
+
+SbDecodeTarget DecodeTargetCreate(
+    SbDecodeTargetGraphicsContextProvider* provider,
+    scoped_refptr<starboard::player::VideoFrame> frame,
+    SbDecodeTarget decode_target) {
+  CreateParams params;
+  params.decode_target_out = decode_target;
+  params.frame = frame;
+
+#if SB_HAS(GLES2)
+  SbDecodeTargetRunInGlesContext(provider, &CreateWithContextRunner, &params);
+#endif
+
+  return params.decode_target_out;
+}
+
+void DecodeTargetRelease(SbDecodeTargetGraphicsContextProvider*
+                             decode_target_graphics_context_provider,
+                         SbDecodeTarget decode_target) {
+#if SB_HAS(GLES2)
+  SbDecodeTargetReleaseInGlesContext(decode_target_graphics_context_provider,
+                                     decode_target);
+#endif
+}
+
+SbDecodeTarget DecodeTargetCopy(SbDecodeTarget decode_target) {
+  SbDecodeTarget out_decode_target = new SbDecodeTargetPrivate;
+  out_decode_target->data = decode_target->data;
+
+  return out_decode_target;
+}
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/linux/shared/decode_target_internal.h b/src/starboard/linux/shared/decode_target_internal.h
new file mode 100644
index 0000000..2b087a3
--- /dev/null
+++ b/src/starboard/linux/shared/decode_target_internal.h
@@ -0,0 +1,56 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_SHARED_DECODE_TARGET_INTERNAL_H_
+#define STARBOARD_LINUX_SHARED_DECODE_TARGET_INTERNAL_H_
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/decode_target.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+
+struct SbDecodeTargetPrivate {
+  class Data : public starboard::RefCounted<Data> {
+   public:
+    Data() {}
+
+    SbDecodeTargetInfo info;
+
+   private:
+    friend class starboard::RefCounted<Data>;
+    ~Data();
+  };
+
+  starboard::scoped_refptr<Data> data;
+};
+
+namespace starboard {
+namespace shared {
+
+// Outputs a video frame into a SbDecodeTarget.
+SbDecodeTarget DecodeTargetCreate(
+    SbDecodeTargetGraphicsContextProvider* provider,
+    scoped_refptr<starboard::player::VideoFrame> frame,
+    // Possibly valid structure to reuse, instead of allocating a new object.
+    SbDecodeTarget decode_target);
+
+void DecodeTargetRelease(SbDecodeTargetGraphicsContextProvider*
+                             decode_target_graphics_context_provider,
+                         SbDecodeTarget decode_target);
+
+SbDecodeTarget DecodeTargetCopy(SbDecodeTarget decode_target);
+
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_LINUX_SHARED_DECODE_TARGET_INTERNAL_H_
diff --git a/src/starboard/linux/shared/decode_target_release.cc b/src/starboard/linux/shared/decode_target_release.cc
new file mode 100644
index 0000000..52c6f74
--- /dev/null
+++ b/src/starboard/linux/shared/decode_target_release.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/linux/shared/decode_target_internal.h"
+
+void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
+  // Most of the actual data within |decode_target| is stored in the reference
+  // counted decode_target->data, so deleting |decode_target| here may not
+  // actually release any resources, if there are other references to
+  // decode_target->data.
+  delete decode_target;
+}
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index 9ac2033..b20fee6 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -16,6 +16,10 @@
     'starboard_platform_sources': [
       '<(DEPTH)/starboard/linux/shared/atomic_public.h',
       '<(DEPTH)/starboard/linux/shared/configuration_public.h',
+      '<(DEPTH)/starboard/linux/shared/decode_target_get_info.cc',
+      '<(DEPTH)/starboard/linux/shared/decode_target_internal.cc',
+      '<(DEPTH)/starboard/linux/shared/decode_target_internal.h',
+      '<(DEPTH)/starboard/linux/shared/decode_target_release.cc',
       '<(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',
@@ -32,6 +36,8 @@
       '<(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_audio_resampler.cc',
+      '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h',
       '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
       '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
       '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc',
@@ -227,9 +233,14 @@
       '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.cc',
       '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.h',
       '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_frame_tracker.h',
       '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc',
       '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h',
       '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.h',
       '<(DEPTH)/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc',
       '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
       '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h',
@@ -238,6 +249,8 @@
       '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc',
       '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h',
       '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_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/job_queue.cc',
@@ -280,8 +293,6 @@
       '<(DEPTH)/starboard/shared/stub/cryptography_set_authenticated_data.cc',
       '<(DEPTH)/starboard/shared/stub/cryptography_set_initialization_vector.cc',
       '<(DEPTH)/starboard/shared/stub/cryptography_transform.cc',
-      '<(DEPTH)/starboard/shared/stub/decode_target_get_info.cc',
-      '<(DEPTH)/starboard/shared/stub/decode_target_release.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',
diff --git a/src/starboard/linux/x64x11/clang/3.3/compiler_flags.gypi b/src/starboard/linux/x64x11/clang/3.3/compiler_flags.gypi
index a6629b0..22d5331 100644
--- a/src/starboard/linux/x64x11/clang/3.3/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/clang/3.3/compiler_flags.gypi
@@ -109,7 +109,7 @@
       '-std=gnu++98',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi b/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
index 5cde2f7..d700cb9 100644
--- a/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
@@ -126,7 +126,7 @@
       '-std=gnu++11',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/cpp11/compiler_flags.gypi b/src/starboard/linux/x64x11/cpp11/compiler_flags.gypi
index a59e9c6..56dae63 100644
--- a/src/starboard/linux/x64x11/cpp11/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/cpp11/compiler_flags.gypi
@@ -105,7 +105,7 @@
       '-Wno-inline-new-delete',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/directgles/gyp_configuration.gypi b/src/starboard/linux/x64x11/directgles/gyp_configuration.gypi
index 66e6d55..3cfea95 100644
--- a/src/starboard/linux/x64x11/directgles/gyp_configuration.gypi
+++ b/src/starboard/linux/x64x11/directgles/gyp_configuration.gypi
@@ -17,8 +17,8 @@
     # Use the direct-to-GLES rasterizer.
     # This rasterizer falls back to the hardware skia rasterizer in certain
     # situations.
-    # NOTE: This rasterizer requires a 16-bit depth buffer and a full frame
-    # scratch surface (without depth buffer).
+    # NOTE: This rasterizer allocates offscreen render targets upfront,
+    # including a full screen scratch surface.
     'rasterizer_type': 'direct-gles',
 
     # Accommodate the direct-to-GLES rasterizer's additional memory overhead
@@ -31,12 +31,17 @@
     # target atlases. One will be used as a cache and the other used as a
     # working scratch. It is recommended to allot enough memory for two
     # atlases that are roughly a quarter of the framebuffer. (The render
-    # target atlases use power-of-2 dimenions.)
+    # target atlases use power-of-2 dimenions.) If the target web app frequently
+    # uses effects which require offscreen targets, then more memory should be
+    # reserved for optimal performance.
     'surface_cache_size_in_bytes': 2 * (1024 * 512 * 4),
 
     # The rasterizer does not benefit much from rendering only the dirty
     # region. Disable this option since it costs GPU time.
     'render_dirty_region_only': 0,
+
+    # Map to mesh is not yet supported with the DirectGLES renderer.
+    'enable_map_to_mesh': 0,
   },
   'target_defaults': {
     'default_configuration': 'linux-x64x11-directgles_debug',
diff --git a/src/starboard/linux/x64x11/gcc/4.2/compiler_flags.gypi b/src/starboard/linux/x64x11/gcc/4.2/compiler_flags.gypi
index cbc3aeb..49a5317 100644
--- a/src/starboard/linux/x64x11/gcc/4.2/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/gcc/4.2/compiler_flags.gypi
@@ -100,7 +100,7 @@
       '-Wno-deprecated',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/gcc/4.4/compiler_flags.gypi b/src/starboard/linux/x64x11/gcc/4.4/compiler_flags.gypi
index 7af753c..7c5b9c9 100644
--- a/src/starboard/linux/x64x11/gcc/4.4/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/gcc/4.4/compiler_flags.gypi
@@ -96,7 +96,7 @@
       '-Wno-deprecated',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/gcc/6.3/compiler_flags.gypi b/src/starboard/linux/x64x11/gcc/6.3/compiler_flags.gypi
index e0c0bce..fd25c54 100644
--- a/src/starboard/linux/x64x11/gcc/6.3/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/gcc/6.3/compiler_flags.gypi
@@ -101,7 +101,7 @@
       '-Wno-deprecated',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/gyp_configuration.gypi b/src/starboard/linux/x64x11/gyp_configuration.gypi
index 948e2b7..0521206 100644
--- a/src/starboard/linux/x64x11/gyp_configuration.gypi
+++ b/src/starboard/linux/x64x11/gyp_configuration.gypi
@@ -18,6 +18,7 @@
     # there for acceptable values for this variable.
     'javascript_engine': 'mozjs-45',
     'cobalt_enable_jit': 0,
+    'enable_map_to_mesh%': 1,
   },
   'target_defaults': {
     'default_configuration': 'linux-x64x11_debug',
diff --git a/src/starboard/linux/x64x11/mock/gyp_configuration.gypi b/src/starboard/linux/x64x11/mock/gyp_configuration.gypi
index 24a0f14..659c47d 100644
--- a/src/starboard/linux/x64x11/mock/gyp_configuration.gypi
+++ b/src/starboard/linux/x64x11/mock/gyp_configuration.gypi
@@ -145,7 +145,7 @@
       },
     }, # end of configurations
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/linux/x64x11/starboard_platform_tests.gyp b/src/starboard/linux/x64x11/starboard_platform_tests.gyp
index 0250125..461529f 100644
--- a/src/starboard/linux/x64x11/starboard_platform_tests.gyp
+++ b/src/starboard/linux/x64x11/starboard_platform_tests.gyp
@@ -16,9 +16,18 @@
     {
       'target_name': 'starboard_platform_tests',
       'type': '<(gtest_target_type)',
+      'includes': [
+        '<(DEPTH)/starboard/shared/starboard/media/media_tests.gypi',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/filter_tests.gypi',
+      ],
       'sources': [
         '<(DEPTH)/starboard/common/test_main.cc',
-        '<(DEPTH)/starboard/shared/starboard/media/mime_type_test.cc',
+        '<@(filter_tests_sources)',
+        '<@(media_tests_sources)',
+      ],
+      'defines': [
+        # This allows the tests to include internal only header files.
+        'STARBOARD_IMPLEMENTATION',
       ],
       'dependencies': [
         '<(DEPTH)/starboard/starboard.gyp:starboard',
diff --git a/src/starboard/microphone.h b/src/starboard/microphone.h
index 52464c3..ae6a02f 100644
--- a/src/starboard/microphone.h
+++ b/src/starboard/microphone.h
@@ -43,7 +43,7 @@
 #include "starboard/export.h"
 #include "starboard/types.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #ifdef __cplusplus
 extern "C" {
@@ -194,6 +194,6 @@
 }  // extern "C"
 #endif
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #endif  // STARBOARD_MICROPHONE_H_
diff --git a/src/starboard/nplb/audio_sink_create_test.cc b/src/starboard/nplb/audio_sink_create_test.cc
index d4702cf..590ada8 100644
--- a/src/starboard/nplb/audio_sink_create_test.cc
+++ b/src/starboard/nplb/audio_sink_create_test.cc
@@ -28,7 +28,13 @@
                                 int* offset_in_frames,
                                 bool* is_playing,
                                 bool* is_eos_reached,
-                                void* context) {}
+                                void* context) {
+  *frames_in_buffer = 0;
+  *offset_in_frames = 0;
+  *is_playing = false;
+  *is_eos_reached = false;
+}
+
 void ConsumeFramesFuncStub(int frames_consumed, void* context) {}
 
 }  // namespace
diff --git a/src/starboard/nplb/flat_map_test.cc b/src/starboard/nplb/flat_map_test.cc
index fd1d5c1..b5817f4 100644
--- a/src/starboard/nplb/flat_map_test.cc
+++ b/src/starboard/nplb/flat_map_test.cc
@@ -89,7 +89,7 @@
 }
 
 SbTimeMonotonic GetThreadTimeMonotonicNow() {
-#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
   return SbTimeGetMonotonicThreadNow();
 #else
   return SbTimeGetMonotonicNow();
diff --git a/src/starboard/nplb/microphone_close_test.cc b/src/starboard/nplb/microphone_close_test.cc
index 662eb0a..b18508a 100644
--- a/src/starboard/nplb/microphone_close_test.cc
+++ b/src/starboard/nplb/microphone_close_test.cc
@@ -19,7 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneCloseTest, SunnyDayCloseAreCalledMultipleTimes) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -73,7 +73,7 @@
   EXPECT_FALSE(success);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/microphone_create_test.cc b/src/starboard/nplb/microphone_create_test.cc
index 9ce1042..4fbd015 100644
--- a/src/starboard/nplb/microphone_create_test.cc
+++ b/src/starboard/nplb/microphone_create_test.cc
@@ -19,7 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneCreateTest, SunnyDayOnlyOneMicrophone) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -177,7 +177,7 @@
   }
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/microphone_destroy_test.cc b/src/starboard/nplb/microphone_destroy_test.cc
index 707a3f1..8f7a69e 100644
--- a/src/starboard/nplb/microphone_destroy_test.cc
+++ b/src/starboard/nplb/microphone_destroy_test.cc
@@ -18,13 +18,13 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneDestroyTest, DestroyInvalidMicrophone) {
   SbMicrophoneDestroy(kSbMicrophoneInvalid);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_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
index 232995b..5324be0 100644
--- a/src/starboard/nplb/microphone_get_available_test.cc
+++ b/src/starboard/nplb/microphone_get_available_test.cc
@@ -19,7 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneGetAvailableTest, SunnyDay) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -53,7 +53,7 @@
   }
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/microphone_helpers.h b/src/starboard/nplb/microphone_helpers.h
index 06d4c37..b413f02 100644
--- a/src/starboard/nplb/microphone_helpers.h
+++ b/src/starboard/nplb/microphone_helpers.h
@@ -17,7 +17,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 namespace starboard {
 namespace nplb {
@@ -27,6 +27,6 @@
 }  // namespace nplb
 }  // namespace starboard
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_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
index 1016b8d..7b86992 100644
--- a/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
+++ b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
@@ -19,7 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneIsSampleRateSupportedTest, SunnyDay) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -49,7 +49,7 @@
                                                  kNormallyUsedSampleRateInHz));
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/microphone_open_test.cc b/src/starboard/nplb/microphone_open_test.cc
index 2853457..273f081 100644
--- a/src/starboard/nplb/microphone_open_test.cc
+++ b/src/starboard/nplb/microphone_open_test.cc
@@ -19,7 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneOpenTest, SunnyDay) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -92,7 +92,7 @@
   EXPECT_FALSE(success);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/microphone_read_test.cc b/src/starboard/nplb/microphone_read_test.cc
index 7df0260..805f035 100644
--- a/src/starboard/nplb/microphone_read_test.cc
+++ b/src/starboard/nplb/microphone_read_test.cc
@@ -20,7 +20,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 TEST(SbMicrophoneReadTest, SunnyDay) {
   SbMicrophoneInfo info_array[kMaxNumberOfMicrophone];
@@ -235,7 +235,7 @@
   EXPECT_LT(read_bytes, 0);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 174b062..ef605f5 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -18,6 +18,7 @@
 {
   'targets': [
     {
+      'msvs_disabled_warnings': [4100, 4189, 4456],
       'target_name': 'nplb',
       'type': '<(gtest_target_type)',
       'sources': [
diff --git a/src/starboard/nplb/speech_recognizer_cancel_test.cc b/src/starboard/nplb/speech_recognizer_cancel_test.cc
index 471275e..883f2e0 100644
--- a/src/starboard/nplb/speech_recognizer_cancel_test.cc
+++ b/src/starboard/nplb/speech_recognizer_cancel_test.cc
@@ -20,8 +20,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, CancelTestSunnyDay) {
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
@@ -66,8 +65,7 @@
   SbSpeechRecognizerCancel(kSbSpeechRecognizerInvalid);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_create_test.cc b/src/starboard/nplb/speech_recognizer_create_test.cc
index ddbc93f..7abdd53 100644
--- a/src/starboard/nplb/speech_recognizer_create_test.cc
+++ b/src/starboard/nplb/speech_recognizer_create_test.cc
@@ -19,8 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, CreateTestSunnyDay) {
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
@@ -28,8 +27,7 @@
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_destroy_test.cc b/src/starboard/nplb/speech_recognizer_destroy_test.cc
index ffd14de..e240d5b 100644
--- a/src/starboard/nplb/speech_recognizer_destroy_test.cc
+++ b/src/starboard/nplb/speech_recognizer_destroy_test.cc
@@ -19,8 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, DestroyInvalidSpeechRecognizer) {
   SbSpeechRecognizerDestroy(kSbSpeechRecognizerInvalid);
@@ -34,8 +33,7 @@
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_helper.h b/src/starboard/nplb/speech_recognizer_helper.h
index 727a993..242053b 100644
--- a/src/starboard/nplb/speech_recognizer_helper.h
+++ b/src/starboard/nplb/speech_recognizer_helper.h
@@ -25,8 +25,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 class EventMock : public RefCounted<EventMock> {
  public:
@@ -94,8 +93,7 @@
   const scoped_refptr<EventMock> test_mock_;
 };
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_start_test.cc b/src/starboard/nplb/speech_recognizer_start_test.cc
index 65fb03d..4515379 100644
--- a/src/starboard/nplb/speech_recognizer_start_test.cc
+++ b/src/starboard/nplb/speech_recognizer_start_test.cc
@@ -19,8 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, StartTestSunnyDay) {
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
@@ -112,8 +111,7 @@
   EXPECT_FALSE(success);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_stop_test.cc b/src/starboard/nplb/speech_recognizer_stop_test.cc
index 0663a50..7aeb105 100644
--- a/src/starboard/nplb/speech_recognizer_stop_test.cc
+++ b/src/starboard/nplb/speech_recognizer_stop_test.cc
@@ -19,8 +19,7 @@
 namespace starboard {
 namespace nplb {
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 TEST_F(SpeechRecognizerTest, StopIsCalledMultipleTimes) {
   SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
@@ -38,8 +37,7 @@
   SbSpeechRecognizerDestroy(recognizer);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc
index d2e67c7..7dd636f 100644
--- a/src/starboard/nplb/speech_synthesis_basic_test.cc
+++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -15,11 +15,11 @@
 #include "starboard/speech_synthesis.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#if SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 
 TEST(SbSpeechSynthesisBasicTest, Basic) {
   SbSpeechSynthesisSpeak("Hello");
   SbSpeechSynthesisCancel();
 }
 
-#endif  // SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#endif  // SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
diff --git a/src/starboard/nplb/system_get_property_test.cc b/src/starboard/nplb/system_get_property_test.cc
index e27b2ce..cebf148 100644
--- a/src/starboard/nplb/system_get_property_test.cc
+++ b/src/starboard/nplb/system_get_property_test.cc
@@ -70,9 +70,9 @@
   BasicTest(kSbSystemPropertyChipsetModelNumber, false, true, __LINE__);
   BasicTest(kSbSystemPropertyFirmwareVersion, false, true, __LINE__);
   BasicTest(kSbSystemPropertyNetworkOperatorName, false, true, __LINE__);
-#if SB_VERSION(2)
+#if SB_API_VERSION >= 2
   BasicTest(kSbSystemPropertySpeechApiKey, false, true, __LINE__);
-#endif  // SB_VERSION(2)
+#endif  // SB_API_VERSION >= 2
 
   if (IsCEDevice(SbSystemGetDeviceType())) {
     BasicTest(kSbSystemPropertyBrandName, true, true, __LINE__);
diff --git a/src/starboard/raspi/2/directgles/gyp_configuration.gypi b/src/starboard/raspi/2/directgles/gyp_configuration.gypi
index fbac27e..98d9129 100644
--- a/src/starboard/raspi/2/directgles/gyp_configuration.gypi
+++ b/src/starboard/raspi/2/directgles/gyp_configuration.gypi
@@ -17,8 +17,8 @@
     # Use the direct-to-GLES rasterizer.
     # This rasterizer falls back to the hardware skia rasterizer in certain
     # situations.
-    # NOTE: This rasterizer requires a 16-bit depth buffer and a full frame
-    # scratch surface (without depth buffer).
+    # NOTE: This rasterizer allocates offscreen render targets upfront,
+    # including a full screen scratch surface.
     'rasterizer_type': 'direct-gles',
 
     # Accommodate the direct-to-GLES rasterizer's additional memory overhead
@@ -31,7 +31,9 @@
     # target atlases. One will be used as a cache and the other used as a
     # working scratch. It is recommended to allot enough memory for two
     # atlases that are roughly a quarter of the framebuffer. (The render
-    # target atlases use power-of-2 dimenions.)
+    # target atlases use power-of-2 dimenions.) If the target web app frequently
+    # uses effects which require offscreen targets, then more memory should be
+    # reserved for optimal performance.
     'surface_cache_size_in_bytes': 2 * (1024 * 512 * 4),
 
     # The rasterizer does not benefit much from rendering only the dirty
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h
index f89c9a5..dc0b878 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 4
+#define SB_API_VERSION 6
 
 // --- System Header Configuration -------------------------------------------
 
diff --git a/src/starboard/raspi/shared/gyp_configuration.gypi b/src/starboard/raspi/shared/gyp_configuration.gypi
index 96dca33..1e1f4cf 100644
--- a/src/starboard/raspi/shared/gyp_configuration.gypi
+++ b/src/starboard/raspi/shared/gyp_configuration.gypi
@@ -30,7 +30,7 @@
 
     # This should have a default value in cobalt/base.gypi. See the comment
     # there for acceptable values for this variable.
-    'javascript_engine': 'mozjs',
+    'javascript_engine': 'mozjs-45',
     'cobalt_enable_jit': 0,
     'cobalt_media_source_2016': 1,
 
@@ -161,7 +161,7 @@
       '-Wno-literal-suffix',
     ],
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/raspi/shared/player_components_impl.cc b/src/starboard/raspi/shared/player_components_impl.cc
index 5644922..4d5c678 100644
--- a/src/starboard/raspi/shared/player_components_impl.cc
+++ b/src/starboard/raspi/shared/player_components_impl.cc
@@ -43,8 +43,7 @@
       video_parameters.video_codec, video_parameters.job_queue);
 
   AudioRendererImpl* audio_renderer =
-      new AudioRendererImpl(audio_parameters.job_queue,
-                            scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
+      new AudioRendererImpl(scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
                             audio_parameters.audio_header);
 
   VideoRendererImpl* video_renderer = new VideoRendererImpl(
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index dd195e1..1170664 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -81,6 +81,8 @@
         '<(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_audio_resampler.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h',
         '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc',
         '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h',
         '<(DEPTH)/starboard/shared/gcc/atomic_gcc.h',
@@ -275,9 +277,14 @@
         '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_frame_tracker.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.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/player_components.h',
@@ -285,6 +292,8 @@
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_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/job_queue.cc',
diff --git a/src/starboard/shared/directfb/application_directfb.h b/src/starboard/shared/directfb/application_directfb.h
index 49feff7..65f96c2 100644
--- a/src/starboard/shared/directfb/application_directfb.h
+++ b/src/starboard/shared/directfb/application_directfb.h
@@ -44,6 +44,11 @@
   IDirectFB* GetDirectFB();
   SbWindow GetWindow();
 
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  bool IsStartImmediate() SB_OVERRIDE { return !HasPreloadSwitch(); }
+  bool IsPreloadImmediate() SB_OVERRIDE { return HasPreloadSwitch(); }
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+
  protected:
   // --- Application overrides ---
   void Initialize() SB_OVERRIDE;
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
index 8951eaf..16bc1c5 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -17,6 +17,7 @@
 #include "starboard/audio_sink.h"
 #include "starboard/log.h"
 #include "starboard/memory.h"
+#include "starboard/shared/starboard/media/media_util.h"
 
 namespace starboard {
 namespace shared {
@@ -31,39 +32,6 @@
   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);
-
-  av_opt_set_int(context, "in_channel_layout", channel_layout, 0);
-  av_opt_set_int(context, "out_channel_layout", channel_layout, 0);
-  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", target_sample_format, 0);
-  av_opt_set_int(context, "internal_sample_fmt", source_sample_format, 0);
-
-  int result = avresample_open(context);
-  SB_DCHECK(!result);
-
-  int samples_resampled =
-      avresample_convert(context, &output_buffer, 1024, samples_per_channel,
-                         input_buffer, 0, samples_per_channel);
-  SB_DCHECK(samples_resampled == samples_per_channel);
-
-  avresample_close(context);
-  av_free(context);
-}
-
 AVCodecID GetFfmpegCodecIdByMediaCodec(SbMediaAudioCodec audio_codec) {
   switch (audio_codec) {
     case kSbMediaAudioCodecAac:
@@ -80,7 +48,6 @@
 AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
                            const SbMediaAudioHeader& audio_header)
     : audio_codec_(audio_codec),
-      sample_type_(GetSupportedSampleType()),
       codec_context_(NULL),
       av_frame_(NULL),
       stream_ended_(false),
@@ -95,9 +62,22 @@
   TeardownCodec();
 }
 
-void AudioDecoder::Decode(const InputBuffer& input_buffer) {
+void AudioDecoder::Initialize(const Closure& output_cb) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(output_cb.is_valid());
+  SB_DCHECK(!output_cb_.is_valid());
+
+  output_cb_ = output_cb;
+}
+
+void AudioDecoder::Decode(const InputBuffer& input_buffer,
+                          const Closure& consumed_cb) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(output_cb_.is_valid());
   SB_CHECK(codec_context_ != NULL);
 
+  Schedule(consumed_cb);
+
   if (stream_ended_) {
     SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
     return;
@@ -127,20 +107,24 @@
 
   if (decoded_audio_size > 0) {
     scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+        codec_context_->channels, GetSampleType(), GetStorageType(),
         input_buffer.pts(),
         codec_context_->channels * av_frame_->nb_samples *
-            (sample_type_ == kSbMediaAudioSampleTypeInt16 ? 2 : 4));
-    if (codec_context_->sample_fmt == codec_context_->request_sample_fmt) {
+            starboard::media::GetBytesPerSample(GetSampleType()));
+    if (GetStorageType() == kSbMediaAudioFrameStorageTypeInterleaved) {
       SbMemoryCopy(decoded_audio->buffer(), *av_frame_->extended_data,
                    decoded_audio->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, decoded_audio->buffer());
+      SB_DCHECK(GetStorageType() == kSbMediaAudioFrameStorageTypePlanar);
+      const int per_channel_size_in_bytes =
+          decoded_audio->size() / decoded_audio->channels();
+      for (int i = 0; i < decoded_audio->channels(); ++i) {
+        SbMemoryCopy(decoded_audio->buffer() + per_channel_size_in_bytes * i,
+                     av_frame_->extended_data[i], per_channel_size_in_bytes);
+      }
     }
     decoded_audios_.push(decoded_audio);
+    Schedule(output_cb_);
   } else {
     // TODO: Consider fill it with silence.
     SB_LOG(ERROR) << "Decoded audio frame is empty.";
@@ -148,14 +132,23 @@
 }
 
 void AudioDecoder::WriteEndOfStream() {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(output_cb_.is_valid());
+
   // AAC has no dependent frames so we needn't flush the decoder.  Set the flag
   // to ensure that Decode() is not called when the stream is ended.
   stream_ended_ = true;
   // Put EOS into the queue.
   decoded_audios_.push(new DecodedAudio);
+
+  Schedule(output_cb_);
 }
 
 scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read() {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(output_cb_.is_valid());
+  SB_DCHECK(!decoded_audios_.empty());
+
   scoped_refptr<DecodedAudio> result;
   if (!decoded_audios_.empty()) {
     result = decoded_audios_.front();
@@ -165,17 +158,51 @@
 }
 
 void AudioDecoder::Reset() {
+  SB_DCHECK(BelongsToCurrentThread());
+
   stream_ended_ = false;
   while (!decoded_audios_.empty()) {
     decoded_audios_.pop();
   }
+
+  CancelPendingJobs();
 }
 
 SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
-  return sample_type_;
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16 ||
+      codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) {
+    return kSbMediaAudioSampleTypeInt16;
+  } else if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT ||
+             codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP) {
+    return kSbMediaAudioSampleTypeFloat32;
+  }
+
+  SB_NOTREACHED();
+
+  return kSbMediaAudioSampleTypeFloat32;
+}
+
+SbMediaAudioFrameStorageType AudioDecoder::GetStorageType() const {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16 ||
+      codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) {
+    return kSbMediaAudioFrameStorageTypeInterleaved;
+  }
+  if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P ||
+      codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP) {
+    return kSbMediaAudioFrameStorageTypePlanar;
+  }
+
+  SB_NOTREACHED();
+  return kSbMediaAudioFrameStorageTypeInterleaved;
 }
 
 int AudioDecoder::GetSamplesPerSecond() const {
+  SB_DCHECK(BelongsToCurrentThread());
+
   return audio_header_.samples_per_second;
 }
 
@@ -192,7 +219,7 @@
   codec_context_->codec_type = AVMEDIA_TYPE_AUDIO;
   codec_context_->codec_id = GetFfmpegCodecIdByMediaCodec(audio_codec_);
   // Request_sample_fmt is set by us, but sample_fmt is set by the decoder.
-  if (sample_type_ == kSbMediaAudioSampleTypeInt16) {
+  if (GetSupportedSampleType() == kSbMediaAudioSampleTypeInt16) {
     codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16;
   } else {
     codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT;
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
index 893d2a0..40bd9c8 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
@@ -20,31 +20,31 @@
 #include "starboard/media.h"
 #include "starboard/shared/ffmpeg/ffmpeg_common.h"
 #include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/closure.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
 
 namespace starboard {
 namespace shared {
 namespace ffmpeg {
 
-class AudioDecoder : public starboard::player::filter::AudioDecoder {
+class AudioDecoder : public starboard::player::filter::AudioDecoder,
+                     private starboard::player::JobQueue::JobOwner {
  public:
-  typedef starboard::player::DecodedAudio DecodedAudio;
-  typedef starboard::player::InputBuffer InputBuffer;
-
   AudioDecoder(SbMediaAudioCodec audio_codec,
                const SbMediaAudioHeader& audio_header);
   ~AudioDecoder() SB_OVERRIDE;
 
-  void Decode(const InputBuffer& input_buffer) SB_OVERRIDE;
+  void Initialize(const Closure& output_cb) SB_OVERRIDE;
+  void Decode(const InputBuffer& input_buffer,
+              const Closure& consumed_cb) SB_OVERRIDE;
   void WriteEndOfStream() SB_OVERRIDE;
   scoped_refptr<DecodedAudio> Read() SB_OVERRIDE;
   void Reset() SB_OVERRIDE;
   SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE;
+  SbMediaAudioFrameStorageType GetStorageType() const SB_OVERRIDE;
   int GetSamplesPerSecond() const SB_OVERRIDE;
-  bool CanAcceptMoreData() const SB_OVERRIDE {
-    return !stream_ended_ && decoded_audios_.size() <= kMaxDecodedAudiosSize;
-  }
 
   bool is_valid() const { return codec_context_ != NULL; }
 
@@ -54,8 +54,8 @@
 
   static const int kMaxDecodedAudiosSize = 64;
 
+  Closure output_cb_;
   SbMediaAudioCodec audio_codec_;
-  SbMediaAudioSampleType sample_type_;
   AVCodecContext* codec_context_;
   AVFrame* av_frame_;
 
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.cc
new file mode 100644
index 0000000..3892b04
--- /dev/null
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.cc
@@ -0,0 +1,226 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/ffmpeg/ffmpeg_audio_resampler.h"
+
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/shared/starboard/media/media_util.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+namespace {
+
+const int kMaxChannels = 8;
+const int kMaxCachedSamples = 65536;
+
+int GetChannelLayoutFromChannels(int channels) {
+  if (channels == 1) {
+    return AV_CH_LAYOUT_MONO;
+  }
+  if (channels == 2) {
+    return AV_CH_LAYOUT_STEREO;
+  }
+  if (channels == 6) {
+    return AV_CH_LAYOUT_5POINT1;
+  }
+  if (channels == 8) {
+    return AV_CH_LAYOUT_7POINT1;
+  }
+  SB_NOTREACHED() << "Unsupported channel count: " << channels;
+  return AV_CH_LAYOUT_STEREO;
+}
+
+int GetSampleFormatBySampleTypeAndStorageType(
+    SbMediaAudioSampleType sample_type,
+    SbMediaAudioFrameStorageType storage_type) {
+  if (sample_type == kSbMediaAudioSampleTypeInt16 &&
+      storage_type == kSbMediaAudioFrameStorageTypeInterleaved) {
+    return AV_SAMPLE_FMT_S16;
+  }
+  if (sample_type == kSbMediaAudioSampleTypeFloat32 &&
+      storage_type == kSbMediaAudioFrameStorageTypeInterleaved) {
+    return AV_SAMPLE_FMT_FLT;
+  }
+  if (sample_type == kSbMediaAudioSampleTypeInt16 &&
+      storage_type == kSbMediaAudioFrameStorageTypePlanar) {
+    return AV_SAMPLE_FMT_S16P;
+  }
+  if (sample_type == kSbMediaAudioSampleTypeFloat32 &&
+      storage_type == kSbMediaAudioFrameStorageTypePlanar) {
+    return AV_SAMPLE_FMT_FLTP;
+  }
+  SB_NOTREACHED() << "Unsupported sample type (" << sample_type << ") and "
+                  << " storage type (" << storage_type << ") combination";
+  return AV_SAMPLE_FMT_FLT;
+}
+
+}  // namespace
+
+AudioResampler::AudioResampler(
+    SbMediaAudioSampleType source_sample_type,
+    SbMediaAudioFrameStorageType source_storage_type,
+    int source_sample_rate,
+    SbMediaAudioSampleType destination_sample_type,
+    SbMediaAudioFrameStorageType destination_storage_type,
+    int destination_sample_rate,
+    int channels)
+    : source_sample_type_(source_sample_type),
+      source_storage_type_(source_storage_type),
+      destination_sample_type_(destination_sample_type),
+      destination_storage_type_(destination_storage_type),
+      destination_sample_rate_(destination_sample_rate),
+      channels_(channels),
+      start_pts_(-1),
+      samples_returned_(0),
+      eos_reached_(false) {
+  SB_DCHECK(channels_ <= kMaxChannels);
+
+  context_ = avresample_alloc_context();
+  SB_DCHECK(context_ != NULL);
+
+  av_opt_set_int(context_, "in_channel_layout",
+                 GetChannelLayoutFromChannels(channels_), 0);
+  av_opt_set_int(context_, "out_channel_layout",
+                 GetChannelLayoutFromChannels(channels_), 0);
+  av_opt_set_int(context_, "in_sample_rate", source_sample_rate, 0);
+  av_opt_set_int(context_, "out_sample_rate", destination_sample_rate, 0);
+  av_opt_set_int(context_, "in_sample_fmt",
+                 GetSampleFormatBySampleTypeAndStorageType(source_sample_type,
+                                                           source_storage_type),
+                 0);
+  av_opt_set_int(context_, "out_sample_fmt",
+                 GetSampleFormatBySampleTypeAndStorageType(
+                     destination_sample_type, destination_storage_type),
+                 0);
+  av_opt_set_int(context_, "internal_sample_fmt", AV_SAMPLE_FMT_S16P, 0);
+
+  int result = avresample_open(context_);
+  SB_DCHECK(!result);
+}
+
+AudioResampler::~AudioResampler() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  avresample_close(context_);
+  av_free(context_);
+}
+
+scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::Resample(
+    const scoped_refptr<DecodedAudio>& audio_data) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(audio_data);
+  SB_DCHECK(audio_data->sample_type() == source_sample_type_);
+  SB_DCHECK(audio_data->storage_type() == source_storage_type_);
+  SB_DCHECK(audio_data->channels() == channels_);
+  SB_DCHECK(!eos_reached_);
+
+  if (channels_ > kMaxChannels) {
+    return new DecodedAudio;
+  }
+
+  if (eos_reached_) {
+    return new DecodedAudio;
+  }
+
+  if (start_pts_ < 0) {
+    start_pts_ = audio_data->pts();
+  }
+
+  uint8_t* input_buffers[kMaxChannels] = {
+      const_cast<uint8_t*>(audio_data->buffer())};
+  if (source_storage_type_ == kSbMediaAudioFrameStorageTypePlanar) {
+    for (int i = 1; i < channels_; ++i) {
+      input_buffers[i] = const_cast<uint8_t*>(
+          audio_data->buffer() + audio_data->size() / channels_ * i);
+    }
+  }
+
+  int result = avresample_convert(context_, NULL, 0, 0, input_buffers, 0,
+                                  audio_data->frames());
+  SB_DCHECK(result == 0);
+
+  return RetrieveOutput();
+}
+
+scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::WriteEndOfStream() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(!eos_reached_);
+
+  eos_reached_ = true;
+  int result = avresample_convert(context_, NULL, 0, 0, NULL, 0, 0);
+  SB_DCHECK(result == 0);
+
+  return RetrieveOutput();
+}
+
+scoped_refptr<AudioResampler::DecodedAudio> AudioResampler::RetrieveOutput() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  int frames_in_buffer = avresample_available(context_);
+  SbMediaTime pts = start_pts_ + samples_returned_ * kSbMediaTimeSecond /
+                                     destination_sample_rate_;
+  int bytes_per_sample =
+      starboard::media::GetBytesPerSample(destination_sample_type_);
+  scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+      channels_, destination_sample_type_, destination_storage_type_, pts,
+      frames_in_buffer * bytes_per_sample * channels_);
+  samples_returned_ += frames_in_buffer;
+
+  if (frames_in_buffer > 0) {
+    uint8_t* output_buffers[kMaxChannels] = {
+        const_cast<uint8_t*>(decoded_audio->buffer())};
+    if (destination_storage_type_ == kSbMediaAudioFrameStorageTypePlanar) {
+      for (int i = 1; i < channels_; ++i) {
+        output_buffers[i] = const_cast<uint8_t*>(
+            decoded_audio->buffer() + decoded_audio->size() / channels_ * i);
+      }
+    }
+
+    int frames_read =
+        avresample_read(context_, output_buffers, frames_in_buffer);
+    SB_DCHECK(frames_read == frames_in_buffer);
+  }
+
+  return decoded_audio;
+}
+
+}  // namespace ffmpeg
+
+namespace starboard {
+namespace player {
+namespace filter {
+
+// static
+scoped_ptr<AudioResampler> AudioResampler::Create(
+    SbMediaAudioSampleType source_sample_type,
+    SbMediaAudioFrameStorageType source_storage_type,
+    int source_sample_rate,
+    SbMediaAudioSampleType destination_sample_type,
+    SbMediaAudioFrameStorageType destination_storage_type,
+    int destination_sample_rate,
+    int channels) {
+  return scoped_ptr<AudioResampler>(new ffmpeg::AudioResampler(
+      source_sample_type, source_storage_type, source_sample_rate,
+      destination_sample_type, destination_storage_type,
+      destination_sample_rate, channels));
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h b/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h
new file mode 100644
index 0000000..5ebb3e8
--- /dev/null
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h
@@ -0,0 +1,67 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FFMPEG_FFMPEG_AUDIO_RESAMPLER_H_
+#define STARBOARD_SHARED_FFMPEG_FFMPEG_AUDIO_RESAMPLER_H_
+
+#include <queue>
+
+#include "starboard/media.h"
+#include "starboard/shared/ffmpeg/ffmpeg_common.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_resampler.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+class AudioResampler : public starboard::player::filter::AudioResampler {
+ public:
+  AudioResampler(SbMediaAudioSampleType source_sample_type,
+                 SbMediaAudioFrameStorageType source_storage_type,
+                 int source_sample_rate,
+                 SbMediaAudioSampleType destination_sample_type,
+                 SbMediaAudioFrameStorageType destination_storage_type,
+                 int destination_sample_rate,
+                 int channels);
+  ~AudioResampler() SB_OVERRIDE;
+
+  scoped_refptr<DecodedAudio> Resample(
+      const scoped_refptr<DecodedAudio>& audio_data) SB_OVERRIDE;
+  scoped_refptr<DecodedAudio> WriteEndOfStream() SB_OVERRIDE;
+
+ private:
+  scoped_refptr<DecodedAudio> RetrieveOutput();
+
+  shared::starboard::ThreadChecker thread_checker_;
+
+  SbMediaAudioSampleType source_sample_type_;
+  SbMediaAudioFrameStorageType source_storage_type_;
+  SbMediaAudioSampleType destination_sample_type_;
+  SbMediaAudioFrameStorageType destination_storage_type_;
+  int destination_sample_rate_;  // Used for timestamp adjustment.
+  int channels_;
+  SbMediaTime start_pts_;
+  int samples_returned_;
+  bool eos_reached_;
+  AVAudioResampleContext* context_;
+};
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_FFMPEG_FFMPEG_AUDIO_RESAMPLER_H_
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 2d4e659..111d974 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -14,7 +14,10 @@
 
 #include "starboard/shared/ffmpeg/ffmpeg_video_decoder.h"
 
+#include "starboard/linux/shared/decode_target_internal.h"
+
 #include "starboard/memory.h"
+#include "starboard/thread.h"
 
 namespace starboard {
 namespace shared {
@@ -93,14 +96,21 @@
 
 }  // namespace
 
-VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec)
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
+                           SbPlayerOutputMode output_mode,
+                           SbDecodeTargetGraphicsContextProvider*
+                               decode_target_graphics_context_provider)
     : video_codec_(video_codec),
       host_(NULL),
       codec_context_(NULL),
       av_frame_(NULL),
       stream_ended_(false),
       error_occured_(false),
-      decoder_thread_(kSbThreadInvalid) {
+      decoder_thread_(kSbThreadInvalid),
+      output_mode_(output_mode),
+      decode_target_graphics_context_provider_(
+          decode_target_graphics_context_provider),
+      decode_target_(kSbDecodeTargetInvalid) {
   InitializeCodec();
 }
 
@@ -156,6 +166,11 @@
 
   decoder_thread_ = kSbThreadInvalid;
   stream_ended_ = false;
+
+  if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
+    TeardownCodec();
+    InitializeCodec();
+  }
 }
 
 // static
@@ -227,6 +242,27 @@
       codec_context_->reordered_opaque, av_frame_->data[0], av_frame_->data[1],
       av_frame_->data[2]);
   host_->OnDecoderStatusUpdate(kBufferFull, frame);
+
+  if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
+    return UpdateDecodeTarget(frame);
+  }
+
+  return true;
+}
+
+bool VideoDecoder::UpdateDecodeTarget(const scoped_refptr<VideoFrame>& frame) {
+  SbDecodeTarget decode_target = DecodeTargetCreate(
+      decode_target_graphics_context_provider_, frame, decode_target_);
+
+  // Lock only after the post to the renderer thread, to prevent deadlock.
+  ScopedLock lock(decode_target_mutex_);
+  decode_target_ = decode_target;
+
+  if (!SbDecodeTargetIsValid(decode_target)) {
+    SB_LOG(ERROR) << "Could not acquire a decode target from provider.";
+    return false;
+  }
+
   return true;
 }
 
@@ -289,6 +325,31 @@
     av_free(av_frame_);
     av_frame_ = NULL;
   }
+
+  if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
+    ScopedLock lock(decode_target_mutex_);
+    if (SbDecodeTargetIsValid(decode_target_)) {
+      DecodeTargetRelease(decode_target_graphics_context_provider_,
+                          decode_target_);
+      decode_target_ = kSbDecodeTargetInvalid;
+    }
+  }
+}
+
+// When in decode-to-texture mode, this returns the current decoded video frame.
+SbDecodeTarget VideoDecoder::GetCurrentDecodeTarget() {
+  SB_DCHECK(output_mode_ == kSbPlayerOutputModeDecodeToTexture);
+
+  // We must take a lock here since this function can be called from a
+  // separate thread.
+  ScopedLock lock(decode_target_mutex_);
+  if (SbDecodeTargetIsValid(decode_target_)) {
+    // Make a disposable copy, since the state is internally reused by this
+    // class (to avoid recreating GL objects).
+    return DecodeTargetCopy(decode_target_);
+  } else {
+    return kSbDecodeTargetInvalid;
+  }
 }
 
 }  // namespace ffmpeg
@@ -305,7 +366,12 @@
   SB_UNREFERENCED_PARAMETER(codec);
   SB_UNREFERENCED_PARAMETER(drm_system);
 
-  return output_mode == kSbPlayerOutputModePunchOut;
+  if (output_mode == kSbPlayerOutputModePunchOut ||
+      output_mode == kSbPlayerOutputModeDecodeToTexture) {
+    return true;
+  }
+
+  return false;
 }
 #endif  // SB_API_VERSION >= 4
 
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
index 03639ed..3daf19e 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
@@ -34,7 +34,10 @@
   typedef starboard::player::InputBuffer InputBuffer;
   typedef starboard::player::VideoFrame VideoFrame;
 
-  explicit VideoDecoder(SbMediaVideoCodec video_codec);
+  VideoDecoder(SbMediaVideoCodec video_codec,
+               SbPlayerOutputMode output_mode,
+               SbDecodeTargetGraphicsContextProvider*
+                   decode_target_graphics_context_provider);
   ~VideoDecoder() SB_OVERRIDE;
 
   void SetHost(Host* host) SB_OVERRIDE;
@@ -71,6 +74,9 @@
   bool DecodePacket(AVPacket* packet);
   void InitializeCodec();
   void TeardownCodec();
+  SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
+
+  bool UpdateDecodeTarget(const scoped_refptr<VideoFrame>& frame);
 
   // These variables will be initialized inside ctor or SetHost() and will not
   // be changed during the life time of this class.
@@ -89,6 +95,26 @@
 
   // Working thread to avoid lengthy decoding work block the player thread.
   SbThread decoder_thread_;
+
+  // Decode-to-texture related state.
+  SbPlayerOutputMode output_mode_;
+
+  SbDecodeTargetGraphicsContextProvider*
+      decode_target_graphics_context_provider_;
+
+  // If decode-to-texture is enabled, then we store the decode target texture
+  // inside of this |decode_target_| member.
+  SbDecodeTarget decode_target_;
+
+  // GetCurrentDecodeTarget() needs to be called from an arbitrary thread
+  // to obtain the current decode target (which ultimately ends up being a
+  // copy of |decode_target_|), we need to safe-guard access to |decode_target_|
+  // and we do so through this mutex.
+  Mutex decode_target_mutex_;
+  // Mutex frame_mutex_;
+
+  // int frame_last_rendered_pts_;
+  // scoped_refptr<VideoFrame> frame_;
 };
 
 }  // namespace ffmpeg
diff --git a/src/starboard/shared/msvc/uwp/toolchain.py b/src/starboard/shared/msvc/uwp/toolchain.py
new file mode 100644
index 0000000..e0d9d3d
--- /dev/null
+++ b/src/starboard/shared/msvc/uwp/toolchain.py
@@ -0,0 +1,931 @@
+# Copyright 2017 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""This module implements starboard's abstract toolchain for MSVC UWP."""
+
+from difflib import get_close_matches
+import os
+import re
+import subprocess
+import sys
+
+_COBALT_SRC = os.path.abspath(os.path.join(*([__file__] + 5 * [os.pardir])))
+sys.path.append(os.path.join(_COBALT_SRC, 'starboard', 'tools'))
+
+import gyp.MSVSVersion  # pylint: disable=g-import-not-at-top
+# This tool_chain is starboard/build/toolchain.py
+from starboard.tools.toolchain import CompilerSettings
+from starboard.tools.toolchain import PrecompiledHeader
+from starboard.tools.toolchain import Toolchain
+
+def _LanguageMatchesForPch(source_ext, pch_source_ext):
+  c_exts = ('.c',)
+  cc_exts = ('.cc', '.cxx', '.cpp')
+  return ((source_ext in c_exts and pch_source_ext in c_exts) or
+          (source_ext in cc_exts and pch_source_ext in cc_exts))
+
+
+class MSVCPrecompiledHeader(PrecompiledHeader):
+  """MSVC implementation of PrecompiledHeader interface."""
+
+  def __init__(self, **kwargs):
+    self.settings = kwargs['settings']
+    self.config = kwargs['config']
+    gyp_path_to_ninja = kwargs['gyp_path_to_ninja']
+    gyp_path_to_unique_output = kwargs['gyp_path_to_unique_output']
+    obj_ext = kwargs['obj_ext']
+    pch_source = self.settings.msvs_precompiled_source[self.config]
+    self.pch_source = gyp_path_to_ninja(pch_source)
+    filename, _ = os.path.splitext(pch_source)
+    self.output_obj = gyp_path_to_unique_output(filename + obj_ext).lower()
+
+  def GetFlagsModifications(self, input_flags, output, implicit, command,
+                            cflags_c, cflags_cc, expand_special):
+    """Get the modified cflags and implicit dependencies."""
+    if self.settings.UsesComponentExtensions(self.config):
+      # No-op if component extensions are used.
+      return [], output, []
+
+    if input_flags == self.pch_source:
+      pch_output = ['/Yc' + self._PchHeader()]
+      if command == 'cxx':
+        return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))],
+                self.output_obj, [])
+      elif command == 'cc':
+        return ([('cflags_c', map(expand_special, cflags_c + pch_output))],
+                self.output_obj, [])
+    return [], output, implicit
+
+  def GetInclude(self):
+    pass
+
+  def GetPchBuildCommands(self):
+    """Not used on Windows as there are no additional build steps required
+    (instead, existing steps are modified in GetFlagsModifications below)."""
+    return []
+
+  def _PchHeader(self):
+    """Get the header that will appear in an #include line for all source
+    files."""
+    return os.path.split(self.settings.msvs_precompiled_header[self.config])[1]
+
+  def GetObjDependencies(self, sources, output):
+    """Given a list of sources files and the corresponding object files,
+    returns a list of the pch files that should be depended upon. The
+    additional wrapping in the return value is for interface compatability
+    with make.py on Mac, and xcode_emulation.py."""
+    if not self._PchHeader():
+      return []
+    pch_ext = os.path.splitext(self.pch_source)[1]
+    for source in sources:
+      if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
+        return [(None, None, self.output_obj)]
+    return []
+
+
+vs_version = None
+
+
+def GetVSVersion(generator_flags):
+  global vs_version
+  if not vs_version:
+    vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
+        generator_flags.get('msvs_version', 'auto'))
+  return vs_version
+
+
+def _GenericRetrieve(root, default, path):
+  """Given a list of dictionary keys |path| and a tree of dicts |root|, find
+  value at path, or return |default| if any of the path doesn't exist."""
+  if not root:
+    return default
+  if not path:
+    return root
+  return _GenericRetrieve(root.get(path[0]), default, path[1:])
+
+
+def _AddPrefix(element, prefix):
+  """Add |prefix| to |element| or each subelement if element is iterable."""
+  if element is None:
+    return element
+  # Note, not Iterable because we don't want to handle strings like that.
+  if isinstance(element, list) or isinstance(element, tuple):
+    return [prefix + e for e in element]
+  else:
+    return prefix + element
+
+
+def _CallMap(map, element):
+  e = map(element)
+  if e is None:
+    raise Exception('Invalid element %s' % element)
+  return e
+
+
+def _DoRemapping(element, map):
+  """If |element| then remap it through |map|. If |element| is iterable then
+  each item will be remapped. Any elements not found will be removed."""
+  if map is not None and element is not None:
+    if not callable(map):
+      map = map.get  # Assume it's a dict, otherwise a callable to do the remap.
+    if isinstance(element, list) or isinstance(element, tuple):
+      element = [_CallMap(map, elem) for elem in element]
+    else:
+      element = _CallMap(map, element)
+
+  return element
+
+
+def _AppendOrReturn(append, element):
+  """If |append| is None, simply return |element|. If |append| is not None,
+  then add |element| to it, adding each item in |element| if it's a list or
+  tuple."""
+  if append is not None and element is not None:
+    if isinstance(element, list) or isinstance(element, tuple):
+      append.extend(element)
+    else:
+      append.append(element)
+  else:
+    return element
+
+
+class MsvsSettings(CompilerSettings):
+  """A class that understands the gyp 'msvs_...' values (especially the
+  msvs_settings field). They largely correpond to the VS2008 IDE DOM. This
+  class helps map those settings to command line options."""
+
+  def __init__(self, spec, generator_flags):
+    self.spec = spec
+    self.vs_version = GetVSVersion(generator_flags)
+    # Try to find an installation location for the Windows DDK by checking
+    # the WDK_DIR environment variable, may be None.
+    self.wdk_dir = os.environ.get('WDK_DIR')
+
+    supported_fields = [
+        ('msvs_configuration_attributes', dict),
+        ('msvs_settings', dict),
+        ('msvs_system_include_dirs', list),
+        ('msvs_disabled_warnings', list),
+        ('msvs_precompiled_header', str),
+        ('msvs_precompiled_source', str),
+        ('msvs_configuration_platform', str),
+        ('msvs_target_platform', str),
+    ]
+    validators = {
+        'msvs_settings': self._SettingsValidator,
+    }
+    configs = spec['configurations']
+    for field, default in supported_fields:
+      setattr(self, field, {})
+      for configname, config in configs.iteritems():
+        getattr(self, field)[configname] = config.get(field, default())
+        if field in validators:
+          validators[field](configname)
+
+    self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])
+
+  def GetVSMacroEnv(self, base_to_build=None, config=None):
+    """Get a dict of variables mapping internal VS macro names to their gyp
+    equivalents."""
+    vs_path = self.vs_version.Path()
+    # Absolute paths in cygwin the path will start with /cygdrive/c/
+    # The MS compiler tools need
+    # TODO: this is getting generated from the vs install path
+    # need to pass this in, or fix earlier in generation
+    if sys.platform == 'cygwin':
+      vs_path = cygpath.to_nt(vs_path)
+
+    target_platform = 'Win32' if self.GetArch(config) == 'x86' else 'x64'
+
+    replacements = {
+        '$(VSInstallDir)': vs_path,
+        '$(VCInstallDir)': os.path.join(vs_path, 'VC') + '\\',
+        '$(OutDir)\\': base_to_build + '\\' if base_to_build else '',
+        '$(IntDir)': '$!INTERMEDIATE_DIR',
+        '$(InputPath)': '${source}',
+        '$(InputName)': '${root}',
+        '$(ProjectName)': self.spec['target_name'],
+        '$(PlatformName)': target_platform,
+        '$(ProjectDir)\\': '',
+    }
+
+    replacements['$(WDK_DIR)'] = self.wdk_dir if self.wdk_dir else ''
+    return replacements
+
+  def ConvertVSMacros(self, s, base_to_build=None, config=None):
+    """Convert from VS macro names to something equivalent."""
+    env = self.GetVSMacroEnv(base_to_build, config=config)
+    return ToolchainImpl().ExpandEnvVars(s, env)
+
+  def ProcessLibraries(self, libraries):
+    """Strip -l from library if it's specified with that."""
+    return [lib[2:] if lib.startswith('-l') else lib for lib in libraries]
+
+  def _GetAndMunge(self, field, path, default, prefix, append, map):
+    """Retrieve a value from |field| at |path| or return |default|. If
+    |append| is specified, and the item is found, it will be appended to that
+    object instead of returned. If |map| is specified, results will be
+    remapped through |map| before being returned or appended."""
+    result = _GenericRetrieve(field, default, path)
+    result = _DoRemapping(result, map)
+    result = _AddPrefix(result, prefix)
+    return _AppendOrReturn(append, result)
+
+  class _GetWrapper(object):
+
+    def __init__(self, parent, field, base_path, append=None):
+      self.parent = parent
+      self.field = field
+      self.base_path = [base_path]
+      self.append = append
+
+    def __call__(self, name, map=None, prefix='', default=None):
+      return self.parent._GetAndMunge(
+          self.field,
+          self.base_path + [name],
+          default=default,
+          prefix=prefix,
+          append=self.append,
+          map=map)
+
+  def GetArch(self, config):
+    """Get architecture based on msvs_configuration_platform and
+    msvs_target_platform. Returns either 'x86' or 'x64'."""
+    configuration_platform = self.msvs_configuration_platform.get(config, '')
+    platform = self.msvs_target_platform.get(config, '')
+    if not platform:  # If no specific override, use the configuration's.
+      platform = configuration_platform
+    # Map from platform to architecture.
+    return {'Win32': 'x86', 'x64': 'x64'}.get(platform, 'x86')
+
+  def _TargetConfig(self, config):
+    """Returns the target-specific configuration."""
+    # On Cobalt, we're not using any suffix on config names like Win_Debug_x64.
+    # Cobalt on Windows is x64 only.
+    return config
+
+  def _Setting(self,
+               path,
+               config,
+               default=None,
+               prefix='',
+               append=None,
+               map=None):
+    """_GetAndMunge for msvs_settings."""
+    return self._GetAndMunge(self.msvs_settings[config], path, default, prefix,
+                             append, map)
+
+  def _SettingsValidator(self, configname):
+    """Validate msvs_settings."""
+    valid_fields = [
+        'VCCLCompilerTool',
+        'VCLinkerTool',
+        'VCLibrarianTool',
+        'VCMIDLTool',
+        'VCResourceCompilerTool',
+        'VCManifestTool',
+    ]
+    for field in self.msvs_settings[configname]:
+      if field not in valid_fields:
+        message = ('Invalid msvs_settings field: "%s", '
+                   'config: "%s"' % (field, configname))
+        close_match = get_close_matches(field, valid_fields, 1)
+        if close_match:
+          message += '\nDid you mean %s?' % tuple(close_match)
+        raise Exception(message)
+
+  def _ConfigAttrib(self,
+                    path,
+                    config,
+                    default=None,
+                    prefix='',
+                    append=None,
+                    map=None):
+    """_GetAndMunge for msvs_configuration_attributes."""
+    return self._GetAndMunge(self.msvs_configuration_attributes[config], path,
+                             default, prefix, append, map)
+
+  def ProcessIncludeDirs(self, include_dirs, config):
+    """Updates include_dirs to expand VS specific paths, and adds the system
+    include dirs used for platform SDK and similar."""
+    config = self._TargetConfig(config)
+    includes = include_dirs + self.msvs_system_include_dirs[config]
+    includes.extend(
+        self._Setting(
+            ('VCCLCompilerTool', 'AdditionalIncludeDirectories'),
+            config,
+            default=[]))
+    return [self.ConvertVSMacros(p, config=config) for p in includes]
+
+  def GetDefines(self, config):
+    """Returns the set of defines that are injected to the defines list based
+    on other VS settings."""
+    config = self._TargetConfig(config)
+    defines = []
+    if self._ConfigAttrib(['CharacterSet'], config) == '1':
+      defines.extend(('_UNICODE', 'UNICODE'))
+    if self._ConfigAttrib(['CharacterSet'], config) == '2':
+      defines.append('_MBCS')
+    defines.extend(
+        self._Setting(
+            ('VCCLCompilerTool', 'PreprocessorDefinitions'), config,
+            default=[]))
+    return defines
+
+  def GetOutputName(self, config, expand_special):
+    """Gets the explicitly overridden output name for a target or returns None
+    if it's not overridden."""
+    config = self._TargetConfig(config)
+    type = self.spec['type']
+    root = 'VCLibrarianTool' if type == 'static_library' else 'VCLinkerTool'
+    # TODO: Handle OutputDirectory without OutputFile.
+    output_file = self._Setting((root, 'OutputFile'), config)
+    if output_file:
+      output_file = expand_special(
+          self.ConvertVSMacros(output_file, config=config))
+    return output_file
+
+  def GetPDBName(self, config, expand_special):
+    """Gets the explicitly overridden pdb name for a target or returns None
+    if it's not overridden."""
+    config = self._TargetConfig(config)
+    output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config)
+    if output_file:
+      output_file = expand_special(
+          self.ConvertVSMacros(output_file, config=config))
+    return output_file
+
+  def GetCflags(self, config):
+    """Returns the flags that need to be added to .c and .cc compilations."""
+    config = self._TargetConfig(config)
+    cflags = []
+    cflags.extend(['/wd' + w for w in self.msvs_disabled_warnings[config]])
+    cl = self._GetWrapper(
+        self, self.msvs_settings[config], 'VCCLCompilerTool', append=cflags)
+    cl('Optimization',
+       map={'0': 'd',
+            '1': '1',
+            '2': '2',
+            '3': 'x'},
+       prefix='/O')
+    cl('InlineFunctionExpansion', prefix='/Ob')
+    cl('OmitFramePointers', map={'false': '-', 'true': ''}, prefix='/Oy')
+    cl('FavorSizeOrSpeed', map={'1': 't', '2': 's'}, prefix='/O')
+    cl('WholeProgramOptimization', map={'true': '/GL'})
+    cl('WarningLevel', prefix='/W')
+    cl('WarnAsError', map={'false': '', 'true': '/WX'})
+    cl('DebugInformationFormat',
+       map={'1': '7',
+            '3': 'i',
+            '4': 'I'},
+       prefix='/Z')
+    cl('RuntimeTypeInfo', map={'true': '/GR', 'false': '/GR-'})
+    cl('EnableFunctionLevelLinking', map={'true': '/Gy', 'false': '/Gy-'})
+    cl('MinimalRebuild', map={'true': '/Gm', 'false': '/Gm-'})
+    cl('BufferSecurityCheck', map={'true': '/GS', 'false': '/GS-'})
+    cl('BasicRuntimeChecks', map={'1': 's', '2': 'u', '3': '1'}, prefix='/RTC')
+    cl('RuntimeLibrary',
+       map={'0': 'T',
+            '1': 'Td',
+            '2': 'D',
+            '3': 'Dd'},
+       prefix='/M')
+    cl('ExceptionHandling', map={'1': 'sc', '2': 'a'}, prefix='/EH')
+    cl('EnablePREfast', map={'true': '/analyze'})
+    cl('AdditionalOptions', prefix='')
+    cflags.extend([
+        '/FI' + f
+        for f in self._Setting(
+            ('VCCLCompilerTool', 'ForcedIncludeFiles'), config, default=[])
+    ])
+    cflags.extend([
+        '/Zc:' + f
+        for f in self._Setting(
+            ('VCCLCompilerTool', 'Conformance'), config, default=[])
+    ])
+
+    # ninja handles parallelism by itself, don't have the compiler do it too.
+    cflags = filter(lambda x: not x.startswith('/MP'), cflags)
+    return cflags
+
+  def GetCflagsObjectiveC(self):
+    pass
+
+  def GetCflagsObjectiveCC(self):
+    pass
+
+  def _GetPchFlags(self, config, extension):
+    """Get the flags to be added to the cflags for precompiled header support.
+    """
+    config = self._TargetConfig(config)
+    # The PCH is only built once by a particular source file. Usage of PCH must
+    # only be for the same language (i.e. C vs. C++), so only include the pch
+    # flags when the language matches.
+    if self.msvs_precompiled_header[config]:
+      source_ext = os.path.splitext(self.msvs_precompiled_source[config])[1]
+      if _LanguageMatchesForPch(source_ext, extension):
+        pch = os.path.split(self.msvs_precompiled_header[config])[1]
+        return ['/Yu' + pch, '/FI' + pch, '/Fp${pchprefix}.' + pch + '.pch']
+    return []
+
+  def UsesComponentExtensions(self, config):
+    return self._Setting(
+        ('VCCLCompilerTool', 'ComponentExtensions'), config, default=[])
+
+  def GetCflagsC(self, config):
+    """Returns the flags that need to be added to .c compilations."""
+    config = self._TargetConfig(config)
+    return self._GetPchFlags(config, '.c')
+
+  def GetCflagsCC(self, config):
+    """Returns the flags that need to be added to .cc compilations."""
+    config = self._TargetConfig(config)
+    ccflags = []
+    cl = self._GetWrapper(
+        self, self.msvs_settings[config], 'VCCLCompilerTool', append=ccflags)
+    cl('ComponentExtensions', map={'true': '/ZW'})
+
+    if self.UsesComponentExtensions(config):
+      # Disable PCH for libs compiled with /ZW, even if it was requested.
+      # Causes a fatal compiler error.
+      return ['/TP'] + ccflags
+    else:
+      return ['/TP'] + self._GetPchFlags(config, '.cc') + ccflags
+
+  def _GetAdditionalLibraryDirectories(self, root, config, gyp_path_to_ninja):
+    """Get and normalize the list of paths in AdditionalLibraryDirectories
+    setting."""
+    config = self._TargetConfig(config)
+    libpaths = self._Setting(
+        (root, 'AdditionalLibraryDirectories'), config, default=[])
+    libpaths = [
+        os.path.normpath(
+            gyp_path_to_ninja(self.ConvertVSMacros(p, config=config)))
+        for p in libpaths
+    ]
+    return ['/LIBPATH:"' + p + '"' for p in libpaths]
+
+  def GetLibFlags(self, config, gyp_path_to_ninja):
+    """Returns the flags that need to be added to lib commands."""
+    config = self._TargetConfig(config)
+    libflags = []
+    lib = self._GetWrapper(
+        self, self.msvs_settings[config], 'VCLibrarianTool', append=libflags)
+    libflags.extend(
+        self._GetAdditionalLibraryDirectories('VCLibrarianTool', config,
+                                              gyp_path_to_ninja))
+    lib('AdditionalOptions')
+    return libflags
+
+  def _GetDefFileAsLdflags(self, spec, ldflags, gyp_path_to_ninja):
+    """.def files get implicitly converted to a ModuleDefinitionFile for the
+    linker in the VS generator. Emulate that behaviour here."""
+    def_file = ''
+    if spec['type'] in ('shared_library', 'loadable_module', 'executable'):
+      def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
+      if len(def_files) == 1:
+        ldflags.append('/DEF:"%s"' % gyp_path_to_ninja(def_files[0]))
+      elif len(def_files) > 1:
+        raise Exception('Multiple .def files')
+
+  def GetLdFlags(self, config, **kwargs):
+    """Returns the flags that need to be added to link commands, and the
+    manifest files."""
+
+    gyp_path_to_ninja = kwargs['gyp_path_to_ninja']
+    expand_special = kwargs['expand_special']
+    manifest_name = kwargs['manifest_name']
+    is_executable = kwargs['is_executable']
+
+    config = self._TargetConfig(config)
+    ldflags = []
+    ld = self._GetWrapper(
+        self, self.msvs_settings[config], 'VCLinkerTool', append=ldflags)
+    self._GetDefFileAsLdflags(self.spec, ldflags, gyp_path_to_ninja)
+    ld('GenerateDebugInformation', map={'true': '/DEBUG'})
+    ld('TargetMachine', map={'1': 'X86', '17': 'X64'}, prefix='/MACHINE:')
+    ldflags.extend(
+        self._GetAdditionalLibraryDirectories('VCLinkerTool', config,
+                                              gyp_path_to_ninja))
+    ld('DelayLoadDLLs', prefix='/DELAYLOAD:')
+    out = self.GetOutputName(config, expand_special)
+    if out:
+      ldflags.append('/OUT:' + out)
+    pdb = self.GetPDBName(config, expand_special)
+    if pdb:
+      ldflags.append('/PDB:' + pdb)
+    ld('AdditionalOptions', prefix='')
+    ld('SubSystem', map={'1': 'CONSOLE', '2': 'WINDOWS'}, prefix='/SUBSYSTEM:')
+    ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL')
+    ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED')
+    ld('RandomizedBaseAddress',
+       map={'1': ':NO',
+            '2': ''},
+       prefix='/DYNAMICBASE')
+    ld('DataExecutionPrevention', map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT')
+    ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:')
+    ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:')
+    ld('LinkTimeCodeGeneration', map={'1': '/LTCG'})
+    ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:')
+    ld('ResourceOnlyDLL', map={'true': '/NOENTRY'})
+    ld('EntryPointSymbol', prefix='/ENTRY:')
+    ld('Profile', map={'true': '/PROFILE'})
+    # TODO: This should sort of be somewhere else (not really a flag).
+    ld('AdditionalDependencies', prefix='')
+
+    # If the base address is not specifically controlled, DYNAMICBASE should
+    # be on by default.
+    base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED',
+                        ldflags)
+    if not base_flags:
+      ldflags.append('/DYNAMICBASE')
+
+    # If the NXCOMPAT flag has not been specified, default to on. Despite the
+    # documentation that says this only defaults to on when the subsystem is
+    # Vista or greater (which applies to the linker), the IDE defaults it on
+    # unless it's explicitly off.
+    if not filter(lambda x: 'NXCOMPAT' in x, ldflags):
+      ldflags.append('/NXCOMPAT')
+
+    have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
+    manifest_flags, intermediate_manifest_file = self._GetLdManifestFlags(
+        config, manifest_name, is_executable and not have_def_file)
+    ldflags.extend(manifest_flags)
+    manifest_files = self._GetAdditionalManifestFiles(config, gyp_path_to_ninja)
+    manifest_files.append(intermediate_manifest_file)
+
+    return ldflags, manifest_files
+
+  def _GetLdManifestFlags(self, config, name, allow_isolation):
+    """Returns the set of flags that need to be added to the link to generate
+    a default manifest, as well as the name of the generated file."""
+    output_name = name + '.intermediate.manifest'
+    # Manifests are off for UWP. If needed by another target,
+    # please find a way to configure them per target.
+    flags = [
+        '/MANIFEST:NO',
+    ]
+    return flags, output_name
+
+  def _GetAdditionalManifestFiles(self, config, gyp_path_to_ninja):
+    """Gets additional manifest files that are added to the default one
+    generated by the linker."""
+    files = self._Setting(
+        ('VCManifestTool', 'AdditionalManifestFiles'), config, default=[])
+    if (self._Setting(('VCManifestTool', 'EmbedManifest'), config,
+                      default='') == 'true'):
+      print 'gyp/msvs_emulation.py: "EmbedManifest: true" not yet supported.'
+    if isinstance(files, str):
+      files = files.split(';')
+    return [
+        os.path.normpath(
+            gyp_path_to_ninja(self.ConvertVSMacros(f, config=config)))
+        for f in files
+    ]
+
+  def IsUseLibraryDependencyInputs(self, config):
+    """Returns whether the target should be linked via Use Library Dependency
+    Inputs (using component .objs of a given .lib)."""
+    config = self._TargetConfig(config)
+    uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config)
+    return uldi == 'true'
+
+  def GetRcFlags(self, config, gyp_to_ninja_path):
+    """Returns the flags that need to be added to invocations of the resource
+    compiler."""
+    config = self._TargetConfig(config)
+    rcflags = []
+    rc = self._GetWrapper(
+        self,
+        self.msvs_settings[config],
+        'VCResourceCompilerTool',
+        append=rcflags)
+    rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I')
+    rcflags.append('/I' + gyp_to_ninja_path('.'))
+    rc('PreprocessorDefinitions', prefix='/d')
+    # /l arg must be in hex without leading '0x'
+    rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:])
+    return rcflags
+
+  def BuildCygwinBashCommandLine(self, args, path_to_base):
+    """Build a command line that runs args via cygwin bash. We assume that all
+    incoming paths are in Windows normpath'd form, so they need to be
+    converted to posix style for the part of the command line that's passed to
+    bash. We also have to do some Visual Studio macro emulation here because
+    various rules use magic VS names for things. Also note that rules that
+    contain ninja variables cannot be fixed here (for example ${source}), so
+    the outer generator needs to make sure that the paths that are written out
+    are in posix style, if the command line will be used here."""
+    cygwin_dir = os.path.normpath(
+        os.path.join(path_to_base, self.msvs_cygwin_dirs[0]))
+    cd = ('cd %s' % path_to_base).replace('\\', '/')
+    args = [a.replace('\\', '/').replace('"', '\\"') for a in args]
+    args = ["'%s'" % a.replace("'", "'\\''") for a in args]
+    bash_cmd = ' '.join(args)
+    cmd = ('call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
+           'bash -c "%s ; %s"' % (cd, bash_cmd))
+    return cmd
+
+  def IsRuleRunUnderCygwin(self, rule):
+    """Determine if an action should be run under cygwin. If the variable is
+    unset, or set to 1 we use cygwin."""
+    return int(
+        rule.get('msvs_cygwin_shell', self.spec.get('msvs_cygwin_shell',
+                                                    1))) != 0
+
+  def _HasExplicitRuleForExtension(self, spec, extension):
+    """Determine if there's an explicit rule for a particular extension."""
+    for rule in spec.get('rules', []):
+      if rule['extension'] == extension:
+        return True
+    return False
+
+  def HasExplicitIdlRules(self, spec):
+    """Determine if there's an explicit rule for idl files. When there isn't we
+    need to generate implicit rules to build MIDL .idl files."""
+    return self._HasExplicitRuleForExtension(spec, 'idl')
+
+  def HasExplicitAsmRules(self, spec):
+    """Determine if there's an explicit rule for asm files. When there isn't we
+    need to generate implicit rules to assemble .asm files."""
+    return self._HasExplicitRuleForExtension(spec, 'asm')
+
+  def GetIdlBuildData(self, source, config):
+    """Determine the implicit outputs for an idl file. Returns output
+    directory, outputs, and variables and flags that are required."""
+    config = self._TargetConfig(config)
+    midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
+
+    def midl(name, default=None):
+      return self.ConvertVSMacros(
+          midl_get(name, default=default), config=config)
+
+    # TODO: remove references to xb1 below.
+    if config.startswith('xb1'):
+      tlb = ''
+      header = midl('HeaderFileName', default='${root}.h')
+      dlldata = ''
+      iid = ''
+      proxy = ''
+      outdir = midl('OutputDirectory', default='')
+    else:
+      tlb = midl('TypeLibraryName', default='${root}.tlb')
+      header = midl('HeaderFileName', default='${root}.h')
+      dlldata = midl('DLLDataFileName', default='dlldata.c')
+      iid = midl('InterfaceIdentifierFileName', default='${root}_i.c')
+      proxy = midl('ProxyFileName', default='${root}_p.c')
+      # Note that .tlb is not included in the outputs as it is not always
+      # generated depending on the content of the input idl file.
+      outdir = midl('OutputDirectory', default='')
+    if config.startswith('xb1'):
+      output = [header]
+    else:
+      output = [header, dlldata, iid, proxy]
+    variables = [('tlb', tlb), ('h', header), ('dlldata', dlldata),
+                 ('iid', iid), ('proxy', proxy)]
+    if config.startswith('xb1'):
+      metadata_dir = '"%s%s"' % ('C:\\Program Files (x86)\\Windows Kits\\10\\',
+                                 'UnionMetadata')
+      flags = [
+          '/env', 'x64', '/W1', '/char', 'signed', '/enum_class',
+          '/metadata_dir', metadata_dir, '/notlb', '/winrt'
+      ]
+    else:
+      # TODO: Are there configuration settings to set these flags?
+      flags = ['/char', 'signed', '/env', 'win32', '/Oicf']
+    return outdir, output, variables, flags
+
+
+def _ExtractImportantEnvironment(output_of_set):
+  """Extracts environment variables required for the toolchain to run from
+  a textual dump output by the cmd.exe 'set' command."""
+  envvars_to_save = (
+      'durangoxdk',
+      'goma_.*',  # TODO: This is ugly, but needed for goma.
+      'include',
+      'lib',
+      'libpath',
+      'path',
+      'pathext',
+      'systemroot',
+      'temp',
+      'tmp',
+      'xedk',
+      'cell_.*',
+      'sn_.*',
+      'sce_.*',)
+  env = {}
+  for line in output_of_set.splitlines():
+    for envvar in envvars_to_save:
+      if re.match(envvar + '=', line.lower()):
+        var, setting = line.split('=', 1)
+        if envvar == 'path':
+          # Our own rules (for running gyp-win-tool) and other actions in
+          # Chromium rely on python being in the path. Add the path to this
+          # python here so that if it's not in the path when ninja is run
+          # later, python will still be found.
+          setting = os.path.dirname(sys.executable) + ';' + setting
+        env[var.upper()] = setting
+        break
+  for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
+    if required not in env:
+      raise Exception('Environment variable "%s" '
+                      'required to be set to valid path' % required)
+  return env
+
+
+def _FormatAsEnvironmentBlock(envvar_dict):
+  """Format as an 'environment block' directly suitable for CreateProcess.
+  Briefly this is a list of key=value\0, terminated by an additional \0. See
+  CreateProcess documentation for more details."""
+  block = ''
+  nul = '\0'
+  for key, value in envvar_dict.iteritems():
+    block += key + '=' + value + nul
+  block += nul
+  return block
+
+
+class MSVCUWPToolchain(Toolchain):
+
+  def __init__(self):
+    self.compiler_settings = None
+
+  def _escape(self, string):
+    """Escape a string such that it can be embedded into a Ninja file without
+    further interpretation."""
+    assert '\n' not in string, 'Ninja syntax does not allow newlines'
+    # We only have one special metacharacter: '$'.
+    return string.replace('$', '$$')
+
+  def Define(self, d):
+    # cl.exe replaces literal # characters with = in preprocesor definitions for
+    # some reason. Octal-encode to work around that.
+    d = d.replace('#', '\\%03o' % ord('#'))
+    return '/D' + self.QuoteForRspFile(self._escape(d))
+
+  def EncodeRspFileList(self, args):
+    """Process a list of arguments using QuoteForRspFile."""
+    # Note that the first argument is assumed to be the command. Don't add
+    # quotes around it because then built-ins like 'echo', etc. won't work.
+    # Take care to normpath only the path in the case of 'call ../x.bat' because
+    # otherwise the whole thing is incorrectly interpreted as a path and not
+    # normalized correctly.
+    if not args:
+      return ''
+    if args[0].startswith('call '):
+      call, program = args[0].split(' ', 1)
+      program = call + ' ' + os.path.normpath(program)
+    else:
+      program = os.path.normpath(args[0])
+    return program + ' ' + ' '.join(
+        self.QuoteForRspFile(arg) for arg in args[1:])
+
+  def ExpandEnvVars(self, string, expansions):
+    """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
+    for the canonical way to retrieve a suitable dict."""
+    if '$' in string:
+      for old, new in expansions.iteritems():
+        assert '$(' not in new, new
+        string = string.replace(old, new)
+    return string
+
+  def ExpandRuleVariables(self, path, root, dirname, source, ext, name):
+    path = self.compiler_settings.ConvertVSMacros(path, config=self.config_name)
+    path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root)
+    path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'],
+                        dirname)
+    path = path.replace(generator_default_variables['RULE_INPUT_PATH'], source)
+    path = path.replace(generator_default_variables['RULE_INPUT_EXT'], ext)
+    path = path.replace(generator_default_variables['RULE_INPUT_NAME'], name)
+    return path
+
+  def GenerateEnvironmentFiles(self, toplevel_build_dir, generator_flags,
+                               open_out):
+    """It's not sufficient to have the absolute path to the compiler, linker,
+
+    etc. on Windows, as those tools rely on .dlls being in the PATH. Different
+    architectures require a different compiler binary, and different supporting
+    environment variables (INCLUDE, LIB, LIBPATH). So, we extract the
+    environment
+    here, wrap all invocations of compiler tools (cl, link, lib, rc, midl, etc.)
+    via win_tool.py which sets up the environment, and then we do not prefix the
+    compiler with an absolute path, instead preferring something like "cl.exe"
+    in
+    the rule which will then run whichever the environment setup has put in the
+    path.
+    """
+    arch = 'x64'
+
+    # Get the dos environment via set:
+    # Use cmd /c to execute under native windows command
+    args = 'set'
+
+    popen = subprocess.Popen(
+        args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    variables, _ = popen.communicate()
+    env = _ExtractImportantEnvironment(variables)
+    env_block = _FormatAsEnvironmentBlock(env)
+    f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
+    f.write(env_block)
+    f.close()
+
+  def GetCompilerSettings(self):
+    return self.compiler_settings
+
+  def GetPrecompiledHeader(self, **kwargs):
+    return MSVCPrecompiledHeader(**kwargs)
+
+  def InitCompilerSettings(self, spec, **kwargs):
+    self.compiler_settings = MsvsSettings(spec, kwargs['generator_flags'])
+
+  def SetAdditionalGypVariables(self, default_variables, **kwargs):
+    """Calculate additional variables for use in the build (called by gyp)."""
+    default_variables.setdefault('OS', 'win')
+    default_variables['EXECUTABLE_SUFFIX'] = '.exe'
+    default_variables['STATIC_LIB_PREFIX'] = ''
+    default_variables['STATIC_LIB_SUFFIX'] = '.lib'
+    default_variables['SHARED_LIB_PREFIX'] = ''
+    default_variables['SHARED_LIB_SUFFIX'] = '.dll'
+    generator_flags = {}
+
+    # Copy additional generator configuration data from VS, which is shared
+    # by the Windows Ninja generator.
+    import gyp.generator.msvs as msvs_generator
+    generator_additional_non_configuration_keys = getattr(
+        msvs_generator, 'generator_additional_non_configuration_keys', [])
+    generator_additional_path_sections = getattr(
+        msvs_generator, 'generator_additional_path_sections', [])
+
+    # Set a variable so conditions can be based on msvs_version.
+    msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
+    default_variables['MSVS_VERSION'] = msvs_version.ShortName()
+
+    # To determine processor word size on Windows, in addition to checking
+    # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
+    # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
+    # contains the actual word size of the system when running thru WOW64).
+    if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
+        '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
+      default_variables['MSVS_OS_BITS'] = 64
+    else:
+      default_variables['MSVS_OS_BITS'] = 32
+    return
+
+  def VerifyMissingSources(self, sources, **kwargs):
+    """Emulate behavior of msvs_error_on_missing_sources present in the msvs
+
+    generator: Check that all regular source files, i.e. not created at run
+    time,
+    exist on disk. Missing files cause needless recompilation when building via
+    VS, and we want this check to match for people/bots that build using ninja,
+    so they're not surprised when the VS build fails.
+    """
+    build_dir = kwargs['build_dir']
+    generator_flags = kwargs['generator_flags']
+    gyp_path_to_ninja = kwargs['gyp_path_to_ninja']
+    if int(generator_flags.get('msvs_error_on_missing_sources', 0)):
+      no_specials = filter(lambda x: '$' not in x, sources)
+      relative = [
+          os.path.join(build_dir, gyp_path_to_ninja(s)) for s in no_specials
+      ]
+      missing = filter(lambda x: not os.path.exists(x), relative)
+      if missing:
+        # They'll look like out\Release\..\..\stuff\things.cc, so normalize the
+        # path for a slightly less crazy looking output.
+        cleaned_up = [os.path.normpath(x) for x in missing]
+        raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up))
+
+  def QuoteForRspFile(self, arg):
+    """Quote a command line argument so that it appears as one argument when
+    processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
+    Windows programs)."""
+    # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment
+    # threads. This is actually the quoting rules for CommandLineToArgvW, not
+    # for the shell, because the shell doesn't do anything in Windows. This
+    # works more or less because most programs (including the compiler, etc.)
+    # use that function to handle command line arguments.
+
+    # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
+    # preceding it, and results in n backslashes + the quote. So we substitute
+    # in 2* what we match, +1 more, plus the quote.
+    windows_quoter_regex = re.compile(r'(\\*)"')
+    arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg)
+
+    # %'s also need to be doubled otherwise they're interpreted as batch
+    # positional arguments. Also make sure to escape the % so that they're
+    # passed literally through escaping so they can be singled to just the
+    # original %. Otherwise, trying to pass the literal representation that
+    # looks like an environment variable to the shell (e.g. %PATH%) would fail.
+    arg = arg.replace('%', '%%')
+
+    # These commands are used in rsp files, so no escaping for the shell (via ^)
+    # is necessary.
+
+    # Finally, wrap the whole thing in quotes so that the above quote rule
+    # applies and whitespace isn't a word break.
+    return '"' + arg + '"'
+
+
+def ToolchainImpl():
+  return MSVCUWPToolchain()
\ No newline at end of file
diff --git a/src/starboard/shared/starboard/application.cc b/src/starboard/shared/starboard/application.cc
index 1402d12..1a4bf89 100644
--- a/src/starboard/shared/starboard/application.cc
+++ b/src/starboard/shared/starboard/application.cc
@@ -15,8 +15,10 @@
 #include "starboard/shared/starboard/application.h"
 
 #include "starboard/atomic.h"
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/condition_variable.h"
 #include "starboard/event.h"
+#include "starboard/log.h"
 #include "starboard/memory.h"
 #include "starboard/string.h"
 
@@ -28,6 +30,8 @@
 
 namespace {
 
+const char kPreloadSwitch[] = "preload";
+
 // Dispatches an event of |type| with |data| to the system event handler,
 // calling |destructor| on |data| when finished dispatching. Does all
 // appropriate NULL checks so you don't have to.
@@ -75,7 +79,9 @@
 int Application::Run(int argc, char** argv) {
   Initialize();
   command_line_.reset(new CommandLine(argc, argv));
-  if (IsStartImmediate()) {
+  if (IsPreloadImmediate()) {
+    DispatchPreload();
+  } else if (IsStartImmediate()) {
     DispatchStart();
   }
 
@@ -129,7 +135,6 @@
 }
 
 #if SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
-
 void Application::HandleFrame(SbPlayer player,
                               const scoped_refptr<VideoFrame>& frame,
                               int x,
@@ -141,6 +146,7 @@
 #endif  // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
 
 void Application::SetStartLink(const char* start_link) {
+  SB_DCHECK(IsCurrentThread());
   SbMemoryDeallocate(start_link_);
   if (start_link) {
     start_link_ = SbStringDuplicate(start_link);
@@ -150,60 +156,90 @@
 }
 
 void Application::DispatchStart() {
+  SB_DCHECK(IsCurrentThread());
+  SB_DCHECK(state_ == kStateUnstarted || state_ == kStatePreloading);
+  DispatchAndDelete(CreateInitialEvent(kSbEventTypeStart));
+}
+
+void Application::DispatchPreload() {
+  SB_DCHECK(IsCurrentThread());
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
   SB_DCHECK(state_ == kStateUnstarted);
-  SbEventStartData start_data;
-  start_data.argument_values =
-      const_cast<char**>(command_line_->GetOriginalArgv());
-  start_data.argument_count = command_line_->GetOriginalArgc();
-  start_data.link = start_link_;
-  Dispatch(kSbEventTypeStart, &start_data, NULL);
-  state_ = kStateStarted;
+  DispatchAndDelete(CreateInitialEvent(kSbEventTypePreload));
+#else  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  SB_NOTREACHED();
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+}
+
+bool Application::HasPreloadSwitch() {
+  return command_line_->HasSwitch(kPreloadSwitch);
 }
 
 bool Application::DispatchAndDelete(Application::Event* event) {
+  SB_DCHECK(IsCurrentThread());
   if (!event) {
     return true;
   }
 
-  // DispatchStart() must be called first
-  SB_DCHECK(state_ != kStateUnstarted);
+  // Ensure the event is deleted unless it is released.
+  scoped_ptr<Event> scoped_event(event);
 
   // Ensure that we go through the the appropriate lifecycle events based on the
   // current state.
-  switch (event->event->type) {
+  switch (scoped_event->event->type) {
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+    case kSbEventTypePreload:
+      if (state() != kStateUnstarted) {
+        return true;
+      }
+      break;
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+    case kSbEventTypeStart:
+      if (state() != kStatePreloading && state() != kStateUnstarted) {
+        return true;
+      }
+      break;
     case kSbEventTypePause:
       if (state() != kStateStarted) {
-        delete event;
         return true;
       }
       break;
     case kSbEventTypeUnpause:
       if (state() == kStateStarted) {
-        delete event;
+        return true;
+      }
+
+      if (state() == kStatePreloading) {
+        // Convert to Start event and consume.
+        DispatchStart();
         return true;
       }
 
       if (state() == kStateSuspended) {
         Inject(new Event(kSbEventTypeResume, NULL, NULL));
-        Inject(event);
+        Inject(scoped_event.release());
         return true;
       }
       break;
     case kSbEventTypeSuspend:
       if (state() == kStateSuspended) {
-        delete event;
         return true;
       }
 
+      if (state() == kStatePreloading) {
+        // If Preloading, we can jump straight to Suspended, so we don't try to
+        // do anything fancy here.
+        break;
+      }
+
       if (state() == kStateStarted) {
         Inject(new Event(kSbEventTypePause, NULL, NULL));
-        Inject(event);
+        Inject(scoped_event.release());
         return true;
       }
       break;
     case kSbEventTypeResume:
       if (state() == kStateStarted || state() == kStatePaused) {
-        delete event;
         return true;
       }
       if (state() == kStateSuspended) {
@@ -214,32 +250,40 @@
       if (state() == kStateStarted) {
         Inject(new Event(kSbEventTypePause, NULL, NULL));
         Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
-        Inject(event);
+        Inject(scoped_event.release());
         return true;
       }
 
       if (state() == kStatePaused) {
         Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
-        Inject(event);
+        Inject(scoped_event.release());
         return true;
       }
-      error_level_ = event->error_level;
+      error_level_ = scoped_event->error_level;
       break;
     case kSbEventTypeScheduled: {
       TimedEvent* timed_event =
-          reinterpret_cast<TimedEvent*>(event->event->data);
+          reinterpret_cast<TimedEvent*>(scoped_event->event->data);
       timed_event->callback(timed_event->context);
-      delete event;
       return true;
     }
     default:
       break;
   }
 
-  SbEventHandle(event->event);
+  SbEventHandle(scoped_event->event);
 
-  bool should_continue = true;
-  switch (event->event->type) {
+  switch (scoped_event->event->type) {
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+    case kSbEventTypePreload:
+      SB_DCHECK(state() == kStateUnstarted);
+      state_ = kStatePreloading;
+      break;
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+    case kSbEventTypeStart:
+      SB_DCHECK(state() == kStatePreloading || state() == kStateUnstarted);
+      state_ = kStateStarted;
+      break;
     case kSbEventTypePause:
       SB_DCHECK(state() == kStateStarted);
       state_ = kStatePaused;
@@ -249,7 +293,7 @@
       state_ = kStateStarted;
       break;
     case kSbEventTypeSuspend:
-      SB_DCHECK(state() == kStatePaused);
+      SB_DCHECK(state() == kStatePreloading || state() == kStatePaused);
       state_ = kStateSuspended;
       OnSuspend();
       break;
@@ -260,14 +304,14 @@
     case kSbEventTypeStop:
       SB_DCHECK(state() == kStateSuspended);
       state_ = kStateStopped;
-      should_continue = false;
-      break;
+      return false;
     default:
       break;
   }
 
-  delete event;
-  return should_continue;
+  // Should not be unstarted after the first event.
+  SB_DCHECK(state() != kStateUnstarted);
+  return true;
 }
 
 void Application::CallTeardownCallbacks() {
@@ -277,6 +321,21 @@
   }
 }
 
+Application::Event* Application::CreateInitialEvent(SbEventType type) {
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  SB_DCHECK(type == kSbEventTypePreload || type == kSbEventTypeStart);
+#else  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  SB_DCHECK(type == kSbEventTypeStart);
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  SbEventStartData* start_data = new SbEventStartData();
+  SbMemorySet(start_data, 0, sizeof(SbEventStartData));
+  start_data->argument_values =
+      const_cast<char**>(command_line_->GetOriginalArgv());
+  start_data->argument_count = command_line_->GetOriginalArgc();
+  start_data->link = start_link_;
+  return new Event(type, start_data, &DeleteDestructor<SbEventStartData>);
+}
+
 }  // namespace starboard
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h
index 1137712..c70bcfb 100644
--- a/src/starboard/shared/starboard/application.h
+++ b/src/starboard/shared/starboard/application.h
@@ -55,6 +55,11 @@
     // The initial Unstarted state.
     kStateUnstarted,
 
+    // The preloading state, where the application gets as much work done as
+    // possible to launch, but is not visible. You see exits to kStateStarted
+    // and kStateSuspended.
+    kStatePreloading,
+
     // The normal foreground, fully-visible state after receiving the initial
     // START event or after UNPAUSE from Paused.
     kStateStarted,
@@ -248,12 +253,12 @@
 #if SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
   // 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 scoped_refptr<VideoFrame>& frame,
-                           int x,
-                           int y,
-                           int width,
-                           int height) {}
+  virtual void AcceptFrame(SbPlayer /* player */,
+                           const scoped_refptr<VideoFrame>& /* frame */,
+                           int /* x */,
+                           int /* y */,
+                           int /* width */,
+                           int /* height */) {}
 #endif  // SB_HAS(PLAYER) && (SB_API_VERSION >= 4 || SB_IS(PLAYER_PUNCHED_OUT))
 
   // Blocks until the next event is available. Subclasses must implement this
@@ -314,13 +319,29 @@
   // means "success" or at least "no error."
   int error_level() const { return error_level_; }
 
-  // Returns true if the Start event should be sent in |Run| before entering the
-  // event loop. Derived classes that return false must call |DispatchStart|.
+  // Returns whether the Start event should be sent in |Run| before entering the
+  // event loop. Derived classes that return false must call |DispatchStart| at
+  // some point.
   virtual bool IsStartImmediate() { return true; }
 
-  // Dispatches a Start event to the system event handler.
+  // Synchronously dispatches a Start event to the system event handler. Must be
+  // called on the main dispatch thread.
   void DispatchStart();
 
+  // Returns whether the Preload event should be sent in |Run| before entering
+  // the event loop. Derived classes that return true must call |Unpause| or
+  // |DispatchStart| at some point.
+  //
+  // |IsPreloadImmediate|, if true, takes precedence over |IsStartImmediate|.
+  virtual bool IsPreloadImmediate() { return false; }
+
+  // Synchronously dispatches a Preload event to the system event handler. Must
+  // be called on the main dispatch thread.
+  void DispatchPreload();
+
+  // Returns whether the '--preload' command-line argument is specified.
+  bool HasPreloadSwitch();
+
   // Dispatches |event| to the system event handler, taking ownership of the
   // event. Checks for consistency with the current application state when state
   // events are dispatched. Returns whether to keep servicing the event queue,
@@ -332,6 +353,10 @@
   void CallTeardownCallbacks();
 
  private:
+  // Creates an initial event type of either Start or Preload with the original
+  // command line and deep link.
+  Event* CreateInitialEvent(SbEventType type);
+
   // The single application instance.
   static Application* g_instance;
 
diff --git a/src/starboard/shared/starboard/media/media_tests.gypi b/src/starboard/shared/starboard/media/media_tests.gypi
new file mode 100644
index 0000000..24e641a
--- /dev/null
+++ b/src/starboard/shared/starboard/media/media_tests.gypi
@@ -0,0 +1,23 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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': {
+    # This will be included by 'starboard_platform_tests.gyp' so full path names
+    # have to be used here.
+    'media_tests_sources': [
+      '<(DEPTH)/starboard/shared/starboard/media/mime_type_test.cc',
+    ],
+  },
+}
diff --git a/src/starboard/shared/starboard/media/media_util.cc b/src/starboard/shared/starboard/media/media_util.cc
index bac646d..550dd52 100644
--- a/src/starboard/shared/starboard/media/media_util.cc
+++ b/src/starboard/shared/starboard/media/media_util.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/shared/starboard/media/media_util.h"
 
+#include "starboard/log.h"
+
 namespace starboard {
 namespace shared {
 namespace starboard {
@@ -48,6 +50,18 @@
   return kSbMediaTransferIdUnknown;
 }
 
+int GetBytesPerSample(SbMediaAudioSampleType sample_type) {
+  switch (sample_type) {
+    case kSbMediaAudioSampleTypeInt16:
+      return 2;
+    case kSbMediaAudioSampleTypeFloat32:
+      return 4;
+  }
+
+  SB_NOTREACHED();
+  return 4;
+}
+
 }  // namespace media
 }  // namespace starboard
 }  // namespace shared
diff --git a/src/starboard/shared/starboard/media/media_util.h b/src/starboard/shared/starboard/media/media_util.h
index 51c793c..e778b78 100644
--- a/src/starboard/shared/starboard/media/media_util.h
+++ b/src/starboard/shared/starboard/media/media_util.h
@@ -33,6 +33,8 @@
 // Requirements (2018).
 SbMediaTransferId GetTransferIdFromString(const std::string& eotf);
 
+int GetBytesPerSample(SbMediaAudioSampleType sample_type);
+
 }  // namespace media
 }  // namespace starboard
 }  // namespace shared
diff --git a/src/starboard/shared/starboard/media/mime_type_test.cc b/src/starboard/shared/starboard/media/mime_type_test.cc
index cf18f64..e8847d7 100644
--- a/src/starboard/shared/starboard/media/mime_type_test.cc
+++ b/src/starboard/shared/starboard/media/mime_type_test.cc
@@ -91,32 +91,8 @@
 }
 
 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());
-  }
+  MimeType mime_type("codecs=\"abc\"; audio/mp4");
+  EXPECT_FALSE(mime_type.is_valid());
 }
 
 TEST(MimeTypeTest, ValidContentTypeWithParams) {
@@ -221,11 +197,9 @@
 }
 
 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));
-  }
+  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) {
@@ -242,6 +216,13 @@
   }
 }
 
+TEST(MimeTypeTest, GetParamValueInInvalidType) {
+  MimeType mime_type("video/mp4; name0=abc; name1=123.4");
+  EXPECT_FLOAT_EQ(0, mime_type.GetParamIntValue(0));
+  EXPECT_FLOAT_EQ(0.f, mime_type.GetParamFloatValue(0));
+  EXPECT_FLOAT_EQ(0, mime_type.GetParamIntValue(1));
+}
+
 TEST(MimeTypeTest, GetParamIntValueWithName) {
   {
     MimeType mime_type("video/mp4; name=123");
diff --git a/src/starboard/shared/starboard/microphone/microphone_close.cc b/src/starboard/shared/starboard/microphone/microphone_close.cc
index 60c1a98..9519189 100644
--- a/src/starboard/shared/starboard/microphone/microphone_close.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_close.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -22,4 +22,4 @@
   return SbMicrophoneIsValid(microphone) ? microphone->Close() : false;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_create.cc b/src/starboard/shared/starboard/microphone/microphone_create.cc
index 3eee5d4..962af50 100644
--- a/src/starboard/shared/starboard/microphone/microphone_create.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_create.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -25,4 +25,4 @@
                                                buffer_size);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_destroy.cc b/src/starboard/shared/starboard/microphone/microphone_destroy.cc
index df7891f..59ec025 100644
--- a/src/starboard/shared/starboard/microphone/microphone_destroy.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_destroy.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -22,4 +22,4 @@
   SbMicrophonePrivate::DestroyMicrophone(microphone);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_get_available.cc b/src/starboard/shared/starboard/microphone/microphone_get_available.cc
index 2cb666e..58fe474 100644
--- a/src/starboard/shared/starboard/microphone/microphone_get_available.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_get_available.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -24,4 +24,4 @@
                                                       info_array_size);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_internal.h b/src/starboard/shared/starboard/microphone/microphone_internal.h
index f14e734..4895da5 100644
--- a/src/starboard/shared/starboard/microphone/microphone_internal.h
+++ b/src/starboard/shared/starboard/microphone/microphone_internal.h
@@ -18,7 +18,7 @@
 #include "starboard/microphone.h"
 #include "starboard/shared/internal_only.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 struct SbMicrophonePrivate {
   virtual ~SbMicrophonePrivate() {}
@@ -36,6 +36,6 @@
   static void DestroyMicrophone(SbMicrophone microphone);
 };
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #endif  // STARBOARD_SHARED_STARBOARD_MICROPHONE_MICROPHONE_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc b/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
index 9f32f2b..2154a0b 100644
--- a/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -24,4 +24,4 @@
       id, sample_rate_in_hz);
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_open.cc b/src/starboard/shared/starboard/microphone/microphone_open.cc
index a7ebb6c..4042c76 100644
--- a/src/starboard/shared/starboard/microphone/microphone_open.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_open.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -22,4 +22,4 @@
   return SbMicrophoneIsValid(microphone) ? microphone->Open() : false;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/microphone/microphone_read.cc b/src/starboard/shared/starboard/microphone/microphone_read.cc
index 137c854..1fbfce6 100644
--- a/src/starboard/shared/starboard/microphone/microphone_read.cc
+++ b/src/starboard/shared/starboard/microphone/microphone_read.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 #include "starboard/shared/starboard/microphone/microphone_internal.h"
 
@@ -26,4 +26,4 @@
              : -1;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/starboard/player/decoded_audio_internal.cc b/src/starboard/shared/starboard/player/decoded_audio_internal.cc
index 1d53da4..5ee70b0 100644
--- a/src/starboard/shared/starboard/player/decoded_audio_internal.cc
+++ b/src/starboard/shared/starboard/player/decoded_audio_internal.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 
+#include <algorithm>
+
 #include "starboard/log.h"
 
 namespace starboard {
@@ -21,11 +23,100 @@
 namespace starboard {
 namespace player {
 
-DecodedAudio::DecodedAudio() : pts_(0), size_(0) {}
+DecodedAudio::DecodedAudio()
+    : channels_(0),
+      sample_type_(kSbMediaAudioSampleTypeInt16),
+      storage_type_(kSbMediaAudioFrameStorageTypeInterleaved),
+      pts_(0),
+      size_(0) {}
 
-DecodedAudio::DecodedAudio(SbMediaTime pts, size_t size)
-    : pts_(pts), buffer_(new uint8_t[size]), size_(size) {
-  SB_DCHECK(size > 0) << size;
+DecodedAudio::DecodedAudio(int channels,
+                           SbMediaAudioSampleType sample_type,
+                           SbMediaAudioFrameStorageType storage_type,
+                           SbMediaTime pts,
+                           size_t size)
+    : channels_(channels),
+      sample_type_(sample_type),
+      storage_type_(storage_type),
+      pts_(pts),
+      buffer_(new uint8_t[size]),
+      size_(size) {}
+
+int DecodedAudio::frames() const {
+  int bytes_per_sample;
+  if (sample_type_ == kSbMediaAudioSampleTypeInt16) {
+    bytes_per_sample = 2;
+  } else {
+    SB_DCHECK(sample_type_ == kSbMediaAudioSampleTypeFloat32);
+    bytes_per_sample = 4;
+  }
+  SB_DCHECK(size_ % (bytes_per_sample * channels_) == 0);
+  return size_ / bytes_per_sample / channels_;
+}
+
+void DecodedAudio::ShrinkTo(size_t new_size) {
+  SB_DCHECK(new_size <= size_);
+  size_ = new_size;
+}
+
+void DecodedAudio::SwitchFormatTo(
+    SbMediaAudioSampleType new_sample_type,
+    SbMediaAudioFrameStorageType new_storage_type) {
+  if (new_sample_type == sample_type_ && new_storage_type == storage_type_) {
+    return;
+  }
+
+  if (storage_type_ != kSbMediaAudioFrameStorageTypeInterleaved ||
+      new_storage_type != kSbMediaAudioFrameStorageTypeInterleaved) {
+    SB_NOTREACHED();
+    // TODO: Implement switching between other storage type pairs.
+    return;
+  }
+
+  if (sample_type_ == kSbMediaAudioSampleTypeInt16 &&
+      new_sample_type == kSbMediaAudioSampleTypeFloat32 &&
+      storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved &&
+      new_storage_type == kSbMediaAudioFrameStorageTypeInterleaved) {
+    size_t new_size = sizeof(float) * frames() * channels();
+    scoped_array<uint8_t> new_buffer(new uint8_t[new_size]);
+    float* new_samples = reinterpret_cast<float*>(new_buffer.get());
+    int16_t* old_samples = reinterpret_cast<int16_t*>(buffer_.get());
+
+    for (int i = 0; i < frames() * channels(); ++i) {
+      new_samples[i] = static_cast<float>(old_samples[i]) / 32768.f;
+    }
+
+    buffer_.swap(new_buffer);
+    sample_type_ = new_sample_type;
+    size_ = new_size;
+
+    return;
+  }
+
+  if (sample_type_ == kSbMediaAudioSampleTypeFloat32 &&
+      new_sample_type == kSbMediaAudioSampleTypeInt16 &&
+      storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved &&
+      new_storage_type == kSbMediaAudioFrameStorageTypeInterleaved) {
+    size_t new_size = sizeof(int16_t) * frames() * channels();
+    scoped_array<uint8_t> new_buffer(new uint8_t[new_size]);
+    int16_t* new_samples = reinterpret_cast<int16_t*>(new_buffer.get());
+    float* old_samples = reinterpret_cast<float*>(buffer_.get());
+
+    for (int i = 0; i < frames() * channels(); ++i) {
+      float sample = std::max(old_samples[i], -1.f);
+      sample = std::min(sample, 1.f);
+      new_samples[i] = static_cast<int16_t>(sample * 32767.f);
+    }
+
+    buffer_.swap(new_buffer);
+    sample_type_ = new_sample_type;
+    size_ = new_size;
+
+    return;
+  }
+
+  // TODO: Implement switching between other sample and storage types.
+  SB_NOTREACHED();
 }
 
 }  // namespace player
diff --git a/src/starboard/shared/starboard/player/decoded_audio_internal.h b/src/starboard/shared/starboard/player/decoded_audio_internal.h
index 40a12fe..366f412 100644
--- a/src/starboard/shared/starboard/player/decoded_audio_internal.h
+++ b/src/starboard/shared/starboard/player/decoded_audio_internal.h
@@ -33,7 +33,15 @@
 class DecodedAudio : public RefCountedThreadSafe<DecodedAudio> {
  public:
   DecodedAudio();  // Signal an EOS.
-  DecodedAudio(SbMediaTime pts, size_t size);
+  DecodedAudio(int channels,
+               SbMediaAudioSampleType sample_type,
+               SbMediaAudioFrameStorageType storage_type,
+               SbMediaTime pts,
+               size_t size);
+
+  int channels() const { return channels_; }
+  SbMediaAudioSampleType sample_type() const { return sample_type_; }
+  SbMediaAudioFrameStorageType storage_type() const { return storage_type_; }
 
   bool is_end_of_stream() const { return buffer_ == NULL; }
   SbMediaTime pts() const { return pts_; }
@@ -41,8 +49,16 @@
   size_t size() const { return size_; }
 
   uint8_t* buffer() { return buffer_.get(); }
+  int frames() const;
+
+  void SwitchFormatTo(SbMediaAudioSampleType new_sample_type,
+                      SbMediaAudioFrameStorageType new_storage_type);
+  void ShrinkTo(size_t new_size);
 
  private:
+  int channels_;
+  SbMediaAudioSampleType sample_type_;
+  SbMediaAudioFrameStorageType storage_type_;
   // The timestamp of the first audio frame.
   SbMediaTime pts_;
   // Use scoped_array<uint8_t> instead of std::vector<uint8_t> to avoid wasting
diff --git a/src/starboard/shared/starboard/player/filter/audio_buffer_queue.cc b/src/starboard/shared/starboard/player/filter/audio_buffer_queue.cc
deleted file mode 100644
index c051ee8..0000000
--- a/src/starboard/shared/starboard/player/filter/audio_buffer_queue.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "cobalt/media/base/audio_buffer_queue.h"
-
-#include <algorithm>
-
-#include "base/logging.h"
-#include "cobalt/media/base/audio_bus.h"
-
-namespace cobalt {
-namespace media {
-
-AudioBufferQueue::AudioBufferQueue() {
-  Clear();
-}
-AudioBufferQueue::~AudioBufferQueue() {}
-
-void AudioBufferQueue::Clear() {
-  buffers_.clear();
-  current_buffer_ = buffers_.begin();
-  current_buffer_offset_ = 0;
-  frames_ = 0;
-}
-
-void AudioBufferQueue::Append(const scoped_refptr<AudioBuffer>& buffer_in) {
-  // Add the buffer to the queue. Inserting into deque invalidates all
-  // iterators, so point to the first buffer.
-  buffers_.push_back(buffer_in);
-  current_buffer_ = buffers_.begin();
-
-  // Update the |frames_| counter since we have added frames.
-  frames_ += buffer_in->frame_count();
-  CHECK_GT(frames_, 0);  // make sure it doesn't overflow.
-}
-
-int AudioBufferQueue::ReadFrames(int frames,
-                                 int dest_frame_offset,
-                                 AudioBus* dest) {
-  DCHECK_GE(dest->frames(), frames + dest_frame_offset);
-  return InternalRead(frames, true, 0, dest_frame_offset, dest);
-}
-
-int AudioBufferQueue::PeekFrames(int frames,
-                                 int source_frame_offset,
-                                 int dest_frame_offset,
-                                 AudioBus* dest) {
-  DCHECK_GE(dest->frames(), frames);
-  return InternalRead(frames, false, source_frame_offset, dest_frame_offset,
-                      dest);
-}
-
-void AudioBufferQueue::SeekFrames(int frames) {
-  // Perform seek only if we have enough bytes in the queue.
-  CHECK_LE(frames, frames_);
-  int taken = InternalRead(frames, true, 0, 0, NULL);
-  DCHECK_EQ(taken, frames);
-}
-
-int AudioBufferQueue::InternalRead(int frames,
-                                   bool advance_position,
-                                   int source_frame_offset,
-                                   int dest_frame_offset,
-                                   AudioBus* dest) {
-  // Counts how many frames are actually read from the buffer queue.
-  int taken = 0;
-  BufferQueue::iterator current_buffer = current_buffer_;
-  int current_buffer_offset = current_buffer_offset_;
-
-  int frames_to_skip = source_frame_offset;
-  while (taken < frames) {
-    // |current_buffer| is valid since the first time this buffer is appended
-    // with data. Make sure there is data to be processed.
-    if (current_buffer == buffers_.end())
-      break;
-
-    scoped_refptr<AudioBuffer> buffer = *current_buffer;
-
-    int remaining_frames_in_buffer =
-        buffer->frame_count() - current_buffer_offset;
-
-    if (frames_to_skip > 0) {
-      // If there are frames to skip, do it first. May need to skip into
-      // subsequent buffers.
-      int skipped = std::min(remaining_frames_in_buffer, frames_to_skip);
-      current_buffer_offset += skipped;
-      frames_to_skip -= skipped;
-    } else {
-      // Find the right amount to copy from the current buffer. We shall copy no
-      // more than |frames| frames in total and each single step copies no more
-      // than the current buffer size.
-      int copied = std::min(frames - taken, remaining_frames_in_buffer);
-
-      // if |dest| is NULL, there's no need to copy.
-      if (dest) {
-        buffer->ReadFrames(copied, current_buffer_offset,
-                           dest_frame_offset + taken, dest);
-      }
-
-      // Increase total number of frames copied, which regulates when to end
-      // this loop.
-      taken += copied;
-
-      // We have read |copied| frames from the current buffer. Advance the
-      // offset.
-      current_buffer_offset += copied;
-    }
-
-    // Has the buffer has been consumed?
-    if (current_buffer_offset == buffer->frame_count()) {
-      // If we are at the last buffer, no more data to be copied, so stop.
-      BufferQueue::iterator next = current_buffer + 1;
-      if (next == buffers_.end())
-        break;
-
-      // Advances the iterator.
-      current_buffer = next;
-      current_buffer_offset = 0;
-    }
-  }
-
-  if (advance_position) {
-    // Update the appropriate values since |taken| frames have been copied out.
-    frames_ -= taken;
-    DCHECK_GE(frames_, 0);
-    DCHECK(current_buffer_ != buffers_.end() || frames_ == 0);
-
-    // Remove any buffers before the current buffer as there is no going
-    // backwards.
-    buffers_.erase(buffers_.begin(), current_buffer);
-    current_buffer_ = buffers_.begin();
-    current_buffer_offset_ = current_buffer_offset;
-  }
-
-  return taken;
-}
-
-}  // namespace media
-}  // namespace cobalt
diff --git a/src/starboard/shared/starboard/player/filter/audio_buffer_queue.h b/src/starboard/shared/starboard/player/filter/audio_buffer_queue.h
deleted file mode 100644
index 64162a4..0000000
--- a/src/starboard/shared/starboard/player/filter/audio_buffer_queue.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COBALT_MEDIA_BASE_AUDIO_BUFFER_QUEUE_H_
-#define COBALT_MEDIA_BASE_AUDIO_BUFFER_QUEUE_H_
-
-#include <deque>
-
-#include "base/basictypes.h"
-#include "cobalt/media/base/audio_buffer.h"
-#include "cobalt/media/base/media_export.h"
-
-namespace cobalt {
-namespace media {
-
-class AudioBus;
-
-// A queue of AudioBuffers to support reading of arbitrary chunks of a media
-// data source. Audio data can be copied into an AudioBus for output. The
-// current position can be forwarded to anywhere in the buffered data.
-//
-// This class is not inherently thread-safe. Concurrent access must be
-// externally serialized.
-class MEDIA_EXPORT AudioBufferQueue {
- public:
-  AudioBufferQueue();
-  ~AudioBufferQueue();
-
-  // Clears the buffer queue.
-  void Clear();
-
-  // Appends |buffer_in| to this queue.
-  void Append(const scoped_refptr<AudioBuffer>& buffer_in);
-
-  // Reads a maximum of |frames| frames into |dest| from the current position.
-  // Returns the number of frames read. The current position will advance by the
-  // amount of frames read. |dest_frame_offset| specifies a starting offset into
-  // |dest|. On each call, the frames are converted from their source format
-  // into the destination AudioBus.
-  int ReadFrames(int frames, int dest_frame_offset, AudioBus* dest);
-
-  // Copies up to |frames| frames from current position to |dest|. Returns
-  // number of frames copied. Doesn't advance current position. Starts at
-  // |source_frame_offset| from current position. |dest_frame_offset| specifies
-  // a starting offset into |dest|. On each call, the frames are converted from
-  // their source format into the destination AudioBus.
-  int PeekFrames(int frames,
-                 int source_frame_offset,
-                 int dest_frame_offset,
-                 AudioBus* dest);
-
-  // Moves the current position forward by |frames| frames. If |frames| exceeds
-  // frames available, the seek operation will fail.
-  void SeekFrames(int frames);
-
-  // Returns the number of frames buffered beyond the current position.
-  int frames() const { return frames_; }
-
- private:
-  // Definition of the buffer queue.
-  typedef std::deque<scoped_refptr<AudioBuffer> > BufferQueue;
-
-  // An internal method shared by ReadFrames() and SeekFrames() that actually
-  // does reading. It reads a maximum of |frames| frames into |dest|. Returns
-  // the number of frames read. The current position will be moved forward by
-  // the number of frames read if |advance_position| is set. If |dest| is NULL,
-  // only the current position will advance but no data will be copied.
-  // |source_frame_offset| can be used to skip frames before reading.
-  // |dest_frame_offset| specifies a starting offset into |dest|.
-  int InternalRead(int frames,
-                   bool advance_position,
-                   int source_frame_offset,
-                   int dest_frame_offset,
-                   AudioBus* dest);
-
-  BufferQueue::iterator current_buffer_;
-  BufferQueue buffers_;
-  int current_buffer_offset_;
-
-  // Number of frames available to be read in the buffer.
-  int frames_;
-
-  DISALLOW_COPY_AND_ASSIGN(AudioBufferQueue);
-};
-
-}  // namespace media
-}  // namespace cobalt
-
-#endif  // COBALT_MEDIA_BASE_AUDIO_BUFFER_QUEUE_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
index bb501f1..dadbe57 100644
--- a/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h
@@ -19,9 +19,9 @@
 
 #include "starboard/media.h"
 #include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/closure.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/input_buffer_internal.h"
-#include "starboard/shared/starboard/player/job_queue.h"
 #include "starboard/types.h"
 
 namespace starboard {
@@ -33,34 +33,58 @@
 // This class decodes encoded audio stream into playable audio data.
 class AudioDecoder {
  public:
+  typedef ::starboard::shared::starboard::player::Closure Closure;
+  typedef ::starboard::shared::starboard::player::DecodedAudio DecodedAudio;
+  typedef ::starboard::shared::starboard::player::InputBuffer InputBuffer;
+
   virtual ~AudioDecoder() {}
 
-  // Decode the encoded audio data stored in |input_buffer|.
-  virtual void Decode(const InputBuffer& input_buffer) = 0;
+  // Whenever the decoder produces a new output, it calls |output_cb| once and
+  // exactly once.  This notify the user to call Read() to acquire the next
+  // output.  The user is free to not call Read() immediately but it can expect
+  // that a further call of Read() returns valid output until Reset() is called.
+  // Note that |output_cb| is always called asynchronously on the calling job
+  // queue.
+  virtual void Initialize(const Closure& output_cb) = 0;
 
-  // Note that there won't be more input data unless Reset() is called.
+  // Decode the encoded audio data stored in |input_buffer|.  Whenever the input
+  // is consumed and the decoder is ready to accept a new input, it calls
+  // |consumed_cb|.
+  // Note that |consumed_cb| is always called asynchronously on the calling job
+  // queue.
+  virtual void Decode(const InputBuffer& input_buffer,
+                      const Closure& consumed_cb) = 0;
+
+  // Notice the object that there is no more input data unless Reset() is
+  // called.
   virtual void WriteEndOfStream() = 0;
 
-  // Try to read the next decoded audio buffer.  If there is no decoded audio
-  // available currently, it returns NULL.  If the audio stream reaches EOS and
-  // there is no more decoded audio available, it returns an EOS buffer.
+  // Try to read the next decoded audio buffer.  If the audio stream reaches EOS
+  // and there is no more decoded audio available, it returns an EOS buffer.  It
+  // should only be called when |output_cb| is called and will always return a
+  // valid buffer containing audio data or signals and EOS.
+  // Note that there may not be a one-to-one relationship between the decoded
+  // audio and the input data passed in via Decode().  The decoder may break or
+  // combine multiple decoded audio access units into one.  The implementation
+  // has to ensure that the particular resampler can handle such combined access
+  // units as input.
   virtual scoped_refptr<DecodedAudio> Read() = 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.
+  // 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
+  // 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 storage type of the decoded pcm data.
+  virtual SbMediaAudioFrameStorageType GetStorageType() 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;
-
-  // Return whether the decoder can accept more data or not.
-  virtual bool CanAcceptMoreData() const = 0;
 };
 
 }  // namespace filter
diff --git a/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h b/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h
new file mode 100644
index 0000000..97bae5b
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_frame_tracker.h
@@ -0,0 +1,112 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FRAME_TRACKER_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_FRAME_TRACKER_H_
+
+#include <queue>
+
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// This class helps on tracking how many audio frames have been played with
+// playback rate taking into account.
+//
+// For example, when playback rate is set to 2.0 and 20 frames have been played,
+// the adjusted played frame will be 40.
+class AudioFrameTracker {
+ public:
+  AudioFrameTracker() { Reset(); }
+
+  // Reset the class to its initial state.  In this state there is no frames
+  // tracked and the playback frames is 0.
+  void Reset() {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    while (!frame_records_.empty()) {
+      frame_records_.pop();
+    }
+    frames_played_adjusted_to_playback_rate_ = 0;
+  }
+
+  void AddFrames(int number_of_frames, double playback_rate) {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    if (number_of_frames == 0) {
+      return;
+    }
+    SB_DCHECK(playback_rate > 0);
+
+    if (frame_records_.empty() ||
+        frame_records_.back().playback_rate != playback_rate) {
+      FrameRecord record = {number_of_frames, playback_rate};
+      frame_records_.push(record);
+    } else {
+      frame_records_.back().number_of_frames += number_of_frames;
+    }
+  }
+
+  void RecordPlayedFrames(int number_of_frames) {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    while (number_of_frames > 0 && !frame_records_.empty()) {
+      FrameRecord& record = frame_records_.front();
+      if (record.number_of_frames > number_of_frames) {
+        frames_played_adjusted_to_playback_rate_ +=
+            static_cast<int>(number_of_frames * record.playback_rate);
+        record.number_of_frames -= number_of_frames;
+        number_of_frames = 0;
+      } else {
+        number_of_frames -= record.number_of_frames;
+        frames_played_adjusted_to_playback_rate_ +=
+            static_cast<int>(record.number_of_frames * record.playback_rate);
+        frame_records_.pop();
+      }
+    }
+    SB_DCHECK(number_of_frames == 0)
+        << number_of_frames << " " << frame_records_.size();
+  }
+
+  SbMediaTime GetFramePlayedAdjustedToPlaybackRate() const {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    return frames_played_adjusted_to_playback_rate_;
+  }
+
+ private:
+  struct FrameRecord {
+    int number_of_frames;
+    double playback_rate;
+  };
+
+  ThreadChecker thread_checker_;
+  std::queue<FrameRecord> frame_records_;
+  int frames_played_adjusted_to_playback_rate_;
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_FRAME_TRACKER_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
index dac6b06..1251d5d 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -18,7 +18,7 @@
 
 #include "starboard/memory.h"
 #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
-#include "starboard/shared/starboard/player/closure.h"
+#include "starboard/shared/starboard/media/media_util.h"
 
 namespace starboard {
 namespace shared {
@@ -28,41 +28,69 @@
 
 namespace {
 
-const SbTime kEndOfStreamWrittenUpdateInterval = 5 * kSbTimeMillisecond;
+// This class works only when the input format and output format are the same.
+// It allows for a simplified AudioRendererImpl implementation by always using a
+// resampler.
+class IdentityAudioResampler : public AudioResampler {
+ public:
+  IdentityAudioResampler() : eos_reached_(false) {}
+  scoped_refptr<DecodedAudio> Resample(
+      const scoped_refptr<DecodedAudio>& audio_data) SB_OVERRIDE {
+    SB_DCHECK(!eos_reached_);
+
+    return audio_data;
+  }
+  scoped_refptr<DecodedAudio> WriteEndOfStream() SB_OVERRIDE {
+    SB_DCHECK(!eos_reached_);
+    eos_reached_ = true;
+    return new DecodedAudio();
+  }
+
+ private:
+  bool eos_reached_;
+};
+
+// AudioRendererImpl uses AudioTimeStretcher internally to adjust to playback
+// rate and AudioTimeStretcher can only process float32 samples.  So we try to
+// use kSbMediaAudioSampleTypeFloat32 and only use kSbMediaAudioSampleTypeInt16
+// when float32 is not supported.  To use kSbMediaAudioSampleTypeFloat32 will
+// cause an extra conversion from float32 to int16 before the samples are sent
+// to the audio sink.
+SbMediaAudioSampleType GetSinkAudioSampleType() {
+  return SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)
+             ? kSbMediaAudioSampleTypeFloat32
+             : kSbMediaAudioSampleTypeInt16;
+}
 
 }  // namespace
 
-AudioRendererImpl::AudioRendererImpl(JobQueue* job_queue,
-                                     scoped_ptr<AudioDecoder> decoder,
+AudioRendererImpl::AudioRendererImpl(scoped_ptr<AudioDecoder> decoder,
                                      const SbMediaAudioHeader& audio_header)
-    : job_queue_(job_queue),
+    : eos_state_(kEOSNotReceived),
       channels_(audio_header.number_of_channels),
-      bytes_per_frame_(
-          (decoder->GetSampleType() == kSbMediaAudioSampleTypeInt16 ? 2 : 4) *
-          channels_),
+      sink_sample_type_(GetSinkAudioSampleType()),
+      bytes_per_frame_(media::GetBytesPerSample(sink_sample_type_) * channels_),
       playback_rate_(1.0),
       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),
+      frames_sent_to_sink_(0),
+      pending_decoder_outputs_(0),
+      frames_consumed_by_sink_(0),
       frames_consumed_set_at_(SbTimeGetMonotonicNow()),
-      end_of_stream_written_(false),
-      end_of_stream_decoded_(false),
       decoder_(decoder.Pass()),
       audio_sink_(kSbAudioSinkInvalid),
+      can_accept_more_data_(true),
+      process_audio_data_scheduled_(false),
       decoder_needs_full_reset_(false) {
-  SB_DCHECK(job_queue != NULL);
   SB_DCHECK(decoder_ != NULL);
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
   frame_buffers_[0] = &frame_buffer_[0];
 
-// TODO: The audio sink on Android is currently broken on certain devices,
-// which causes all of playback to hang.  Log it for now, so we can tell
-// when it happens, but this should be removed once the sink is fixed.
+// TODO: The audio sink on Android is currently broken on certain devices, which
+// causes all of playback to hang.  Log it for now, so we can tell when it
+// happens, but this should be removed once the sink is fixed.
 #if defined(NDEBUG)
   const bool kLogFramesConsumed = false;
 #else
@@ -71,298 +99,384 @@
   if (kLogFramesConsumed) {
     log_frames_consumed_closure_ =
         Bind(&AudioRendererImpl::LogFramesConsumed, this);
-    job_queue_->Schedule(log_frames_consumed_closure_, kSbTimeSecond);
+    Schedule(log_frames_consumed_closure_, kSbTimeSecond);
   }
+
+  decoder_->Initialize(Bind(&AudioRendererImpl::OnDecoderOutput, this));
+
+  int source_sample_rate = decoder_->GetSamplesPerSecond();
+  int destination_sample_rate =
+      SbAudioSinkGetNearestSupportedSampleFrequency(source_sample_rate);
+  time_stretcher_.Initialize(channels_, destination_sample_rate);
 }
 
 AudioRendererImpl::~AudioRendererImpl() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
   if (audio_sink_ != kSbAudioSinkInvalid) {
     SbAudioSinkDestroy(audio_sink_);
   }
-
-  if (read_from_decoder_closure_.is_valid()) {
-    job_queue_->Remove(read_from_decoder_closure_);
-  }
-
-  if (log_frames_consumed_closure_.is_valid()) {
-    job_queue_->Remove(log_frames_consumed_closure_);
-  }
 }
 
 void AudioRendererImpl::WriteSample(const InputBuffer& input_buffer) {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(can_accept_more_data_);
 
-  if (end_of_stream_written_) {
+  if (eos_state_.load() >= kEOSWrittenToDecoder) {
     SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts()
                   << " after EOS reached.";
     return;
   }
 
-  decoder_->Decode(input_buffer);
+  can_accept_more_data_ = false;
 
-  ScopedLock lock(mutex_);
+  decoder_->Decode(input_buffer,
+                   Bind(&AudioRendererImpl::OnDecoderConsumed, this));
   decoder_needs_full_reset_ = true;
-  if (!read_from_decoder_closure_.is_valid()) {
-    read_from_decoder_closure_ =
-        Bind(&AudioRendererImpl::ReadFromDecoder, this);
-    job_queue_->Schedule(read_from_decoder_closure_);
-  }
 }
 
 void AudioRendererImpl::WriteEndOfStream() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
+  // TODO: Check |can_accept_more_data_| and make WriteEndOfStream() depend on
+  // CanAcceptMoreData() or callback.
+  // SB_DCHECK(can_accept_more_data_);
+  // can_accept_more_data_ = false;
 
-  SB_LOG_IF(WARNING, end_of_stream_written_)
-      << "Try to write EOS after EOS is reached";
-  if (end_of_stream_written_) {
+  if (eos_state_.load() >= kEOSWrittenToDecoder) {
+    SB_LOG(ERROR) << "Try to write EOS after EOS is reached";
     return;
   }
 
   decoder_->WriteEndOfStream();
 
-  ScopedLock lock(mutex_);
-  end_of_stream_written_ = true;
+  eos_state_.store(kEOSWrittenToDecoder);
   decoder_needs_full_reset_ = true;
-  // 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 AudioRendererImpl::Play() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
-  ScopedLock lock(mutex_);
-  paused_ = false;
+  paused_.store(false);
 }
 
 void AudioRendererImpl::Pause() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
-  ScopedLock lock(mutex_);
-  paused_ = true;
+  paused_.store(true);
 }
 
 #if SB_API_VERSION >= 4
 void AudioRendererImpl::SetPlaybackRate(double playback_rate) {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
   playback_rate_ = playback_rate;
 
   if (audio_sink_) {
-    audio_sink_->SetPlaybackRate(playback_rate);
+    // TODO: Remove SetPlaybackRate() support from audio sink as it only need to
+    // support play/pause.
+    audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
   }
 }
 #endif  // SB_API_VERSION >= 4
 
 void AudioRendererImpl::Seek(SbMediaTime seek_to_pts) {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
   SB_DCHECK(seek_to_pts >= 0);
 
   SbAudioSinkDestroy(audio_sink_);
+
   // Now the sink is destroyed and the callbacks will no longer be called, so
   // the following modifications are safe without lock.
   audio_sink_ = kSbAudioSinkInvalid;
 
+  if (resampler_) {
+    resampler_.reset();
+    time_stretcher_.FlushBuffers();
+  }
+
+  eos_state_.store(kEOSNotReceived);
   seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
-  seeking_ = true;
-  frames_in_buffer_ = 0;
-  offset_in_frames_ = 0;
-  frames_consumed_ = 0;
-  frames_consumed_set_at_ = SbTimeGetMonotonicNow();
-  end_of_stream_written_ = false;
-  end_of_stream_decoded_ = false;
-  pending_decoded_audio_ = NULL;
+  seeking_.store(true);
+  frames_sent_to_sink_.store(0);
+  frames_consumed_by_sink_.store(0);
+  frames_consumed_by_sink_since_last_get_current_time_.store(0);
+  pending_decoder_outputs_ = 0;
+  audio_frame_tracker_.Reset();
+  frames_consumed_set_at_.store(SbTimeGetMonotonicNow());
+  can_accept_more_data_ = true;
+  process_audio_data_scheduled_ = false;
 
   if (decoder_needs_full_reset_) {
     decoder_->Reset();
     decoder_needs_full_reset_ = false;
   }
+
+  CancelPendingJobs();
+
+  if (log_frames_consumed_closure_.is_valid()) {
+    Schedule(log_frames_consumed_closure_, kSbTimeSecond);
+  }
 }
 
 bool AudioRendererImpl::IsEndOfStreamPlayed() const {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
-  ScopedLock lock(mutex_);
-  return end_of_stream_decoded_ && frames_in_buffer_ == 0;
+  return eos_state_.load() >= kEOSSentToSink &&
+         frames_sent_to_sink_.load() == frames_consumed_by_sink_.load();
 }
 
 bool AudioRendererImpl::CanAcceptMoreData() const {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
-  {
-    ScopedLock lock(mutex_);
-    if (end_of_stream_written_) {
-      return false;
-    }
-  }
-  return decoder_->CanAcceptMoreData();
+  return eos_state_.load() == kEOSNotReceived && can_accept_more_data_ &&
+         !time_stretcher_.IsQueueFull();
 }
 
 bool AudioRendererImpl::IsSeekingInProgress() const {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
-  return seeking_;
+  SB_DCHECK(BelongsToCurrentThread());
+  return seeking_.load();
 }
 
 SbMediaTime AudioRendererImpl::GetCurrentTime() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  SB_DCHECK(BelongsToCurrentThread());
 
-  if (seeking_) {
+  if (seeking_.load()) {
     return seeking_to_pts_;
   }
+
+  audio_frame_tracker_.RecordPlayedFrames(
+      frames_consumed_by_sink_since_last_get_current_time_.exchange(0));
+
   return seeking_to_pts_ +
-         frames_consumed_ * kSbMediaTimeSecond /
-             decoder_->GetSamplesPerSecond();
+         audio_frame_tracker_.GetFramePlayedAdjustedToPlaybackRate() *
+             kSbMediaTimeSecond / decoder_->GetSamplesPerSecond();
+}
+
+void AudioRendererImpl::CreateAudioSinkAndResampler() {
+  int source_sample_rate = decoder_->GetSamplesPerSecond();
+  SbMediaAudioSampleType source_sample_type = decoder_->GetSampleType();
+  SbMediaAudioFrameStorageType source_storage_type = decoder_->GetStorageType();
+
+  int destination_sample_rate =
+      SbAudioSinkGetNearestSupportedSampleFrequency(source_sample_rate);
+
+  // AudioTimeStretcher only supports interleaved float32 samples.
+  if (source_sample_rate != destination_sample_rate ||
+      source_sample_type != kSbMediaAudioSampleTypeFloat32 ||
+      source_storage_type != kSbMediaAudioFrameStorageTypeInterleaved) {
+    resampler_ = AudioResampler::Create(
+        decoder_->GetSampleType(), decoder_->GetStorageType(),
+        source_sample_rate, kSbMediaAudioSampleTypeFloat32,
+        kSbMediaAudioFrameStorageTypeInterleaved, destination_sample_rate,
+        channels_);
+    SB_DCHECK(resampler_);
+  } else {
+    resampler_.reset(new IdentityAudioResampler);
+  }
+
+  // TODO: Support planar only audio sink.
+  audio_sink_ = SbAudioSinkCreate(
+      channels_, destination_sample_rate, sink_sample_type_,
+      kSbMediaAudioFrameStorageTypeInterleaved,
+      reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_),
+      kMaxCachedFrames, &AudioRendererImpl::UpdateSourceStatusFunc,
+      &AudioRendererImpl::ConsumeFramesFunc, this);
+  SB_DCHECK(SbAudioSinkIsValid(audio_sink_));
+
+#if SB_API_VERSION >= 4
+  // TODO: Remove SetPlaybackRate() support from audio sink as it only need to
+  // support play/pause.
+  audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
+#endif  // SB_API_VERSION >= 4
 }
 
 void AudioRendererImpl::UpdateSourceStatus(int* frames_in_buffer,
                                            int* offset_in_frames,
                                            bool* is_playing,
                                            bool* is_eos_reached) {
-  ScopedLock lock(mutex_);
+  *is_eos_reached = eos_state_.load() >= kEOSSentToSink;
 
-  *is_eos_reached = end_of_stream_decoded_;
+  *is_playing = !paused_.load() && !seeking_.load();
 
-  if (!end_of_stream_decoded_ && !read_from_decoder_closure_.is_valid()) {
-    read_from_decoder_closure_ =
-        Bind(&AudioRendererImpl::ReadFromDecoder, this);
-    if (paused_ || seeking_) {
-      job_queue_->Schedule(read_from_decoder_closure_, 10 * kSbTimeMillisecond);
-    } else {
-      job_queue_->Schedule(read_from_decoder_closure_);
-    }
-  }
-
-  if (paused_ || seeking_) {
-    *is_playing = false;
+  if (*is_playing) {
+    *frames_in_buffer =
+        frames_sent_to_sink_.load() - frames_consumed_by_sink_.load();
+    *offset_in_frames = frames_consumed_by_sink_.load() % kMaxCachedFrames;
+  } else {
     *frames_in_buffer = *offset_in_frames = 0;
-    return;
   }
-
-  *is_playing = true;
-  *frames_in_buffer = frames_in_buffer_;
-  *offset_in_frames = offset_in_frames_;
 }
 
 void AudioRendererImpl::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;
-  frames_consumed_set_at_ = SbTimeGetMonotonicNow();
+  frames_consumed_by_sink_.fetch_add(frames_consumed);
+  SB_DCHECK(frames_consumed_by_sink_.load() <= frames_sent_to_sink_.load());
+  frames_consumed_by_sink_since_last_get_current_time_.fetch_add(
+      frames_consumed);
+  frames_consumed_set_at_.store(SbTimeGetMonotonicNow());
 }
 
 void AudioRendererImpl::LogFramesConsumed() {
   SbTimeMonotonic time_since =
-      SbTimeGetMonotonicNow() - frames_consumed_set_at_;
+      SbTimeGetMonotonicNow() - frames_consumed_set_at_.load();
   if (time_since > kSbTimeSecond) {
     SB_DLOG(WARNING) << "|frames_consumed_| has not been updated for "
                      << (time_since / kSbTimeSecond) << "."
                      << ((time_since / (kSbTimeSecond / 10)) % 10)
-                     << " seconds, and |pending_decoded_audio_| is "
-                     << (!!pending_decoded_audio_ ? "" : "not ") << "ready.";
+                     << " seconds";
   }
-  job_queue_->Schedule(log_frames_consumed_closure_, kSbTimeSecond);
+  Schedule(log_frames_consumed_closure_, kSbTimeSecond);
 }
 
-// Try to read some audio data from the decoder.  Note that this operation is
-// valid across seeking.  If a seek happens immediately after a ReadFromDecoder
-// request is scheduled, the seek will reset the decoder.  So the
-// ReadFromDecoder request will not read stale data.
-void AudioRendererImpl::ReadFromDecoder() {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+void AudioRendererImpl::OnDecoderConsumed() {
+  SB_DCHECK(BelongsToCurrentThread());
 
-  ScopedLock lock(mutex_);
-  SB_DCHECK(read_from_decoder_closure_.is_valid());
-  read_from_decoder_closure_.reset();
+  // TODO: Unify EOS and non EOS request once WriteEndOfStream() depends on
+  // CanAcceptMoreData().
+  if (eos_state_.load() == kEOSNotReceived) {
+    SB_DCHECK(!can_accept_more_data_);
 
-  // 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, &AudioRendererImpl::UpdateSourceStatusFunc,
-        &AudioRendererImpl::ConsumeFramesFunc, this);
-#if SB_API_VERSION >= 4
-    audio_sink_->SetPlaybackRate(playback_rate_);
-#endif  // SB_API_VERSION >= 4
+    can_accept_more_data_ = true;
   }
+}
 
-  scoped_refptr<DecodedAudio> decoded_audio;
-  if (pending_decoded_audio_) {
-    decoded_audio = pending_decoded_audio_;
-  } else {
-    decoded_audio = decoder_->Read();
-  }
-  pending_decoded_audio_ = NULL;
-  if (!decoded_audio) {
+void AudioRendererImpl::OnDecoderOutput() {
+  SB_DCHECK(BelongsToCurrentThread());
+
+  ++pending_decoder_outputs_;
+
+  if (process_audio_data_scheduled_) {
+    // A ProcessAudioData() callback has been scheduled and we should let it
+    // process the output.
     return;
   }
 
-  if (decoded_audio->is_end_of_stream()) {
-    SB_DCHECK(end_of_stream_written_);
-    end_of_stream_decoded_ = true;
-    return;
+  process_audio_data_scheduled_ = true;
+  ProcessAudioData();
+}
+
+void AudioRendererImpl::ProcessAudioData() {
+  process_audio_data_scheduled_ = false;
+
+  if (!SbAudioSinkIsValid(audio_sink_)) {
+    CreateAudioSinkAndResampler();
   }
 
-  if (seeking_) {
-    if (decoded_audio->pts() < seeking_to_pts_) {
-      // Discard any audio data before the seeking target.
+  // Loop until no audio is appended, i.e. AppendAudioToFrameBuffer() returns
+  // false.
+  while (AppendAudioToFrameBuffer()) {
+  }
+
+  while (pending_decoder_outputs_ > 0) {
+    if (time_stretcher_.IsQueueFull()) {
+      // There is no room to do any further processing, schedule the function
+      // again for a later time.  The delay time is 1/4 of the buffer size.
+      const SbTimeMonotonic delay = kMaxCachedFrames * kSbTimeSecond /
+                                    decoder_->GetSamplesPerSecond() / 4;
+      process_audio_data_scheduled_ = true;
+      Schedule(Bind(&AudioRendererImpl::ProcessAudioData, this), delay);
       return;
     }
+
+    scoped_refptr<DecodedAudio> resampled_audio;
+    scoped_refptr<DecodedAudio> decoded_audio = decoder_->Read();
+
+    SB_DCHECK(decoded_audio);
+    --pending_decoder_outputs_;
+
+    if (decoded_audio->is_end_of_stream()) {
+      SB_DCHECK(eos_state_.load() == kEOSWrittenToDecoder) << eos_state_.load();
+      eos_state_.store(kEOSDecoded);
+      seeking_.store(false);
+
+      resampled_audio = resampler_->WriteEndOfStream();
+    } else {
+      // Discard any audio data before the seeking target.
+      if (seeking_.load() && decoded_audio->pts() < seeking_to_pts_) {
+        continue;
+      }
+
+      resampled_audio = resampler_->Resample(decoded_audio);
+    }
+
+    if (resampled_audio->size() > 0) {
+      time_stretcher_.EnqueueBuffer(resampled_audio);
+    }
+
+    // Loop until no audio is appended, i.e. AppendAudioToFrameBuffer() returns
+    // false.
+    while (AppendAudioToFrameBuffer()) {
+    }
   }
 
-  if (!AppendDecodedAudio_Locked(decoded_audio)) {
-    pending_decoded_audio_ = decoded_audio;
-    return;
-  }
-
-  if (seeking_ && frame_buffer_.size() > kPrerollFrames * bytes_per_frame_) {
-    seeking_ = false;
+  int64_t frames_in_buffer =
+      frames_sent_to_sink_.load() - frames_consumed_by_sink_.load();
+  if (kMaxCachedFrames - frames_in_buffer < kFrameAppendUnit &&
+      eos_state_.load() < kEOSSentToSink) {
+    // There are still audio data not appended so schedule a callback later.
+    SbTimeMonotonic delay = 0;
+    if (kMaxCachedFrames - frames_in_buffer < kMaxCachedFrames / 4) {
+      int frames_to_delay =
+          kMaxCachedFrames / 4 - (kMaxCachedFrames - frames_in_buffer);
+      delay = frames_to_delay * kSbTimeSecond / decoder_->GetSamplesPerSecond();
+    }
+    process_audio_data_scheduled_ = true;
+    Schedule(Bind(&AudioRendererImpl::ProcessAudioData, this), delay);
   }
 }
 
-// TODO: This function should be executed when lock is not acquired as it copies
-// relatively large amount of data.
-bool AudioRendererImpl::AppendDecodedAudio_Locked(
-    const scoped_refptr<DecodedAudio>& decoded_audio) {
-  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+bool AudioRendererImpl::AppendAudioToFrameBuffer() {
+  SB_DCHECK(BelongsToCurrentThread());
 
-  const uint8_t* source_buffer = decoded_audio->buffer();
-  int frames_to_append = decoded_audio->size() / bytes_per_frame_;
+  int frames_in_buffer =
+      frames_sent_to_sink_.load() - frames_consumed_by_sink_.load();
 
-  if (frames_in_buffer_ + frames_to_append > kMaxCachedFrames) {
+  if (kMaxCachedFrames - frames_in_buffer < kFrameAppendUnit) {
     return false;
   }
 
-  int offset_to_append =
-      (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames;
+  int offset_to_append = frames_sent_to_sink_.load() % kMaxCachedFrames;
+
+  // When |playback_rate_| is 0, try to fill the buffer with playback rate as 1.
+  // Otherwise the preroll will never finish.
+  float playback_rate_to_fill = playback_rate_ == 0.f ? 1.f : playback_rate_;
+  scoped_refptr<DecodedAudio> decoded_audio =
+      time_stretcher_.Read(kFrameAppendUnit, playback_rate_to_fill);
+  SB_DCHECK(decoded_audio);
+  if (decoded_audio->frames() == 0 && eos_state_.load() == kEOSDecoded) {
+    eos_state_.store(kEOSSentToSink);
+  }
+  audio_frame_tracker_.AddFrames(decoded_audio->frames(),
+                                 playback_rate_to_fill);
+  // TODO: Support kSbMediaAudioFrameStorageTypePlanar.
+  decoded_audio->SwitchFormatTo(sink_sample_type_,
+                                kSbMediaAudioFrameStorageTypeInterleaved);
+  const uint8_t* source_buffer = decoded_audio->buffer();
+  int frames_to_append = decoded_audio->frames();
+  int frames_appended = 0;
+
   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;
+    frames_appended += 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;
+  frames_appended += frames_to_append;
 
-  return true;
+  frames_sent_to_sink_.fetch_add(frames_appended);
+
+  int64_t preroll_frames =
+      decoder_->GetSamplesPerSecond() * kPrerollTime / kSbTimeSecond;
+  if (seeking_.load() && frames_sent_to_sink_.load() > preroll_frames) {
+    seeking_.store(false);
+  }
+
+  return frames_appended > 0;
 }
 
 // static
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
index ac90e0a..f908fd8 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
@@ -17,16 +17,19 @@
 
 #include <vector>
 
+#include "starboard/atomic.h"
 #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/closure.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_frame_tracker.h"
 #include "starboard/shared/starboard/player/filter/audio_renderer_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_resampler.h"
+#include "starboard/shared/starboard/player/filter/audio_time_stretcher.h"
 #include "starboard/shared/starboard/player/input_buffer_internal.h"
 #include "starboard/shared/starboard/player/job_queue.h"
 #include "starboard/types.h"
@@ -39,10 +42,13 @@
 
 // A default implementation of |AudioRenderer| that only depends on the
 // |AudioDecoder| interface, rather than a platform specific implementation.
-class AudioRendererImpl : public AudioRenderer {
+class AudioRendererImpl : public AudioRenderer, private JobQueue::JobOwner {
  public:
-  AudioRendererImpl(JobQueue* job_queue,
-                    scoped_ptr<AudioDecoder> decoder,
+  // Preroll is considered as finished after either the amount of audio caches
+  // exceeds kPrerollTime or if EOS is reached.
+  static const size_t kPrerollTime = kSbTimeSecond / 4;
+
+  AudioRendererImpl(scoped_ptr<AudioDecoder> decoder,
                     const SbMediaAudioHeader& audio_header);
   ~AudioRendererImpl() SB_OVERRIDE;
 
@@ -57,7 +63,7 @@
   void Seek(SbMediaTime seek_to_pts) SB_OVERRIDE;
 
   bool IsEndOfStreamWritten() const SB_OVERRIDE {
-    return end_of_stream_written_;
+    return eos_state_.load() >= kEOSWrittenToDecoder;
   };
   bool IsEndOfStreamPlayed() const SB_OVERRIDE;
   bool CanAcceptMoreData() const SB_OVERRIDE;
@@ -65,15 +71,23 @@
   SbMediaTime GetCurrentTime() SB_OVERRIDE;
 
  private:
-  // Preroll considered finished after either kPrerollFrames is cached or EOS
-  // is reached.
-  static const size_t kPrerollFrames = 64 * 1024;
+  enum EOSState {
+    kEOSNotReceived,
+    kEOSWrittenToDecoder,
+    kEOSDecoded,
+    kEOSSentToSink
+  };
+
   // 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.
+  // 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;
+  // The audio renderer tries to append |kAppendFrameUnit| frames every time to
+  // the sink buffer.
+  static const size_t kFrameAppendUnit = 16384;
 
+  void CreateAudioSinkAndResampler();
   void UpdateSourceStatus(int* frames_in_buffer,
                           int* offset_in_frames,
                           bool* is_playing,
@@ -81,9 +95,11 @@
   void ConsumeFrames(int frames_consumed);
   void LogFramesConsumed();
 
-  void ReadFromDecoder();
-  bool AppendDecodedAudio_Locked(
-      const scoped_refptr<DecodedAudio>& decoded_audio);
+  void OnDecoderConsumed();
+  void OnDecoderOutput();
+  void ProcessAudioData();
+  void FillResamplerAndTimeStretcher();
+  bool AppendAudioToFrameBuffer();
 
   // SbAudioSink callbacks
   static void UpdateSourceStatusFunc(int* frames_in_buffer,
@@ -93,32 +109,35 @@
                                      void* context);
   static void ConsumeFramesFunc(int frames_consumed, void* context);
 
-  JobQueue* job_queue_;
+  atomic_int32_t eos_state_;
   const int channels_;
+  const SbMediaAudioSampleType sink_sample_type_;
   const int bytes_per_frame_;
 
+  scoped_ptr<AudioResampler> resampler_;
+  AudioTimeStretcher time_stretcher_;
   double playback_rate_;
 
-  Mutex mutex_;
-  bool paused_;
-  bool seeking_;
+  atomic_bool paused_;
+  atomic_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_;
+  atomic_int64_t frames_sent_to_sink_;
+  atomic_int64_t frames_consumed_by_sink_;
+  atomic_int32_t frames_consumed_by_sink_since_last_get_current_time_;
 
-  int frames_consumed_;
-  SbTimeMonotonic frames_consumed_set_at_;
-  bool end_of_stream_written_;
-  bool end_of_stream_decoded_;
+  int32_t pending_decoder_outputs_;
+  AudioFrameTracker audio_frame_tracker_;
+  atomic_int64_t frames_consumed_set_at_;
   Closure log_frames_consumed_closure_;
 
   scoped_ptr<AudioDecoder> decoder_;
   SbAudioSink audio_sink_;
-  scoped_refptr<DecodedAudio> pending_decoded_audio_;
-  Closure read_from_decoder_closure_;
+
+  bool can_accept_more_data_;
+  bool process_audio_data_scheduled_;
 
   // Our owner will attempt to seek to pts 0 when playback begins.  In
   // general, seeking could require a full reset of the underlying decoder on
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc
new file mode 100644
index 0000000..5737d57
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc
@@ -0,0 +1,439 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_impl_internal.h"
+
+#include <set>
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.h"
+#include "starboard/memory.h"
+#include "starboard/shared/starboard/media/media_util.h"
+#include "starboard/shared/starboard/player/filter/mock_audio_decoder.h"
+#include "starboard/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+namespace testing {
+namespace {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+// TODO: Inject and test the renderer using SbAudioSink mock.  Otherwise the
+// tests rely on a correctly implemented audio sink and may fail on other audio
+// sinks.
+
+class AudioRendererImplTest : public ::testing::Test {
+ protected:
+  static const int kDefaultNumberOfChannels = 2;
+  static const int kDefaultSamplesPerSecond = 100000;
+  static const SbMediaAudioSampleType kDefaultAudioSampleType =
+      kSbMediaAudioSampleTypeFloat32;
+  static const SbMediaAudioFrameStorageType kDefaultAudioFrameStorageType =
+      kSbMediaAudioFrameStorageTypeInterleaved;
+
+  AudioRendererImplTest() {
+    ResetToFormat(kSbMediaAudioSampleTypeFloat32,
+                  kSbMediaAudioFrameStorageTypeInterleaved);
+  }
+
+  // This function should be called in the fixture before any other functions
+  // to set the desired format of the decoder.
+  void ResetToFormat(SbMediaAudioSampleType sample_type,
+                     SbMediaAudioFrameStorageType storage_type) {
+    audio_renderer_.reset(NULL);
+    sample_type_ = sample_type;
+    storage_type_ = storage_type;
+    audio_decoder_ = new MockAudioDecoder(sample_type_, storage_type_,
+                                          kDefaultSamplesPerSecond);
+    EXPECT_CALL(*audio_decoder_, Initialize(_))
+        .WillOnce(SaveArg<0>(&output_cb_));
+    audio_renderer_.reset(
+        new AudioRendererImpl(make_scoped_ptr<AudioDecoder>(audio_decoder_),
+                              GetDefaultAudioHeader()));
+  }
+
+  void WriteSample(InputBuffer input_buffer) {
+    ASSERT_TRUE(input_buffer.is_valid());
+    ASSERT_FALSE(consumed_cb_.is_valid());
+
+    buffers_in_decoder_.insert(input_buffer.data());
+    EXPECT_CALL(*audio_decoder_, Decode(input_buffer, _))
+        .WillOnce(SaveArg<1>(&consumed_cb_));
+    audio_renderer_->WriteSample(input_buffer);
+    job_queue_.RunUntilIdle();
+
+    ASSERT_TRUE(consumed_cb_.is_valid());
+  }
+
+  void WriteEndOfStream() {
+    EXPECT_CALL(*audio_decoder_, WriteEndOfStream());
+    audio_renderer_->WriteEndOfStream();
+    job_queue_.RunUntilIdle();
+  }
+
+  void Seek(SbMediaTime seek_to_pts) {
+    audio_renderer_->Seek(seek_to_pts);
+    job_queue_.RunUntilIdle();
+    EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
+  }
+
+  void CallConsumedCB() {
+    ASSERT_TRUE(consumed_cb_.is_valid());
+    consumed_cb_.Run();
+    consumed_cb_.reset();
+    job_queue_.RunUntilIdle();
+  }
+
+  void SendDecoderOutput(const scoped_refptr<DecodedAudio>& decoded_audio) {
+    ASSERT_TRUE(output_cb_.is_valid());
+
+    EXPECT_CALL(*audio_decoder_, Read()).WillOnce(Return(decoded_audio));
+    output_cb_.Run();
+    job_queue_.RunUntilIdle();
+  }
+
+  InputBuffer CreateInputBuffer(SbMediaTime pts) {
+    const int kInputBufferSize = 4;
+    return InputBuffer(kSbMediaTypeAudio, DeallocateSampleCB, NULL, this,
+                       SbMemoryAllocate(kInputBufferSize), kInputBufferSize,
+                       pts, NULL, NULL);
+  }
+
+  scoped_refptr<DecodedAudio> CreateDecodedAudio(SbMediaTime pts, int frames) {
+    scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+        kDefaultNumberOfChannels, sample_type_, storage_type_, pts,
+        frames * kDefaultNumberOfChannels *
+            media::GetBytesPerSample(sample_type_));
+    SbMemorySet(decoded_audio->buffer(), 0, decoded_audio->size());
+    return decoded_audio;
+  }
+
+  SbMediaAudioSampleType sample_type_;
+  SbMediaAudioFrameStorageType storage_type_;
+
+  JobQueue job_queue_;
+  std::set<const void*> buffers_in_decoder_;
+
+  Closure output_cb_;
+  Closure consumed_cb_;
+
+  scoped_ptr<AudioRenderer> audio_renderer_;
+  MockAudioDecoder* audio_decoder_;
+
+ private:
+  void OnDeallocateSample(const void* sample_buffer) {
+    ASSERT_TRUE(buffers_in_decoder_.find(sample_buffer) !=
+                buffers_in_decoder_.end());
+    buffers_in_decoder_.erase(buffers_in_decoder_.find(sample_buffer));
+    SbMemoryDeallocate(const_cast<void*>(sample_buffer));
+  }
+
+  static SbMediaAudioHeader GetDefaultAudioHeader() {
+    SbMediaAudioHeader audio_header = {};
+
+    audio_header.number_of_channels = kDefaultNumberOfChannels;
+    audio_header.samples_per_second = kDefaultSamplesPerSecond;
+    audio_header.bits_per_sample = 32;
+    audio_header.average_bytes_per_second = audio_header.samples_per_second *
+                                            audio_header.number_of_channels *
+                                            audio_header.bits_per_sample / 8;
+    audio_header.block_alignment = 4;
+    audio_header.audio_specific_config_size = 0;
+
+    return audio_header;
+  }
+
+  static void DeallocateSampleCB(SbPlayer player,
+                                 void* context,
+                                 const void* sample_buffer) {
+    AudioRendererImplTest* test = static_cast<AudioRendererImplTest*>(context);
+    EXPECT_TRUE(test != NULL);
+    test->OnDeallocateSample(sample_buffer);
+  }
+};
+
+TEST_F(AudioRendererImplTest, StateAfterConstructed) {
+  EXPECT_FALSE(audio_renderer_->IsEndOfStreamWritten());
+  EXPECT_FALSE(audio_renderer_->IsEndOfStreamPlayed());
+  EXPECT_TRUE(audio_renderer_->CanAcceptMoreData());
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+}
+
+TEST_F(AudioRendererImplTest, SunnyDay) {
+  Seek(0);
+
+  const int kFramesPerBuffer = 1024;
+
+  int frames_written = 0;
+  int preroll_frames = kDefaultSamplesPerSecond *
+                       AudioRendererImpl::kPrerollTime / kSbTimeSecond;
+
+  while (frames_written <= preroll_frames) {
+    SbMediaTime pts = frames_written / kDefaultSamplesPerSecond;
+    WriteSample(CreateInputBuffer(pts));
+    CallConsumedCB();
+    SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
+    frames_written += kFramesPerBuffer;
+  }
+
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  WriteEndOfStream();
+
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  audio_renderer_->Play();
+
+  SendDecoderOutput(new DecodedAudio);
+
+  SbMediaTime media_time = audio_renderer_->GetCurrentTime();
+
+  while (!audio_renderer_->IsEndOfStreamPlayed()) {
+    SbThreadSleep(AudioRendererImpl::kPrerollTime);
+    SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
+    EXPECT_GT(new_media_time, media_time);
+    media_time = new_media_time;
+  }
+}
+
+TEST_F(AudioRendererImplTest, SunnyDayWithDoublePlaybackRateAndInt16Samples) {
+  const int kPlaybackRate = 2;
+
+  ResetToFormat(kSbMediaAudioSampleTypeInt16,
+                kSbMediaAudioFrameStorageTypeInterleaved);
+  audio_renderer_->SetPlaybackRate(static_cast<float>(kPlaybackRate));
+
+  Seek(0);
+
+  const int kFramesPerBuffer = 1024;
+
+  int frames_written = 0;
+  int preroll_frames = kDefaultSamplesPerSecond *
+                       AudioRendererImpl::kPrerollTime / kSbTimeSecond *
+                       kPlaybackRate * 2;
+
+  while (frames_written <= preroll_frames) {
+    SbMediaTime pts = frames_written / kDefaultSamplesPerSecond;
+    WriteSample(CreateInputBuffer(pts));
+    CallConsumedCB();
+    SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
+    frames_written += kFramesPerBuffer;
+
+    if (!audio_renderer_->IsSeekingInProgress()) {
+      break;
+    }
+  }
+
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  WriteEndOfStream();
+
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  audio_renderer_->Play();
+
+  SendDecoderOutput(new DecodedAudio);
+
+  SbMediaTime media_time = audio_renderer_->GetCurrentTime();
+
+  while (!audio_renderer_->IsEndOfStreamPlayed()) {
+    SbThreadSleep(AudioRendererImpl::kPrerollTime);
+    SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
+    EXPECT_GT(new_media_time, media_time);
+    media_time = new_media_time;
+  }
+}
+
+TEST_F(AudioRendererImplTest, StartPlayBeforePreroll) {
+  Seek(0);
+
+  const int kFramesPerBuffer = 1024;
+
+  int frames_written = 0;
+  int preroll_frames = kDefaultSamplesPerSecond *
+                       AudioRendererImpl::kPrerollTime / kSbTimeSecond;
+
+  audio_renderer_->Play();
+
+  while (frames_written <= preroll_frames) {
+    SbMediaTime pts = frames_written / kDefaultSamplesPerSecond;
+    WriteSample(CreateInputBuffer(pts));
+    CallConsumedCB();
+    SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
+    frames_written += kFramesPerBuffer;
+  }
+
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  WriteEndOfStream();
+  SendDecoderOutput(new DecodedAudio);
+
+  SbMediaTime media_time = audio_renderer_->GetCurrentTime();
+
+  while (!audio_renderer_->IsEndOfStreamPlayed()) {
+    SbThreadSleep(AudioRendererImpl::kPrerollTime);
+    SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
+    EXPECT_GT(new_media_time, media_time);
+    media_time = new_media_time;
+  }
+}
+
+TEST_F(AudioRendererImplTest, DecoderReturnsEOSWithoutAnyData) {
+  Seek(0);
+
+  WriteSample(CreateInputBuffer(0));
+  CallConsumedCB();
+
+  WriteEndOfStream();
+
+  EXPECT_TRUE(audio_renderer_->IsEndOfStreamWritten());
+  EXPECT_FALSE(audio_renderer_->CanAcceptMoreData());
+  EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
+
+  // Return EOS from decoder without sending any audio data, which is valid.
+  SendDecoderOutput(new DecodedAudio);
+
+  EXPECT_TRUE(audio_renderer_->IsEndOfStreamPlayed());
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+}
+
+// Test decoders that take many input samples before returning any output.
+TEST_F(AudioRendererImplTest, DecoderConsumeAllInputBeforeReturningData) {
+  Seek(0);
+
+  for (int i = 0; i < 128; ++i) {
+    WriteSample(CreateInputBuffer(i));
+    CallConsumedCB();
+
+    if (!audio_renderer_->CanAcceptMoreData()) {
+      break;
+    }
+  }
+
+  // Send an EOS to "force" the decoder return data.
+  WriteEndOfStream();
+
+  EXPECT_TRUE(audio_renderer_->IsEndOfStreamWritten());
+  EXPECT_FALSE(audio_renderer_->CanAcceptMoreData());
+  EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
+
+  // Return EOS from decoder without sending any audio data, which is valid.
+  SendDecoderOutput(new DecodedAudio);
+
+  EXPECT_TRUE(audio_renderer_->IsEndOfStreamPlayed());
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+}
+
+TEST_F(AudioRendererImplTest, MoreNumberOfOuputBuffersThanInputBuffers) {
+  Seek(0);
+
+  const int kFramesPerBuffer = 1024;
+
+  int frames_written = 0;
+  int preroll_frames = kDefaultSamplesPerSecond *
+                       AudioRendererImpl::kPrerollTime / kSbTimeSecond;
+
+  while (frames_written <= preroll_frames) {
+    SbMediaTime pts = frames_written / kDefaultSamplesPerSecond;
+    WriteSample(CreateInputBuffer(pts));
+    CallConsumedCB();
+    SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2));
+    frames_written += kFramesPerBuffer / 2;
+    pts = frames_written / kDefaultSamplesPerSecond;
+    SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2));
+    frames_written += kFramesPerBuffer / 2;
+  }
+
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  WriteEndOfStream();
+
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  audio_renderer_->Play();
+
+  SendDecoderOutput(new DecodedAudio);
+
+  SbMediaTime media_time = audio_renderer_->GetCurrentTime();
+
+  while (!audio_renderer_->IsEndOfStreamPlayed()) {
+    SbThreadSleep(AudioRendererImpl::kPrerollTime);
+    SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
+    EXPECT_GT(new_media_time, media_time);
+    media_time = new_media_time;
+  }
+}
+
+TEST_F(AudioRendererImplTest, LessNumberOfOuputBuffersThanInputBuffers) {
+  Seek(0);
+
+  const int kFramesPerBuffer = 1024;
+
+  int frames_written = 0;
+  int preroll_frames = kDefaultSamplesPerSecond *
+                       AudioRendererImpl::kPrerollTime / kSbTimeSecond;
+
+  while (frames_written <= preroll_frames) {
+    SbMediaTime pts = frames_written / kDefaultSamplesPerSecond;
+    WriteSample(CreateInputBuffer(pts));
+    CallConsumedCB();
+    frames_written += kFramesPerBuffer;
+  }
+
+  SendDecoderOutput(CreateDecodedAudio(0, frames_written));
+
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  WriteEndOfStream();
+
+  EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
+  EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
+
+  audio_renderer_->Play();
+
+  SendDecoderOutput(new DecodedAudio);
+
+  SbMediaTime media_time = audio_renderer_->GetCurrentTime();
+
+  while (!audio_renderer_->IsEndOfStreamPlayed()) {
+    SbThreadSleep(AudioRendererImpl::kPrerollTime);
+    SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
+    EXPECT_GT(new_media_time, media_time);
+    media_time = new_media_time;
+  }
+}
+
+TEST_F(AudioRendererImplTest, Seek) {}
+
+}  // namespace
+}  // namespace testing
+}  // 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
index 816c959..ccb12a7 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -42,6 +42,7 @@
   virtual void Seek(SbMediaTime seek_to_pts) = 0;
   virtual bool IsEndOfStreamWritten() const = 0;
   virtual bool IsEndOfStreamPlayed() const = 0;
+  // TODO: Replace polling with callbacks.
   virtual bool CanAcceptMoreData() const = 0;
   virtual bool IsSeekingInProgress() const = 0;
   virtual SbMediaTime GetCurrentTime() = 0;
diff --git a/src/starboard/shared/starboard/player/filter/audio_resampler.h b/src/starboard/shared/starboard/player/filter/audio_resampler.h
new file mode 100644
index 0000000..c994fac
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_resampler.h
@@ -0,0 +1,76 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_RESAMPLER_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RESAMPLER_H_
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// Classes inherited from this interface can convert input audio samples in one
+// sample and storage type into another sample and storage type.  For example,
+// it can convert planar int16 samples into interleaved float32 samples.
+// All functions (including Create() and the dtor) of the class should be called
+// on the same thread as the JobQueue passed in the Create() function.
+// It doesn't have a function to reset its internal state so during a seek the
+// user of this class shoudld destroy and recreate it.
+class AudioResampler {
+ public:
+  typedef ::starboard::shared::starboard::player::DecodedAudio DecodedAudio;
+
+  virtual ~AudioResampler() {}
+
+  // Write frames to the AudioResampler.  The format of the frames is determined
+  // by the input formats passed to Create().
+  virtual scoped_refptr<DecodedAudio> Resample(
+      const scoped_refptr<DecodedAudio>& audio_data) = 0;
+
+  // Signal that the last audio input frame has been written.  The resampler
+  // should allow for reading of any audio data inside its internal cache.  The
+  // caller should continue call Read() after calling this function until Read()
+  // returns EOS.
+  virtual scoped_refptr<DecodedAudio> WriteEndOfStream() = 0;
+
+  // Create an AudioResampler that takes input specified by |source_*| and
+  // produce output specified by |destination_*|.  The input and output have to
+  // have the same number of channels which is specified in |channels|.
+  // The |output_cb| will be called whenever there is resampled data ready.  It
+  // is always called asynchronously on |job_queue| and then the user of
+  // AudioResampler can call Read() to read the next chunk of resampled data.
+  static scoped_ptr<AudioResampler> Create(
+      SbMediaAudioSampleType source_sample_type,
+      SbMediaAudioFrameStorageType source_storage_type,
+      int source_sample_rate,
+      SbMediaAudioSampleType destination_sample_type,
+      SbMediaAudioFrameStorageType destination_storage_type,
+      int destination_sample_rate,
+      int channels);
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RESAMPLER_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_resampler_impl.cc b/src/starboard/shared/starboard/player/filter/audio_resampler_impl.cc
new file mode 100644
index 0000000..4ad3330
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/audio_resampler_impl.cc
@@ -0,0 +1,86 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/log.h"
+#include "starboard/shared/starboard/player/filter/audio_resampler.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+namespace {
+
+// CPU based simple AudioResampler implementation.  Note that it currently only
+// supports resample audio between same storage type and same channels but with
+// different sample types.
+class AudioResamplerImpl : public AudioResampler {
+ public:
+  AudioResamplerImpl(SbMediaAudioSampleType source_sample_type,
+                     SbMediaAudioFrameStorageType source_storage_type,
+                     SbMediaAudioSampleType destination_sample_type,
+                     SbMediaAudioFrameStorageType destination_storage_type,
+                     int channels)
+      : source_sample_type_(source_sample_type),
+        source_storage_type_(source_storage_type),
+        destination_sample_type_(destination_sample_type),
+        destination_storage_type_(destination_storage_type),
+        channels_(channels) {}
+
+  scoped_refptr<DecodedAudio> Resample(
+      const scoped_refptr<DecodedAudio>& audio_data) SB_OVERRIDE {
+    SB_DCHECK(audio_data->sample_type() == source_sample_type_);
+    SB_DCHECK(audio_data->storage_type() == source_storage_type_);
+    SB_DCHECK(audio_data->channels() == channels_);
+    audio_data->SwitchFormatTo(destination_sample_type_,
+                               destination_storage_type_);
+    return audio_data;
+  }
+
+  scoped_refptr<DecodedAudio> WriteEndOfStream() SB_OVERRIDE {
+    return new DecodedAudio;
+  }
+
+ private:
+  SbMediaAudioSampleType source_sample_type_;
+  SbMediaAudioFrameStorageType source_storage_type_;
+  SbMediaAudioSampleType destination_sample_type_;
+  SbMediaAudioFrameStorageType destination_storage_type_;
+  int channels_;
+};
+
+}  // namespace
+
+// static
+scoped_ptr<AudioResampler> AudioResampler::Create(
+    SbMediaAudioSampleType source_sample_type,
+    SbMediaAudioFrameStorageType source_storage_type,
+    int source_sample_rate,
+    SbMediaAudioSampleType destination_sample_type,
+    SbMediaAudioFrameStorageType destination_storage_type,
+    int destination_sample_rate,
+    int channels) {
+  SB_DCHECK(source_sample_rate == destination_sample_rate);
+  return scoped_ptr<AudioResampler>(new AudioResamplerImpl(
+      source_sample_type, source_storage_type, destination_sample_type,
+      destination_storage_type, channels));
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.cc b/src/starboard/shared/starboard/player/filter/audio_time_stretcher.cc
similarity index 61%
rename from src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.cc
rename to src/starboard/shared/starboard/player/filter/audio_time_stretcher.cc
index 5143d7b..5d5b589 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_time_stretcher.cc
@@ -2,19 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "cobalt/media/filters/audio_renderer_algorithm.h"
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_time_stretcher.h"
 
 #include <algorithm>
 #include <cmath>
 
-#include "base/logging.h"
-#include "cobalt/media/base/audio_bus.h"
-#include "cobalt/media/base/limits.h"
-#include "cobalt/media/filters/wsola_internals.h"
+#include "starboard/log.h"
 #include "starboard/memory.h"
+#include "starboard/shared/starboard/player/filter/wsola_internal.h"
 
-namespace cobalt {
-namespace media {
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
 
 // Waveform Similarity Overlap-and-add (WSOLA).
 //
@@ -65,8 +80,9 @@
 // The minimum size in ms for the |audio_buffer_|. Arbitrarily determined.
 static const int kStartingCapacityInMs = 200;
 
-AudioRendererAlgorithm::AudioRendererAlgorithm()
+AudioTimeStretcher::AudioTimeStretcher()
     : channels_(0),
+      bytes_per_frame_(0),
       samples_per_second_(0),
       muted_partial_frame_(0),
       capacity_(0),
@@ -81,16 +97,14 @@
       initial_capacity_(0),
       max_capacity_(0) {}
 
-AudioRendererAlgorithm::~AudioRendererAlgorithm() {}
+AudioTimeStretcher::~AudioTimeStretcher() {}
 
-void AudioRendererAlgorithm::Initialize(const AudioParameters& params) {
-  CHECK(params.IsValid());
-
-  channels_ = params.channels();
-  samples_per_second_ = params.sample_rate();
+void AudioTimeStretcher::Initialize(int channels, int samples_per_second) {
+  channels_ = channels;
+  bytes_per_frame_ = sizeof(float) * channels_;
+  samples_per_second_ = samples_per_second;
   initial_capacity_ = capacity_ =
-      std::max(params.frames_per_buffer() * 2,
-               ConvertMillisecondsToFrames(kStartingCapacityInMs));
+      ConvertMillisecondsToFrames(kStartingCapacityInMs);
   max_capacity_ =
       std::max(initial_capacity_, kMaxCapacityInSeconds * samples_per_second_);
   num_candidate_blocks_ = ConvertMillisecondsToFrames(kWsolaSearchIntervalMs);
@@ -130,23 +144,40 @@
   internal::GetSymmetricHanningWindow(2 * ola_window_size_,
                                       transition_window_.get());
 
-  wsola_output_ = AudioBus::Create(channels_, ola_window_size_ + ola_hop_size_);
-  wsola_output_->Zero();  // Initialize for overlap-and-add of the first block.
+  wsola_output_ =
+      new DecodedAudio(channels_, kSbMediaAudioSampleTypeFloat32,
+                       kSbMediaAudioFrameStorageTypeInterleaved, 0,
+                       (ola_window_size_ + ola_hop_size_) * bytes_per_frame_);
+  // Initialize for overlap-and-add of the first block.
+  SbMemorySet(wsola_output_->buffer(), 0, wsola_output_->size());
 
   // Auxiliary containers.
-  optimal_block_ = AudioBus::Create(channels_, ola_window_size_);
-  search_block_ = AudioBus::Create(
-      channels_, num_candidate_blocks_ + (ola_window_size_ - 1));
-  target_block_ = AudioBus::Create(channels_, ola_window_size_);
+  optimal_block_ = new DecodedAudio(channels_, kSbMediaAudioSampleTypeFloat32,
+                                    kSbMediaAudioFrameStorageTypeInterleaved, 0,
+                                    ola_window_size_ * bytes_per_frame_);
+  search_block_ = new DecodedAudio(
+      channels_, kSbMediaAudioSampleTypeFloat32,
+      kSbMediaAudioFrameStorageTypeInterleaved, 0,
+      (num_candidate_blocks_ + (ola_window_size_ - 1)) * bytes_per_frame_);
+  target_block_ = new DecodedAudio(channels_, kSbMediaAudioSampleTypeFloat32,
+                                   kSbMediaAudioFrameStorageTypeInterleaved, 0,
+                                   ola_window_size_ * bytes_per_frame_);
 }
 
-int AudioRendererAlgorithm::FillBuffer(AudioBus* dest, int dest_offset,
-                                       int requested_frames,
-                                       double playback_rate) {
-  if (playback_rate == 0) return 0;
+scoped_refptr<DecodedAudio> AudioTimeStretcher::Read(int requested_frames,
+                                                     double playback_rate) {
+  SB_DCHECK(bytes_per_frame_ > 0);
+  SB_DCHECK(playback_rate >= 0);
 
-  DCHECK_GT(playback_rate, 0);
-  DCHECK_EQ(channels_, dest->channels());
+  scoped_refptr<DecodedAudio> dest =
+      new DecodedAudio(channels_, kSbMediaAudioSampleTypeFloat32,
+                       kSbMediaAudioFrameStorageTypeInterleaved, 0,
+                       requested_frames * bytes_per_frame_);
+
+  if (playback_rate == 0) {
+    dest->ShrinkTo(0);
+    return dest;
+  }
 
   // Optimize the muted case to issue a single clear instead of performing
   // the full crossfade and clearing each crossfaded frame.
@@ -164,7 +195,7 @@
     // audio_buffer_.frames()+1.
     int seek_frames = std::min(static_cast<int>(muted_partial_frame_),
                                audio_buffer_.frames());
-    dest->ZeroFramesPartial(dest_offset, frames_to_render);
+    SbMemorySet(dest->buffer(), 0, frames_to_render * bytes_per_frame_);
     audio_buffer_.SeekFrames(seek_frames);
 
     // Determine the partial frame that remains to be skipped for next call. If
@@ -173,7 +204,8 @@
     // another playback rate that mutes, the code will attempt to line up the
     // frames again.
     muted_partial_frame_ -= seek_frames;
-    return frames_to_render;
+    dest->ShrinkTo(frames_to_render * bytes_per_frame_);
+    return dest;
   }
 
   int slower_step = ceil(ola_window_size_ * playback_rate);
@@ -184,29 +216,29 @@
   if (ola_window_size_ <= faster_step && slower_step >= ola_window_size_) {
     const int frames_to_copy =
         std::min(audio_buffer_.frames(), requested_frames);
-    const int frames_read =
-        audio_buffer_.ReadFrames(frames_to_copy, dest_offset, dest);
-    DCHECK_EQ(frames_read, frames_to_copy);
-    return frames_read;
+    const int frames_read = audio_buffer_.ReadFrames(frames_to_copy, 0, dest);
+    SB_DCHECK(frames_read == frames_to_copy);
+    dest->ShrinkTo(frames_read * bytes_per_frame_);
+    return dest;
   }
 
   int rendered_frames = 0;
   do {
-    rendered_frames +=
-        WriteCompletedFramesTo(requested_frames - rendered_frames,
-                               dest_offset + rendered_frames, dest);
+    rendered_frames += WriteCompletedFramesTo(
+        requested_frames - rendered_frames, rendered_frames, dest);
   } while (rendered_frames < requested_frames &&
            RunOneWsolaIteration(playback_rate));
-  return rendered_frames;
+  dest->ShrinkTo(rendered_frames * bytes_per_frame_);
+  return dest;
 }
 
-void AudioRendererAlgorithm::FlushBuffers() {
+void AudioTimeStretcher::FlushBuffers() {
   // Clear the queue of decoded packets (releasing the buffers).
   audio_buffer_.Clear();
   output_time_ = 0.0;
   search_block_index_ = 0;
   target_block_index_ = 0;
-  wsola_output_->Zero();
+  SbMemorySet(wsola_output_->buffer(), 0, wsola_output_->size());
   num_complete_frames_ = 0;
 
   // Reset |capacity_| so growth triggered by underflows doesn't penalize seek
@@ -214,55 +246,57 @@
   capacity_ = initial_capacity_;
 }
 
-void AudioRendererAlgorithm::EnqueueBuffer(
-    const scoped_refptr<AudioBuffer>& buffer_in) {
-  DCHECK(!buffer_in->end_of_stream());
-  audio_buffer_.Append(buffer_in);
+void AudioTimeStretcher::EnqueueBuffer(
+    const scoped_refptr<DecodedAudio>& audio_data) {
+  SB_DCHECK(!audio_data->is_end_of_stream());
+  audio_buffer_.Append(audio_data);
 }
 
-bool AudioRendererAlgorithm::IsQueueFull() {
+bool AudioTimeStretcher::IsQueueFull() const {
   return audio_buffer_.frames() >= capacity_;
 }
 
-void AudioRendererAlgorithm::IncreaseQueueCapacity() {
-  DCHECK_LE(capacity_, max_capacity_);
-  capacity_ = std::min(2 * capacity_, max_capacity_);
-}
-
-int64_t AudioRendererAlgorithm::GetMemoryUsage() const {
-  return audio_buffer_.frames() * channels_ * sizeof(float);
-}
-
-bool AudioRendererAlgorithm::CanPerformWsola() const {
+// TODO: Make order of functions in .cc the same as order of functions in .h.
+bool AudioTimeStretcher::CanPerformWsola() const {
   const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
   const int frames = audio_buffer_.frames();
   return target_block_index_ + ola_window_size_ <= frames &&
          search_block_index_ + search_block_size <= frames;
 }
 
-int AudioRendererAlgorithm::ConvertMillisecondsToFrames(int ms) const {
-  return ms * (samples_per_second_ /
-               static_cast<double>(base::Time::kMillisecondsPerSecond));
+int AudioTimeStretcher::ConvertMillisecondsToFrames(int ms) const {
+  const double kMillsecondsPerSeconds =
+      static_cast<double>(kSbTimeSecond / kSbTimeMillisecond);
+  return ms * (samples_per_second_ / kMillsecondsPerSeconds);
 }
 
-bool AudioRendererAlgorithm::RunOneWsolaIteration(double playback_rate) {
+bool AudioTimeStretcher::RunOneWsolaIteration(double playback_rate) {
+  SB_DCHECK(bytes_per_frame_ > 0);
+
   if (!CanPerformWsola()) return false;
 
   GetOptimalBlock();
 
   // Overlap-and-add.
   for (int k = 0; k < channels_; ++k) {
-    const float* const ch_opt_frame = optimal_block_->channel(k);
-    float* ch_output = wsola_output_->channel(k) + num_complete_frames_;
+    const float* const ch_opt_frame =
+        reinterpret_cast<const float*>(optimal_block_->buffer()) + k;
+    float* ch_output = reinterpret_cast<float*>(wsola_output_->buffer()) + k +
+                       num_complete_frames_ * sizeof(float);
     for (int n = 0; n < ola_hop_size_; ++n) {
-      ch_output[n] = ch_output[n] * ola_window_[ola_hop_size_ + n] +
-                     ch_opt_frame[n] * ola_window_[n];
+      ch_output[n * channels_] =
+          ch_output[n * channels_] * ola_window_[ola_hop_size_ + n] +
+          ch_opt_frame[n * channels_] * ola_window_[n];
     }
-
-    // Copy the second half to the output.
-    SbMemoryCopy(&ch_output[ola_hop_size_], &ch_opt_frame[ola_hop_size_],
-                 sizeof(*ch_opt_frame) * ola_hop_size_);
   }
+  // Copy the second half to the output.
+  const float* const ch_opt_frame =
+      reinterpret_cast<const float*>(optimal_block_->buffer());
+  float* ch_output = reinterpret_cast<float*>(wsola_output_->buffer()) +
+                     num_complete_frames_ * sizeof(float);
+  SbMemoryCopy(&ch_output[ola_hop_size_ * channels_],
+               &ch_opt_frame[ola_hop_size_ * channels_],
+               sizeof(*ch_opt_frame) * ola_hop_size_ * channels_);
 
   num_complete_frames_ += ola_hop_size_;
   UpdateOutputTime(playback_rate, ola_hop_size_);
@@ -270,8 +304,8 @@
   return true;
 }
 
-void AudioRendererAlgorithm::UpdateOutputTime(double playback_rate,
-                                              double time_change) {
+void AudioTimeStretcher::UpdateOutputTime(double playback_rate,
+                                          double time_change) {
   output_time_ += time_change;
   // Center of the search region, in frames.
   const int search_block_center_index =
@@ -279,7 +313,7 @@
   search_block_index_ = search_block_center_index - search_block_center_offset_;
 }
 
-void AudioRendererAlgorithm::RemoveOldInputFrames(double playback_rate) {
+void AudioTimeStretcher::RemoveOldInputFrames(double playback_rate) {
   const int earliest_used_index =
       std::min(target_block_index_, search_block_index_);
   if (earliest_used_index <= 0) return;  // Nothing to remove.
@@ -291,31 +325,33 @@
   // Adjust output index.
   double output_time_change =
       static_cast<double>(earliest_used_index) / playback_rate;
-  CHECK_GE(output_time_, output_time_change);
+  SB_CHECK(output_time_ >= output_time_change);
   UpdateOutputTime(playback_rate, -output_time_change);
 }
 
-int AudioRendererAlgorithm::WriteCompletedFramesTo(int requested_frames,
-                                                   int dest_offset,
-                                                   AudioBus* dest) {
+int AudioTimeStretcher::WriteCompletedFramesTo(int requested_frames,
+                                               int dest_offset,
+                                               DecodedAudio* dest) {
+  SB_DCHECK(bytes_per_frame_ > 0);
+
   int rendered_frames = std::min(num_complete_frames_, requested_frames);
 
   if (rendered_frames == 0)
     return 0;  // There is nothing to read from |wsola_output_|, return.
 
-  wsola_output_->CopyPartialFramesTo(0, rendered_frames, dest_offset, dest);
+  SbMemoryCopy(dest->buffer() + bytes_per_frame_ * dest_offset,
+               wsola_output_->buffer(), rendered_frames * bytes_per_frame_);
 
   // Remove the frames which are read.
   int frames_to_move = wsola_output_->frames() - rendered_frames;
-  for (int k = 0; k < channels_; ++k) {
-    float* ch = wsola_output_->channel(k);
-    SbMemoryMove(ch, &ch[rendered_frames], sizeof(*ch) * frames_to_move);
-  }
+  SbMemoryMove(wsola_output_->buffer(),
+               wsola_output_->buffer() + rendered_frames * bytes_per_frame_,
+               frames_to_move * bytes_per_frame_);
   num_complete_frames_ -= rendered_frames;
   return rendered_frames;
 }
 
-bool AudioRendererAlgorithm::TargetIsWithinSearchRegion() const {
+bool AudioTimeStretcher::TargetIsWithinSearchRegion() const {
   const int search_block_size = num_candidate_blocks_ + (ola_window_size_ - 1);
 
   return target_block_index_ >= search_block_index_ &&
@@ -323,7 +359,7 @@
              search_block_index_ + search_block_size;
 }
 
-void AudioRendererAlgorithm::GetOptimalBlock() {
+void AudioTimeStretcher::GetOptimalBlock() {
   int optimal_index = 0;
 
   // An interval around last optimal block which is excluded from the search.
@@ -345,7 +381,9 @@
     // |optimal_index| is in frames and it is relative to the beginning of the
     // |search_block_|.
     optimal_index = internal::OptimalIndex(
-        search_block_.get(), target_block_.get(), exclude_iterval);
+        search_block_.get(), target_block_.get(),
+        kSbMediaAudioSampleTypeFloat32,
+        kSbMediaAudioFrameStorageTypeInterleaved, exclude_iterval);
 
     // Translate |index| w.r.t. the beginning of |audio_buffer_| and extract the
     // optimal block.
@@ -361,11 +399,13 @@
     // where target-block has higher weight close to zero (weight of 1 at index
     // 0) and lower weight close the end.
     for (int k = 0; k < channels_; ++k) {
-      float* ch_opt = optimal_block_->channel(k);
-      const float* const ch_target = target_block_->channel(k);
+      float* ch_opt = reinterpret_cast<float*>(optimal_block_->buffer()) + k;
+      const float* const ch_target =
+          reinterpret_cast<float*>(target_block_->buffer()) + k;
       for (int n = 0; n < ola_window_size_; ++n) {
-        ch_opt[n] = ch_opt[n] * transition_window_[n] +
-                    ch_target[n] * transition_window_[ola_window_size_ + n];
+        ch_opt[n * channels_] =
+            ch_opt[n * channels_] * transition_window_[n] +
+            ch_target[n * channels_] * transition_window_[ola_window_size_ + n];
       }
     }
   }
@@ -374,9 +414,10 @@
   target_block_index_ = optimal_index + ola_hop_size_;
 }
 
-void AudioRendererAlgorithm::PeekAudioWithZeroPrepend(int read_offset_frames,
-                                                      AudioBus* dest) {
-  CHECK_LE(read_offset_frames + dest->frames(), audio_buffer_.frames());
+void AudioTimeStretcher::PeekAudioWithZeroPrepend(int read_offset_frames,
+                                                  DecodedAudio* dest) {
+  SB_DCHECK(bytes_per_frame_ > 0);
+  SB_CHECK(read_offset_frames + dest->frames() <= audio_buffer_.frames());
 
   int write_offset = 0;
   int num_frames_to_read = dest->frames();
@@ -386,11 +427,14 @@
     read_offset_frames = 0;
     num_frames_to_read -= num_zero_frames_appended;
     write_offset = num_zero_frames_appended;
-    dest->ZeroFrames(num_zero_frames_appended);
+    SbMemorySet(dest->buffer(), 0, num_zero_frames_appended * bytes_per_frame_);
   }
   audio_buffer_.PeekFrames(num_frames_to_read, read_offset_frames, write_offset,
                            dest);
 }
 
-}  // namespace media
-}  // namespace cobalt
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.h b/src/starboard/shared/starboard/player/filter/audio_time_stretcher.h
similarity index 71%
rename from src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.h
rename to src/starboard/shared/starboard/player/filter/audio_time_stretcher.h
index e2becc9..8ff6033 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_algorithm.h
+++ b/src/starboard/shared/starboard/player/filter/audio_time_stretcher.h
@@ -2,46 +2,58 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// AudioRendererAlgorithm buffers and transforms audio data. The owner of
-// this object provides audio data to the object through EnqueueBuffer() and
-// requests data from the buffer via FillBuffer().
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// AudioTimeStretcher buffers and transforms audio data. The owner of this
+// object provides audio data to the object through EnqueueBuffer() and requests
+// data from the buffer via FillBuffer().
 //
 // This class is *not* thread-safe. Calls to enqueue and retrieve data must be
 // locked if called from multiple threads.
 //
-// AudioRendererAlgorithm uses the Waveform Similarity Overlap and Add (WSOLA)
+// AudioTimeStretcher uses the Waveform Similarity Overlap and Add (WSOLA)
 // algorithm to stretch or compress audio data to meet playback speeds less than
 // or greater than the natural playback of the audio stream. The algorithm
 // preserves local properties of the audio, therefore, pitch and harmonics are
-// are preserved. See audio_renderer_algorith.cc for a more elaborate
-// description of the algorithm.
+// preserved. See audio_renderer_algorith.cc for a more elaborate description of
+// the algorithm.
 //
 // Audio at very low or very high playback rates are muted to preserve quality.
 
-#ifndef COBALT_MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
-#define COBALT_MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
+#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_TIME_STRETCHER_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_TIME_STRETCHER_H_
 
-#include <memory>
-
-#include "base/basictypes.h"
-#include "base/memory/ref_counted.h"
-#include "cobalt/media/base/audio_buffer.h"
-#include "cobalt/media/base/audio_buffer_queue.h"
-#include "cobalt/media/base/audio_parameters.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/decoded_audio_queue.h"
 #include "starboard/types.h"
 
-namespace cobalt {
-namespace media {
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
 
-class AudioBus;
-
-class MEDIA_EXPORT AudioRendererAlgorithm {
+class AudioTimeStretcher {
  public:
-  AudioRendererAlgorithm();
-  ~AudioRendererAlgorithm();
+  AudioTimeStretcher();
+  ~AudioTimeStretcher();
 
   // Initializes this object with information about the audio stream.
-  void Initialize(const AudioParameters& params);
+  void Initialize(int channels, int samples_per_second);
 
   // Tries to fill |requested_frames| frames into |dest| with possibly scaled
   // data from our |audio_buffer_|. Data is scaled based on |playback_rate|,
@@ -52,36 +64,23 @@
   // |dest_offset| is the offset in frames for writing into |dest|.
   //
   // Returns the number of frames copied into |dest|.
-  int FillBuffer(AudioBus* dest, int dest_offset, int requested_frames,
-                 double playback_rate);
+  scoped_refptr<DecodedAudio> Read(int requested_frames, double playback_rate);
 
   // Clears |audio_buffer_|.
   void FlushBuffers();
 
   // Enqueues a buffer. It is called from the owner of the algorithm after a
   // read completes.
-  void EnqueueBuffer(const scoped_refptr<AudioBuffer>& buffer_in);
+  void EnqueueBuffer(const scoped_refptr<DecodedAudio>& audio_data);
 
   // Returns true if |audio_buffer_| is at or exceeds capacity.
-  bool IsQueueFull();
-
-  // Returns the capacity of |audio_buffer_| in frames.
-  int QueueCapacity() const { return capacity_; }
-
-  // Increase the capacity of |audio_buffer_| if possible.
-  void IncreaseQueueCapacity();
-
-  // Returns an estimate of the amount of memory (in bytes) used for frames.
-  int64_t GetMemoryUsage() const;
+  bool IsQueueFull() const;
 
   // Returns the number of frames left in |audio_buffer_|, which may be larger
   // than QueueCapacity() in the event that EnqueueBuffer() delivered more data
   // than |audio_buffer_| was intending to hold.
   int frames_buffered() { return audio_buffer_.frames(); }
 
-  // Returns the samples per second for this audio stream.
-  int samples_per_second() { return samples_per_second_; }
-
  private:
   // Within |search_block_|, find the block of data that is most similar to
   // |target_block_|, and write it in |optimal_block_|. This method assumes that
@@ -91,8 +90,9 @@
 
   // Read a maximum of |requested_frames| frames from |wsola_output_|. Returns
   // number of frames actually read.
-  int WriteCompletedFramesTo(int requested_frames, int output_offset,
-                             AudioBus* dest);
+  int WriteCompletedFramesTo(int requested_frames,
+                             int output_offset,
+                             DecodedAudio* dest);
 
   // Fill |dest| with frames from |audio_buffer_| starting from frame
   // |read_offset_frames|. |dest| is expected to have the same number of
@@ -101,7 +101,7 @@
   // for negative indices. This might happen for few first frames. This method
   // assumes there is enough frames to fill |dest|, i.e. |read_offset_frames| +
   // |dest->frames()| does not extend to future.
-  void PeekAudioWithZeroPrepend(int read_offset_frames, AudioBus* dest);
+  void PeekAudioWithZeroPrepend(int read_offset_frames, DecodedAudio* dest);
 
   // Run one iteration of WSOLA, if there are sufficient frames. This will
   // overlap-and-add one block to |wsola_output_|, hence, |num_complete_frames_|
@@ -129,11 +129,14 @@
   // Number of channels in audio stream.
   int channels_;
 
+  // Bytes per audio frame.
+  int bytes_per_frame_;
+
   // Sample rate of audio stream.
   int samples_per_second_;
 
   // Buffered audio data.
-  AudioBufferQueue audio_buffer_;
+  DecodedAudioQueue audio_buffer_;
 
   // If muted, keep track of partial frames that should have been skipped over.
   double muted_partial_frame_;
@@ -181,38 +184,41 @@
   // number of requested samples. Furthermore, due to overlap-and-add,
   // the last half-window of the output is incomplete, which is stored in this
   // buffer.
-  std::unique_ptr<AudioBus> wsola_output_;
+  scoped_refptr<DecodedAudio> wsola_output_;
 
   // Overlap-and-add window.
-  std::unique_ptr<float[]> ola_window_;
+  scoped_array<float> ola_window_;
 
   // Transition window, used to update |optimal_block_| by a weighted sum of
   // |optimal_block_| and |target_block_|.
-  std::unique_ptr<float[]> transition_window_;
+  scoped_array<float> transition_window_;
 
   // Auxiliary variables to avoid allocation in every iteration.
 
   // Stores the optimal block in every iteration. This is the most
   // similar block to |target_block_| within |search_block_| and it is
   // overlap-and-added to |wsola_output_|.
-  std::unique_ptr<AudioBus> optimal_block_;
+  scoped_refptr<DecodedAudio> optimal_block_;
 
   // A block of data that search is performed over to find the |optimal_block_|.
-  std::unique_ptr<AudioBus> search_block_;
+  scoped_refptr<DecodedAudio> search_block_;
 
   // Stores the target block, denoted as |target| above. |search_block_| is
   // searched for a block (|optimal_block_|) that is most similar to
   // |target_block_|.
-  std::unique_ptr<AudioBus> target_block_;
+  scoped_refptr<DecodedAudio> target_block_;
 
   // The initial and maximum capacity calculated by Initialize().
   int initial_capacity_;
   int max_capacity_;
 
-  DISALLOW_COPY_AND_ASSIGN(AudioRendererAlgorithm);
+  SB_DISALLOW_COPY_AND_ASSIGN(AudioTimeStretcher);
 };
 
-}  // namespace media
-}  // namespace cobalt
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
 
-#endif  // COBALT_MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_H_
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_TIME_STRETCHER_H_
diff --git a/src/starboard/shared/starboard/player/filter/decoded_audio_queue.cc b/src/starboard/shared/starboard/player/filter/decoded_audio_queue.cc
new file mode 100644
index 0000000..509453f
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/decoded_audio_queue.cc
@@ -0,0 +1,168 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/decoded_audio_queue.h"
+
+#include <algorithm>
+
+#include "starboard/log.h"
+#include "starboard/media.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+DecodedAudioQueue::DecodedAudioQueue() {
+  Clear();
+}
+DecodedAudioQueue::~DecodedAudioQueue() {}
+
+void DecodedAudioQueue::Clear() {
+  buffers_.clear();
+  current_buffer_ = buffers_.begin();
+  current_buffer_offset_ = 0;
+  frames_ = 0;
+}
+
+void DecodedAudioQueue::Append(
+    const scoped_refptr<DecodedAudio>& decoded_audio) {
+  SB_DCHECK(decoded_audio->sample_type() == kSbMediaAudioSampleTypeFloat32);
+  SB_DCHECK(decoded_audio->storage_type() ==
+            kSbMediaAudioFrameStorageTypeInterleaved)
+      << decoded_audio->storage_type();
+  // Add the buffer to the queue. Inserting into deque invalidates all
+  // iterators, so point to the first buffer.
+  buffers_.push_back(decoded_audio);
+  current_buffer_ = buffers_.begin();
+
+  // Update the |frames_| counter since we have added frames.
+  frames_ += decoded_audio->frames();
+  SB_CHECK(frames_ > 0);  // make sure it doesn't overflow.
+}
+
+int DecodedAudioQueue::ReadFrames(int frames,
+                                  int dest_frame_offset,
+                                  DecodedAudio* dest) {
+  SB_DCHECK(dest->frames() >= frames + dest_frame_offset);
+  return InternalRead(frames, true, 0, dest_frame_offset, dest);
+}
+
+int DecodedAudioQueue::PeekFrames(int frames,
+                                  int source_frame_offset,
+                                  int dest_frame_offset,
+                                  DecodedAudio* dest) {
+  SB_DCHECK(dest->frames() >= frames);
+  return InternalRead(frames, false, source_frame_offset, dest_frame_offset,
+                      dest);
+}
+
+void DecodedAudioQueue::SeekFrames(int frames) {
+  // Perform seek only if we have enough bytes in the queue.
+  SB_CHECK(frames <= frames_);
+  int taken = InternalRead(frames, true, 0, 0, NULL);
+  SB_DCHECK(taken == frames);
+}
+
+int DecodedAudioQueue::InternalRead(int frames,
+                                    bool advance_position,
+                                    int source_frame_offset,
+                                    int dest_frame_offset,
+                                    DecodedAudio* dest) {
+  // Counts how many frames are actually read from the buffer queue.
+  int taken = 0;
+  BufferQueue::iterator current_buffer = current_buffer_;
+  int current_buffer_offset = current_buffer_offset_;
+
+  int frames_to_skip = source_frame_offset;
+  while (taken < frames) {
+    // |current_buffer| is valid since the first time this buffer is appended
+    // with data. Make sure there is data to be processed.
+    if (current_buffer == buffers_.end())
+      break;
+
+    scoped_refptr<DecodedAudio> buffer = *current_buffer;
+
+    int remaining_frames_in_buffer = buffer->frames() - current_buffer_offset;
+
+    if (frames_to_skip > 0) {
+      // If there are frames to skip, do it first. May need to skip into
+      // subsequent buffers.
+      int skipped = std::min(remaining_frames_in_buffer, frames_to_skip);
+      current_buffer_offset += skipped;
+      frames_to_skip -= skipped;
+    } else {
+      // Find the right amount to copy from the current buffer. We shall copy no
+      // more than |frames| frames in total and each single step copies no more
+      // than the current buffer size.
+      int copied = std::min(frames - taken, remaining_frames_in_buffer);
+
+      // if |dest| is NULL, there's no need to copy.
+      if (dest) {
+        SB_DCHECK(buffer->channels() == dest->channels());
+        const float* source = reinterpret_cast<const float*>(buffer->buffer()) +
+                              buffer->channels() * current_buffer_offset;
+        float* destination = reinterpret_cast<float*>(dest->buffer()) +
+                             dest->channels() * (dest_frame_offset + taken);
+        SbMemoryCopy(destination, source, copied * dest->channels() * 4);
+      }
+
+      // Increase total number of frames copied, which regulates when to end
+      // this loop.
+      taken += copied;
+
+      // We have read |copied| frames from the current buffer. Advance the
+      // offset.
+      current_buffer_offset += copied;
+    }
+
+    // Has the buffer has been consumed?
+    if (current_buffer_offset == buffer->frames()) {
+      // If we are at the last buffer, no more data to be copied, so stop.
+      BufferQueue::iterator next = current_buffer + 1;
+      if (next == buffers_.end())
+        break;
+
+      // Advances the iterator.
+      current_buffer = next;
+      current_buffer_offset = 0;
+    }
+  }
+
+  if (advance_position) {
+    // Update the appropriate values since |taken| frames have been copied out.
+    frames_ -= taken;
+    SB_DCHECK(frames_ >= 0);
+    SB_DCHECK(current_buffer_ != buffers_.end() || frames_ == 0);
+
+    // Remove any buffers before the current buffer as there is no going
+    // backwards.
+    buffers_.erase(buffers_.begin(), current_buffer);
+    current_buffer_ = buffers_.begin();
+    current_buffer_offset_ = current_buffer_offset;
+  }
+
+  return taken;
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/decoded_audio_queue.h b/src/starboard/shared/starboard/player/filter/decoded_audio_queue.h
new file mode 100644
index 0000000..5dce63b
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/decoded_audio_queue.h
@@ -0,0 +1,108 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_DECODED_AUDIO_QUEUE_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_DECODED_AUDIO_QUEUE_H_
+
+#include <deque>
+
+#include "starboard/configuration.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// A queue of AudioBuffers to support reading of arbitrary chunks of a media
+// data source. Audio data can be copied into an DecodedAudio for output. The
+// current position can be forwarded to anywhere in the buffered data.
+//
+// This class is not inherently thread-safe. Concurrent access must be
+// externally serialized.
+class DecodedAudioQueue {
+ public:
+  DecodedAudioQueue();
+  ~DecodedAudioQueue();
+
+  // Clears the buffer queue.
+  void Clear();
+
+  // Appends |decoded_audio| to this queue.
+  void Append(const scoped_refptr<DecodedAudio>& decoded_audio);
+
+  // Reads a maximum of |frames| frames into |dest| from the current position.
+  // Returns the number of frames read. The current position will advance by the
+  // amount of frames read. |dest_frame_offset| specifies a starting offset into
+  // |dest|. On each call, the frames are converted from their source format
+  // into the destination DecodedAudio.
+  int ReadFrames(int frames, int dest_frame_offset, DecodedAudio* dest);
+
+  // Copies up to |frames| frames from current position to |dest|. Returns
+  // number of frames copied. Doesn't advance current position. Starts at
+  // |source_frame_offset| from current position. |dest_frame_offset| specifies
+  // a starting offset into |dest|. On each call, the frames are converted from
+  // their source format into the destination DecodedAudio.
+  int PeekFrames(int frames,
+                 int source_frame_offset,
+                 int dest_frame_offset,
+                 DecodedAudio* dest);
+
+  // Moves the current position forward by |frames| frames. If |frames| exceeds
+  // frames available, the seek operation will fail.
+  void SeekFrames(int frames);
+
+  // Returns the number of frames buffered beyond the current position.
+  int frames() const { return frames_; }
+
+ private:
+  // Definition of the buffer queue.
+  typedef std::deque<scoped_refptr<DecodedAudio> > BufferQueue;
+
+  // An internal method shared by ReadFrames() and SeekFrames() that actually
+  // does reading. It reads a maximum of |frames| frames into |dest|. Returns
+  // the number of frames read. The current position will be moved forward by
+  // the number of frames read if |advance_position| is set. If |dest| is NULL,
+  // only the current position will advance but no data will be copied.
+  // |source_frame_offset| can be used to skip frames before reading.
+  // |dest_frame_offset| specifies a starting offset into |dest|.
+  int InternalRead(int frames,
+                   bool advance_position,
+                   int source_frame_offset,
+                   int dest_frame_offset,
+                   DecodedAudio* dest);
+
+  BufferQueue::iterator current_buffer_;
+  BufferQueue buffers_;
+  int current_buffer_offset_;
+
+  // Number of frames available to be read in the buffer.
+  int frames_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(DecodedAudioQueue);
+};
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_DECODED_AUDIO_QUEUE_H_
diff --git a/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc b/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
index 5b19b46..033b543 100644
--- a/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/ffmpeg_player_components_impl.cc
@@ -48,16 +48,16 @@
     return scoped_ptr<PlayerComponents>(NULL);
   }
 
-  VideoDecoderImpl* video_decoder =
-      new VideoDecoderImpl(video_parameters.video_codec);
+  VideoDecoderImpl* video_decoder = new VideoDecoderImpl(
+      video_parameters.video_codec, video_parameters.output_mode,
+      video_parameters.decode_target_graphics_context_provider);
   if (!video_decoder->is_valid()) {
     delete video_decoder;
     return scoped_ptr<PlayerComponents>(NULL);
   }
 
   AudioRendererImpl* audio_renderer =
-      new AudioRendererImpl(audio_parameters.job_queue,
-                            scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
+      new AudioRendererImpl(scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
                             audio_parameters.audio_header);
   VideoRendererImpl* video_renderer = new VideoRendererImpl(
       scoped_ptr<HostedVideoDecoder>(video_decoder).Pass());
diff --git a/src/starboard/shared/starboard/player/filter/filter_tests.gypi b/src/starboard/shared/starboard/player/filter/filter_tests.gypi
new file mode 100644
index 0000000..12ef856
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/filter_tests.gypi
@@ -0,0 +1,24 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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': {
+    # This will be included by 'starboard_platform_tests.gyp' on platforms that
+    # uses the filter graph based SbPlayer implementation.  So full path names
+    # have to be used here.
+    'filter_tests_sources': [
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc',
+    ],
+  },
+}
diff --git a/src/starboard/shared/starboard/player/filter/mock_audio_decoder.h b/src/starboard/shared/starboard/player/filter/mock_audio_decoder.h
new file mode 100644
index 0000000..d1726c2
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/mock_audio_decoder.h
@@ -0,0 +1,72 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MOCK_AUDIO_DECODER_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_MOCK_AUDIO_DECODER_H_
+
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/closure.h"
+#include "starboard/shared/starboard/player/decoded_audio_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 {
+namespace testing {
+
+class MockAudioDecoder : public AudioDecoder {
+ public:
+  MockAudioDecoder(SbMediaAudioSampleType sample_type,
+                   SbMediaAudioFrameStorageType storage_type,
+                   int sample_per_second)
+      : sample_type_(sample_type),
+        storage_type_(storage_type),
+        samples_per_second_(sample_per_second) {}
+
+  MOCK_METHOD1(Initialize, void(const Closure&));
+  MOCK_METHOD2(Decode, void(const InputBuffer&, const Closure&));
+  MOCK_METHOD0(WriteEndOfStream, void());
+  MOCK_METHOD0(Read, scoped_refptr<DecodedAudio>());
+  MOCK_METHOD0(Reset, void());
+
+  SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE {
+    return sample_type_;
+  }
+  SbMediaAudioFrameStorageType GetStorageType() const SB_OVERRIDE {
+    return storage_type_;
+  }
+  int GetSamplesPerSecond() const SB_OVERRIDE { return samples_per_second_; }
+
+ private:
+  SbMediaAudioSampleType sample_type_;
+  SbMediaAudioFrameStorageType storage_type_;
+  int samples_per_second_;
+};
+
+}  // namespace testing
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_MOCK_AUDIO_DECODER_H_
diff --git a/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc b/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
index 8711274..0d3728d 100644
--- a/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/stub_player_components_impl.cc
@@ -16,8 +16,10 @@
 
 #include <queue>
 
+#include "starboard/shared/starboard/player/closure.h"
 #include "starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h"
 #include "starboard/shared/starboard/player/filter/video_renderer_impl_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
 
 namespace starboard {
 namespace shared {
@@ -38,13 +40,17 @@
 
 }  // namespace
 
-class StubAudioDecoder : public AudioDecoder {
+class StubAudioDecoder : public AudioDecoder, JobQueue::JobOwner {
  public:
   explicit StubAudioDecoder(const SbMediaAudioHeader& audio_header)
       : sample_type_(GetSupportedSampleType()),
         audio_header_(audio_header),
         stream_ended_(false) {}
-  void Decode(const InputBuffer& input_buffer) SB_OVERRIDE {
+  void Initialize(const Closure& output_cb) SB_OVERRIDE {
+    output_cb_ = output_cb;
+  }
+  void Decode(const InputBuffer& input_buffer,
+              const Closure& consumed_cb) SB_OVERRIDE {
     // Values to represent what kind of dummy audio to fill the decoded audio
     // we produce with.
     enum FillType {
@@ -79,6 +85,8 @@
       }
     }
     last_input_buffer_ = input_buffer;
+    Schedule(consumed_cb);
+    Schedule(output_cb_);
   }
   void WriteEndOfStream() SB_OVERRIDE {
     if (last_input_buffer_.is_valid()) {
@@ -89,6 +97,7 @@
     }
     decoded_audios_.push(new DecodedAudio());
     stream_ended_ = true;
+    Schedule(output_cb_);
   }
   scoped_refptr<DecodedAudio> Read() SB_OVERRIDE {
     scoped_refptr<DecodedAudio> result;
@@ -104,6 +113,8 @@
     }
     stream_ended_ = false;
     last_input_buffer_ = InputBuffer();
+
+    CancelPendingJobs();
   }
   SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE {
     return sample_type_;
@@ -118,6 +129,7 @@
  private:
   static const kMaxDecodedAudiosSize = 64;
 
+  Closure output_cb_;
   SbMediaAudioSampleType sample_type_;
   SbMediaAudioHeader audio_header_;
   bool stream_ended_;
diff --git a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
index 246ee6c..6dcce32 100644
--- a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
@@ -83,7 +83,7 @@
         const scoped_refptr<VideoFrame>& frame) = 0;
 
    protected:
-    ~Host() {}
+    virtual ~Host() {}
   };
 
   virtual void SetHost(Host* host) = 0;
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
index 2a35283..e0ca1a2 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
@@ -67,7 +67,7 @@
 
   // Preroll considered finished after either kPrerollFrames is cached or EOS
   // is reached.
-  static const size_t kPrerollFrames = 8;
+  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
diff --git a/src/starboard/shared/starboard/player/filter/wsola_internals.cc b/src/starboard/shared/starboard/player/filter/wsola_internal.cc
similarity index 67%
rename from src/starboard/shared/starboard/player/filter/wsola_internals.cc
rename to src/starboard/shared/starboard/player/filter/wsola_internal.cc
index 507893e..a7291cf 100644
--- a/src/starboard/shared/starboard/player/filter/wsola_internals.cc
+++ b/src/starboard/shared/starboard/player/filter/wsola_internal.cc
@@ -2,25 +2,44 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // MSVC++ requires this to be set before any other includes to get M_PI.
 #define _USE_MATH_DEFINES
 
-#include "cobalt/media/filters/wsola_internals.h"
+#include "starboard/shared/starboard/player/filter/wsola_internal.h"
 
 #include <algorithm>
 #include <cmath>
 #include <limits>
 #include <memory>
 
-#include "base/logging.h"
-#include "cobalt/media/base/audio_bus.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/log.h"
 #include "starboard/memory.h"
+#include "starboard/shared/internal_only.h"
 
-namespace cobalt {
-namespace media {
-
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
 namespace internal {
 
+namespace {
+
 bool InInterval(int n, Interval q) { return n >= q.first && n <= q.second; }
 
 float MultiChannelSimilarityMeasure(const float* dot_prod_a_b,
@@ -35,43 +54,53 @@
   return similarity_measure;
 }
 
-void MultiChannelDotProduct(const AudioBus* a, int frame_offset_a,
-                            const AudioBus* b, int frame_offset_b,
-                            int num_frames, float* dot_product) {
-  DCHECK_EQ(a->channels(), b->channels());
-  DCHECK_GE(frame_offset_a, 0);
-  DCHECK_GE(frame_offset_b, 0);
-  DCHECK_LE(frame_offset_a + num_frames, a->frames());
-  DCHECK_LE(frame_offset_b + num_frames, b->frames());
+void MultiChannelDotProduct(const scoped_refptr<DecodedAudio>& a,
+                            int frame_offset_a,
+                            const scoped_refptr<DecodedAudio>& b,
+                            int frame_offset_b,
+                            int num_frames,
+                            float* dot_product) {
+  SB_DCHECK(a->channels() == b->channels());
+  SB_DCHECK(frame_offset_a >= 0) << frame_offset_a;
+  SB_DCHECK(frame_offset_b >= 0) << frame_offset_b;
+  SB_DCHECK(frame_offset_a + num_frames <= a->frames());
+  SB_DCHECK(frame_offset_b + num_frames <= b->frames());
 
   SbMemorySet(dot_product, 0, sizeof(*dot_product) * a->channels());
+  const float* a_frames = reinterpret_cast<const float*>(a->buffer());
+  const float* b_frames = reinterpret_cast<const float*>(b->buffer());
   for (int k = 0; k < a->channels(); ++k) {
-    const float* ch_a = a->channel(k) + frame_offset_a;
-    const float* ch_b = b->channel(k) + frame_offset_b;
+    const float* ch_a = a_frames + frame_offset_a * a->channels() + k;
+    const float* ch_b = b_frames + frame_offset_b * b->channels() + k;
     for (int n = 0; n < num_frames; ++n) {
-      dot_product[k] += *ch_a++ * *ch_b++;
+      dot_product[k] += *ch_a * *ch_b;
+      ch_a += a->channels();
+      ch_b += b->channels();
     }
   }
 }
 
-void MultiChannelMovingBlockEnergies(const AudioBus* input,
-                                     int frames_per_block, float* energy) {
+void MultiChannelMovingBlockEnergies(const scoped_refptr<DecodedAudio>& input,
+                                     int frames_per_block,
+                                     float* energy) {
   int num_blocks = input->frames() - (frames_per_block - 1);
   int channels = input->channels();
 
-  for (int k = 0; k < input->channels(); ++k) {
-    const float* input_channel = input->channel(k);
+  const float* input_frames = reinterpret_cast<const float*>(input->buffer());
+  for (int k = 0; k < channels; ++k) {
+    const float* input_channel = input_frames + k;
 
     energy[k] = 0;
 
     // First block of channel |k|.
     for (int m = 0; m < frames_per_block; ++m) {
-      energy[k] += input_channel[m] * input_channel[m];
+      energy[k] += input_channel[m * channels] * input_channel[m * channels];
     }
 
     const float* slide_out = input_channel;
-    const float* slide_in = input_channel + frames_per_block;
-    for (int n = 1; n < num_blocks; ++n, ++slide_in, ++slide_out) {
+    const float* slide_in = input_channel + frames_per_block * channels;
+    for (int n = 1; n < num_blocks;
+         ++n, slide_in += channels, slide_out += channels) {
       energy[k + n * channels] = energy[k + (n - 1) * channels] -
                                  *slide_out * *slide_out +
                                  *slide_in * *slide_in;
@@ -100,15 +129,16 @@
   }
 }
 
-int DecimatedSearch(int decimation, Interval exclude_interval,
-                    const AudioBus* target_block,
-                    const AudioBus* search_segment,
+int DecimatedSearch(int decimation,
+                    Interval exclude_interval,
+                    const scoped_refptr<DecodedAudio>& target_block,
+                    const scoped_refptr<DecodedAudio>& search_segment,
                     const float* energy_target_block,
                     const float* energy_candidate_blocks) {
   int channels = search_segment->channels();
   int block_size = target_block->frames();
   int num_candidate_blocks = search_segment->frames() - (block_size - 1);
-  std::unique_ptr<float[]> dot_prod(new float[channels]);
+  scoped_array<float> dot_prod(new float[channels]);
   float similarity[3];  // Three elements for cubic interpolation.
 
   int n = 0;
@@ -178,13 +208,16 @@
   return optimal_index;
 }
 
-int FullSearch(int low_limit, int high_limit, Interval exclude_interval,
-               const AudioBus* target_block, const AudioBus* search_block,
+int FullSearch(int low_limit,
+               int high_limit,
+               Interval exclude_interval,
+               const scoped_refptr<DecodedAudio>& target_block,
+               const scoped_refptr<DecodedAudio>& search_block,
                const float* energy_target_block,
                const float* energy_candidate_blocks) {
   int channels = search_block->channels();
   int block_size = target_block->frames();
-  std::unique_ptr<float[]> dot_prod(new float[channels]);
+  scoped_array<float> dot_prod(new float[channels]);
 
   float best_similarity = std::numeric_limits<float>::min();
   int optimal_index = 0;
@@ -209,10 +242,18 @@
   return optimal_index;
 }
 
-int OptimalIndex(const AudioBus* search_block, const AudioBus* target_block,
+}  // namespace
+
+int OptimalIndex(const scoped_refptr<DecodedAudio>& search_block,
+                 const scoped_refptr<DecodedAudio>& target_block,
+                 SbMediaAudioSampleType sample_type,
+                 SbMediaAudioFrameStorageType storage_type,
                  Interval exclude_interval) {
   int channels = search_block->channels();
-  DCHECK_EQ(channels, target_block->channels());
+  SB_DCHECK(channels == target_block->channels());
+  SB_DCHECK(sample_type == kSbMediaAudioSampleTypeFloat32);
+  SB_DCHECK(storage_type == kSbMediaAudioFrameStorageTypeInterleaved);
+
   int target_size = target_block->frames();
   int num_candidate_blocks = search_block->frames() - (target_size - 1);
 
@@ -224,8 +265,8 @@
   // heuristically based on experiments.
   const int kSearchDecimation = 5;
 
-  std::unique_ptr<float[]> energy_target_block(new float[channels]);
-  std::unique_ptr<float[]> energy_candidate_blocks(
+  scoped_array<float> energy_target_block(new float[channels]);
+  scoped_array<float> energy_candidate_blocks(
       new float[channels * num_candidate_blocks]);
 
   // Energy of all candid frames.
@@ -255,6 +296,8 @@
 }
 
 }  // namespace internal
-
-}  // namespace media
-}  // namespace cobalt
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/wsola_internal.h b/src/starboard/shared/starboard/player/filter/wsola_internal.h
new file mode 100644
index 0000000..c49dc0c
--- /dev/null
+++ b/src/starboard/shared/starboard/player/filter/wsola_internal.h
@@ -0,0 +1,62 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Modifications Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A set of utility functions to perform WSOLA (Waveform Similarity Based
+// Overlap-Add) to modify audio playback speed without changing the pitch.
+// See http://ieeexplore.ieee.org/document/319366 for more details.
+
+#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_WSOLA_INTERNAL_H_
+#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_WSOLA_INTERNAL_H_
+
+#include <utility>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+namespace internal {
+
+typedef std::pair<int, int> Interval;
+
+// Find the index of the block, within |search_block|, that is most similar
+// to |target_block|. Obviously, the returned index is w.r.t. |search_block|.
+// |exclude_interval| is an interval that is excluded from the search.
+int OptimalIndex(const scoped_refptr<DecodedAudio>& search_block,
+                 const scoped_refptr<DecodedAudio>& target_block,
+                 SbMediaAudioSampleType sample_type,
+                 SbMediaAudioFrameStorageType storage_type,
+                 Interval exclude_interval);
+
+// Return a "periodic" Hann window(https://en.wikipedia.org/wiki/Hann_function).
+// This is the first L samples of an L+1 Hann window. It is perfect
+// reconstruction for overlap-and-add.
+void GetSymmetricHanningWindow(int window_length, float* window);
+
+}  // namespace internal
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_WSOLA_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/wsola_internals.h b/src/starboard/shared/starboard/player/filter/wsola_internals.h
deleted file mode 100644
index 4d96b39..0000000
--- a/src/starboard/shared/starboard/player/filter/wsola_internals.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// A set of utility functions to perform WSOLA.
-
-#ifndef COBALT_MEDIA_FILTERS_WSOLA_INTERNALS_H_
-#define COBALT_MEDIA_FILTERS_WSOLA_INTERNALS_H_
-
-#include <utility>
-
-#include "cobalt/media/base/media_export.h"
-
-namespace cobalt {
-namespace media {
-
-class AudioBus;
-
-namespace internal {
-
-typedef std::pair<int, int> Interval;
-
-// Dot-product of channels of two AudioBus. For each AudioBus an offset is
-// given. |dot_product[k]| is the dot-product of channel |k|. The caller should
-// allocate sufficient space for |dot_product|.
-MEDIA_EXPORT void MultiChannelDotProduct(const AudioBus* a, int frame_offset_a,
-                                         const AudioBus* b, int frame_offset_b,
-                                         int num_frames, float* dot_product);
-
-// Energies of sliding windows of channels are interleaved.
-// The number windows is |input->frames()| - (|frames_per_window| - 1), hence,
-// the method assumes |energy| must be, at least, of size
-// (|input->frames()| - (|frames_per_window| - 1)) * |input->channels()|.
-MEDIA_EXPORT void MultiChannelMovingBlockEnergies(const AudioBus* input,
-                                                  int frames_per_window,
-                                                  float* energy);
-
-// Fit the curve f(x) = a * x^2 + b * x + c such that
-//   f(-1) = y[0]
-//   f(0) = y[1]
-//   f(1) = y[2]
-// and return the maximum, assuming that y[0] <= y[1] >= y[2].
-MEDIA_EXPORT void QuadraticInterpolation(const float* y_values, float* extremum,
-                                         float* extremum_value);
-
-// Search a subset of all candid blocks. The search is performed every
-// |decimation| frames. This reduces complexity by a factor of about
-// 1 / |decimation|. A cubic interpolation is used to have a better estimate of
-// the best match.
-MEDIA_EXPORT int DecimatedSearch(int decimation, Interval exclude_interval,
-                                 const AudioBus* target_block,
-                                 const AudioBus* search_segment,
-                                 const float* energy_target_block,
-                                 const float* energy_candid_blocks);
-
-// Search [|low_limit|, |high_limit|] of |search_segment| to find a block that
-// is most similar to |target_block|. |energy_target_block| is the energy of the
-// |target_block|. |energy_candidate_blocks| is the energy of all blocks within
-// |search_block|.
-MEDIA_EXPORT int FullSearch(int low_limit, int hight_limimit,
-                            Interval exclude_interval,
-                            const AudioBus* target_block,
-                            const AudioBus* search_block,
-                            const float* energy_target_block,
-                            const float* energy_candidate_blocks);
-
-// Find the index of the block, within |search_block|, that is most similar
-// to |target_block|. Obviously, the returned index is w.r.t. |search_block|.
-// |exclude_interval| is an interval that is excluded from the search.
-MEDIA_EXPORT int OptimalIndex(const AudioBus* search_block,
-                              const AudioBus* target_block,
-                              Interval exclude_interval);
-
-// Return a "periodic" Hann window. This is the first L samples of an L+1
-// Hann window. It is perfect reconstruction for overlap-and-add.
-MEDIA_EXPORT void GetSymmetricHanningWindow(int window_length, float* window);
-
-}  // namespace internal
-
-}  // namespace media
-}  // namespace cobalt
-
-#endif  // COBALT_MEDIA_FILTERS_WSOLA_INTERNALS_H_
diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.cc b/src/starboard/shared/starboard/player/input_buffer_internal.cc
index 9794c14..3ff537f 100644
--- a/src/starboard/shared/starboard/player/input_buffer_internal.cc
+++ b/src/starboard/shared/starboard/player/input_buffer_internal.cc
@@ -205,6 +205,20 @@
   buffer_->SetDecryptedContent(buffer, size);
 }
 
+bool operator==(const InputBuffer& lhs, const InputBuffer& rhs) {
+  if (!lhs.is_valid() && !rhs.is_valid()) {
+    return true;
+  }
+  if (lhs.is_valid() && rhs.is_valid()) {
+    return lhs.sample_type() == rhs.sample_type() && lhs.data() == rhs.data() &&
+           lhs.size() == rhs.size() && lhs.pts() == rhs.pts() &&
+           lhs.video_sample_info() == rhs.video_sample_info() &&
+           lhs.drm_info() == rhs.drm_info();
+  }
+
+  return false;
+}
+
 }  // namespace player
 }  // namespace starboard
 }  // namespace shared
diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.h b/src/starboard/shared/starboard/player/input_buffer_internal.h
index 8f4e0ce..123beab 100644
--- a/src/starboard/shared/starboard/player/input_buffer_internal.h
+++ b/src/starboard/shared/starboard/player/input_buffer_internal.h
@@ -64,6 +64,8 @@
   ReferenceCountedBuffer* buffer_;
 };
 
+bool operator==(const InputBuffer& lhs, const InputBuffer& rhs);
+
 }  // namespace player
 }  // namespace starboard
 }  // namespace shared
diff --git a/src/starboard/shared/starboard/player/job_queue.cc b/src/starboard/shared/starboard/player/job_queue.cc
index 254378d..02be55c 100644
--- a/src/starboard/shared/starboard/player/job_queue.cc
+++ b/src/starboard/shared/starboard/player/job_queue.cc
@@ -16,7 +16,6 @@
 
 #include <utility>
 
-#include "starboard/log.h"
 #include "starboard/once.h"
 #include "starboard/thread.h"
 
@@ -62,7 +61,8 @@
 
 }  // namespace
 
-JobQueue::JobQueue() : thread_id_(SbThreadGetId()), stopped_(false) {
+JobQueue::JobQueue()
+    : thread_id_(SbThreadGetId()), condition_(mutex_), stopped_(false) {
   SB_DCHECK(SbThreadIsValidId(thread_id_));
   SetCurrentThreadJobQueue(this);
 }
@@ -73,39 +73,23 @@
   ResetCurrentThreadJobQueue();
 }
 
-void JobQueue::Schedule(Closure job, SbTimeMonotonic delay /*= 0*/) {
-  SB_DCHECK(job.is_valid());
-  SB_DCHECK(delay >= 0) << delay;
-
-  ScopedLock scoped_lock(mutex_);
-  if (stopped_) {
-    return;
-  }
-  if (delay <= 0) {
-    queue_.Put(job);
-    return;
-  }
-  SbTimeMonotonic time_to_run_job = SbTimeGetMonotonicNow() + delay;
-  time_to_job_map_.insert(std::make_pair(time_to_run_job, job));
-  if (time_to_job_map_.begin()->second == job) {
-    queue_.Wake();
-  }
+void JobQueue::Schedule(Closure closure, SbTimeMonotonic delay /*= 0*/) {
+  Schedule(closure, NULL, delay);
 }
 
-void JobQueue::Remove(Closure job) {
+void JobQueue::Remove(Closure closure) {
   SB_DCHECK(BelongsToCurrentThread());
-  SB_DCHECK(job.is_valid());
+  SB_DCHECK(closure.is_valid());
 
   ScopedLock scoped_lock(mutex_);
-  queue_.Remove(job);
   // std::multimap::erase() doesn't return an iterator until C++11.  So this has
-  // to be done in a nested loop to delete multiple occurrences of |job|.
+  // to be done in a nested loop to delete multiple occurrences of |closure|.
   bool should_keep_running = true;
   while (should_keep_running) {
     should_keep_running = false;
     for (TimeToJobMap::iterator iter = time_to_job_map_.begin();
          iter != time_to_job_map_.end(); ++iter) {
-      if (iter->second == job) {
+      if (iter->second.closure == closure) {
         time_to_job_map_.erase(iter);
         should_keep_running = true;
         break;
@@ -118,17 +102,8 @@
   {
     ScopedLock scoped_lock(mutex_);
     stopped_ = true;
+    time_to_job_map_.clear();
   }
-
-  queue_.Wake();
-
-  for (;;) {
-    Closure job = queue_.Poll();
-    if (!job.is_valid()) {
-      break;
-    }
-  }
-  time_to_job_map_.clear();
 }
 
 void JobQueue::RunUntilStopped() {
@@ -164,40 +139,87 @@
   return GetCurrentThreadJobQueue();
 }
 
+void JobQueue::Schedule(Closure closure,
+                        JobOwner* owner,
+                        SbTimeMonotonic delay) {
+  SB_DCHECK(closure.is_valid());
+  SB_DCHECK(delay >= 0) << delay;
+
+  Job job = {closure, owner};
+
+  ScopedLock scoped_lock(mutex_);
+  if (stopped_) {
+    return;
+  }
+  SbTimeMonotonic time_to_run_job = SbTimeGetMonotonicNow() + delay;
+  bool is_first_job = time_to_job_map_.empty() ||
+                      time_to_run_job < time_to_job_map_.begin()->first;
+
+  time_to_job_map_.insert(std::make_pair(time_to_run_job, job));
+  if (is_first_job) {
+    condition_.Signal();
+  }
+}
+
+void JobQueue::RemoveJobsByToken(JobOwner* owner) {
+  SB_DCHECK(BelongsToCurrentThread());
+  SB_DCHECK(owner);
+
+  ScopedLock scoped_lock(mutex_);
+  // std::multimap::erase() doesn't return an iterator until C++11.  So this has
+  // to be done in a nested loop to delete multiple occurrences of |closure|.
+  bool should_keep_running = true;
+  while (should_keep_running) {
+    should_keep_running = false;
+    for (TimeToJobMap::iterator iter = time_to_job_map_.begin();
+         iter != time_to_job_map_.end(); ++iter) {
+      if (iter->second.owner == owner) {
+        time_to_job_map_.erase(iter);
+        should_keep_running = true;
+        break;
+      }
+    }
+  }
+}
+
 bool JobQueue::TryToRunOneJob(bool wait_for_next_job) {
   SB_DCHECK(BelongsToCurrentThread());
 
   Closure job;
-  // |kSbTimeMax| makes more sense here, but |kSbTimeDay| is much safer.
-  SbTimeMonotonic delay = kSbTimeDay;
 
   {
     ScopedLock scoped_lock(mutex_);
     if (stopped_) {
       return false;
     }
-    if (!time_to_job_map_.empty()) {
-      TimeToJobMap::iterator first_delayed_job = time_to_job_map_.begin();
-      delay = first_delayed_job->first - SbTimeGetMonotonicNow();
-      if (delay <= 0) {
-        job = first_delayed_job->second;
-        time_to_job_map_.erase(first_delayed_job);
+    if (time_to_job_map_.empty() && wait_for_next_job) {
+      // |kSbTimeMax| makes more sense here, but |kSbTimeDay| is much safer.
+      condition_.WaitTimed(kSbTimeDay);
+    }
+    if (time_to_job_map_.empty()) {
+      return false;
+    }
+    TimeToJobMap::iterator first_delayed_job = time_to_job_map_.begin();
+    SbTimeMonotonic delay = first_delayed_job->first - SbTimeGetMonotonicNow();
+    if (delay > 0) {
+      if (wait_for_next_job) {
+        condition_.WaitTimed(delay);
+      } else {
+        return false;
       }
     }
-  }
-
-  if (!job.is_valid()) {
-    if (wait_for_next_job) {
-      job = queue_.GetTimed(delay);
-    } else {
-      job = queue_.Poll();
+    // Try to retrieve the job again as the job map can be altered during the
+    // wait.
+    first_delayed_job = time_to_job_map_.begin();
+    delay = first_delayed_job->first - SbTimeGetMonotonicNow();
+    if (delay > 0) {
+      return false;
     }
+    job = first_delayed_job->second.closure;
+    time_to_job_map_.erase(first_delayed_job);
   }
 
-  if (!job.is_valid()) {
-    return false;
-  }
-
+  SB_DCHECK(job.is_valid());
   job.Run();
   return true;
 }
diff --git a/src/starboard/shared/starboard/player/job_queue.h b/src/starboard/shared/starboard/player/job_queue.h
index 36100ab..361d82e 100644
--- a/src/starboard/shared/starboard/player/job_queue.h
+++ b/src/starboard/shared/starboard/player/job_queue.h
@@ -19,6 +19,7 @@
 
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/condition_variable.h"
+#include "starboard/log.h"
 #include "starboard/mutex.h"
 #include "starboard/queue.h"
 #include "starboard/shared/internal_only.h"
@@ -39,11 +40,30 @@
 // A thread can only have one job queue.
 class JobQueue {
  public:
+  class JobOwner {
+   protected:
+    explicit JobOwner(JobQueue* job_queue = JobQueue::current())
+        : job_queue_(job_queue) {
+      SB_DCHECK(job_queue);
+    }
+    ~JobOwner() { CancelPendingJobs(); }
+    bool BelongsToCurrentThread() const {
+      return job_queue_->BelongsToCurrentThread();
+    }
+    void Schedule(Closure closure, SbTimeMonotonic delay = 0) {
+      job_queue_->Schedule(closure, this, delay);
+    }
+    void CancelPendingJobs() { job_queue_->RemoveJobsByToken(this); }
+
+   private:
+    JobQueue* job_queue_;
+  };
+
   JobQueue();
   ~JobQueue();
 
-  void Schedule(Closure job, SbTimeMonotonic delay = 0);
-  void Remove(Closure job);
+  void Schedule(Closure closure, SbTimeMonotonic delay = 0);
+  void Remove(Closure closure);
 
   // The processing of jobs may not be stopped when this function returns, but
   // it is guaranteed that the processing will be stopped very soon.  So it is
@@ -57,8 +77,14 @@
   static JobQueue* current();
 
  private:
-  typedef std::multimap<SbTimeMonotonic, Closure> TimeToJobMap;
+  struct Job {
+    Closure closure;
+    JobOwner* owner;
+  };
+  typedef std::multimap<SbTimeMonotonic, Job> TimeToJobMap;
 
+  void Schedule(Closure closure, JobOwner* owner, SbTimeMonotonic delay);
+  void RemoveJobsByToken(JobOwner* owner);
   // Return true if a job is run, otherwise return false.  When there is no job
   // ready to run currently and |wait_for_next_job| is true, the function will
   // wait to until a job is available or if the |queue_| is woken up.  Note that
@@ -68,7 +94,7 @@
 
   SbThreadId thread_id_;
   Mutex mutex_;
-  Queue<Closure> queue_;
+  ConditionVariable condition_;
   TimeToJobMap time_to_job_map_;
   bool stopped_;
 };
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_cancel.cc b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_cancel.cc
index e831818..5b5e8d6 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_cancel.cc
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_cancel.cc
@@ -14,8 +14,7 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #include "starboard/log.h"
 #include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
@@ -26,5 +25,4 @@
   }
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_create.cc b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_create.cc
index 875726f..d34c9b2 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_create.cc
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_create.cc
@@ -14,8 +14,7 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
 
@@ -24,5 +23,4 @@
   return SbSpeechRecognizerPrivate::CreateSpeechRecognizer(handler);
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_destroy.cc b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_destroy.cc
index 3948cc7..abf0c5a 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_destroy.cc
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_destroy.cc
@@ -14,8 +14,7 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
 
@@ -25,5 +24,4 @@
   }
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h
index a69a05c..d05fccb 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h
@@ -18,8 +18,7 @@
 #include "starboard/shared/internal_only.h"
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 struct SbSpeechRecognizerPrivate {
   virtual ~SbSpeechRecognizerPrivate() {}
   virtual bool Start(const SbSpeechConfiguration* configuration) = 0;
@@ -29,7 +28,6 @@
       const SbSpeechRecognizerHandler* handler);
   static void DestroySpeechRecognizer(SbSpeechRecognizer speech_recognizer);
 };
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #endif  // STARBOARD_SHARED_STARBOARD_SPEECH_RECOGNIZER_SPEECH_RECOGNIZER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_start.cc b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_start.cc
index 2fd1002..523819a 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_start.cc
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_start.cc
@@ -14,8 +14,7 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
 
@@ -26,5 +25,4 @@
              : false;
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_stop.cc b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_stop.cc
index 717d229..96db02a 100644
--- a/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_stop.cc
+++ b/src/starboard/shared/starboard/speech_recognizer/speech_recognizer_stop.cc
@@ -14,8 +14,7 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #include "starboard/log.h"
 #include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
@@ -26,5 +25,4 @@
   }
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/decode_target_is_opaque.cc b/src/starboard/shared/stub/decode_target_is_opaque.cc
index 3759b36..16301d9 100644
--- a/src/starboard/shared/stub/decode_target_is_opaque.cc
+++ b/src/starboard/shared/stub/decode_target_is_opaque.cc
@@ -17,8 +17,8 @@
 
 #if SB_API_VERSION < 4
 
-#if !(SB_VERSION(3) && SB_HAS(GRAPHICS))
-#error "SbDecodeTargetIsOpaque requires SB_VERSION(3) and SB_HAS(GRAPHICS)."
+#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
+#error "Requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
 #endif
 
 bool SbDecodeTargetIsOpaque(SbDecodeTarget decode_target) {
diff --git a/src/starboard/shared/stub/image_decode.cc b/src/starboard/shared/stub/image_decode.cc
index 3072b96..dd8d4d8 100644
--- a/src/starboard/shared/stub/image_decode.cc
+++ b/src/starboard/shared/stub/image_decode.cc
@@ -15,8 +15,8 @@
 #include "starboard/configuration.h"
 #include "starboard/image.h"
 
-#if !(SB_VERSION(3) && SB_HAS(GRAPHICS))
-#error "SbImageDecode requires SB_VERSION(3) and SB_HAS(GRAPHICS)."
+#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
+#error "SbImageDecode requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
 #endif
 
 #if SB_API_VERSION >= 4
diff --git a/src/starboard/shared/stub/image_is_decode_supported.cc b/src/starboard/shared/stub/image_is_decode_supported.cc
index 2b0b367..b620022 100644
--- a/src/starboard/shared/stub/image_is_decode_supported.cc
+++ b/src/starboard/shared/stub/image_is_decode_supported.cc
@@ -15,8 +15,8 @@
 #include "starboard/configuration.h"
 #include "starboard/image.h"
 
-#if !(SB_VERSION(3) && SB_HAS(GRAPHICS))
-#error "SbImageIsDecodeSupported requires SB_VERSION(3) and SB_HAS(GRAPHICS)."
+#if !(SB_API_VERSION >= 3 && SB_HAS(GRAPHICS))
+#error "Requires SB_API_VERSION >= 3 and SB_HAS(GRAPHICS)."
 #endif
 
 bool SbImageIsDecodeSupported(const char* mime_type,
diff --git a/src/starboard/shared/stub/microphone_close.cc b/src/starboard/shared/stub/microphone_close.cc
index fd1bd15..9246202 100644
--- a/src/starboard/shared/stub/microphone_close.cc
+++ b/src/starboard/shared/stub/microphone_close.cc
@@ -14,10 +14,10 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 bool SbMicrophoneClose(SbMicrophone microphone) {
   return false;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_create.cc b/src/starboard/shared/stub/microphone_create.cc
index eeb5c13..bdc4166 100644
--- a/src/starboard/shared/stub/microphone_create.cc
+++ b/src/starboard/shared/stub/microphone_create.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 SbMicrophone SbMicrophoneCreate(SbMicrophoneId id,
                                 int sample_rate_in_hz,
@@ -22,4 +22,4 @@
   return kSbMicrophoneInvalid;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_destroy.cc b/src/starboard/shared/stub/microphone_destroy.cc
index 11b1ce7..b0bbdab 100644
--- a/src/starboard/shared/stub/microphone_destroy.cc
+++ b/src/starboard/shared/stub/microphone_destroy.cc
@@ -14,8 +14,8 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 void SbMicrophoneDestroy(SbMicrophone microphone) {}
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_get_available.cc b/src/starboard/shared/stub/microphone_get_available.cc
index 5fdb309..264b2d0 100644
--- a/src/starboard/shared/stub/microphone_get_available.cc
+++ b/src/starboard/shared/stub/microphone_get_available.cc
@@ -14,11 +14,11 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array,
                              int info_array_size) {
   return 0;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc b/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
index d33766f..82cfbc1 100644
--- a/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
+++ b/src/starboard/shared/stub/microphone_is_sample_rate_supported.cc
@@ -14,11 +14,11 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 bool SbMicrophoneIsSampleRateSupported(SbMicrophoneId id,
                                        int sample_rate_in_hz) {
   return false;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_open.cc b/src/starboard/shared/stub/microphone_open.cc
index 0ce049b..19fedbf 100644
--- a/src/starboard/shared/stub/microphone_open.cc
+++ b/src/starboard/shared/stub/microphone_open.cc
@@ -14,10 +14,10 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 bool SbMicrophoneOpen(SbMicrophone microphone) {
   return false;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/microphone_read.cc b/src/starboard/shared/stub/microphone_read.cc
index 2965831..d798d57 100644
--- a/src/starboard/shared/stub/microphone_read.cc
+++ b/src/starboard/shared/stub/microphone_read.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/microphone.h"
 
-#if SB_HAS(MICROPHONE) && SB_VERSION(2)
+#if SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
 
 int SbMicrophoneRead(SbMicrophone microphone,
                      void* out_audio_data,
@@ -22,4 +22,4 @@
   return -1;
 }
 
-#endif  // SB_HAS(MICROPHONE) && SB_VERSION(2)
+#endif  // SB_HAS(MICROPHONE) && SB_API_VERSION >= 2
diff --git a/src/starboard/shared/stub/player_create.cc b/src/starboard/shared/stub/player_create.cc
index 7d5eb89..0a0bf62 100644
--- a/src/starboard/shared/stub/player_create.cc
+++ b/src/starboard/shared/stub/player_create.cc
@@ -32,7 +32,7 @@
                         ,
                         SbPlayerOutputMode output_mode,
                         SbDecodeTargetGraphicsContextProvider* /*provider*/
-#elif SB_VERSION(3)
+#elif SB_API_VERSION >= 3
                         ,
                         SbDecodeTargetProvider* /*provider*/
 #endif
diff --git a/src/starboard/shared/stub/speech_recognizer_cancel.cc b/src/starboard/shared/stub/speech_recognizer_cancel.cc
index a7c263a..479571a 100644
--- a/src/starboard/shared/stub/speech_recognizer_cancel.cc
+++ b/src/starboard/shared/stub/speech_recognizer_cancel.cc
@@ -14,10 +14,8 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 void SbSpeechRecognizerCancel(SbSpeechRecognizer /*recognizer*/) {}
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/speech_recognizer_create.cc b/src/starboard/shared/stub/speech_recognizer_create.cc
index 5acdf3a..ffa08cd 100644
--- a/src/starboard/shared/stub/speech_recognizer_create.cc
+++ b/src/starboard/shared/stub/speech_recognizer_create.cc
@@ -14,13 +14,11 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 SbSpeechRecognizer SbSpeechRecognizerCreate(
     const SbSpeechRecognizerHandler* /*handler*/) {
   return kSbSpeechRecognizerInvalid;
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/speech_recognizer_destroy.cc b/src/starboard/shared/stub/speech_recognizer_destroy.cc
index 090417c..b79df26 100644
--- a/src/starboard/shared/stub/speech_recognizer_destroy.cc
+++ b/src/starboard/shared/stub/speech_recognizer_destroy.cc
@@ -14,10 +14,8 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 void SbSpeechRecognizerDestroy(SbSpeechRecognizer /*recognizer*/) {}
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/speech_recognizer_start.cc b/src/starboard/shared/stub/speech_recognizer_start.cc
index fc2d43d..0b4e4be 100644
--- a/src/starboard/shared/stub/speech_recognizer_start.cc
+++ b/src/starboard/shared/stub/speech_recognizer_start.cc
@@ -14,13 +14,11 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 bool SbSpeechRecognizerStart(SbSpeechRecognizer /*recognizer*/,
                              const SbSpeechConfiguration* /*configuration*/) {
   return false;
 }
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/speech_recognizer_stop.cc b/src/starboard/shared/stub/speech_recognizer_stop.cc
index b799661..b3b74b9 100644
--- a/src/starboard/shared/stub/speech_recognizer_stop.cc
+++ b/src/starboard/shared/stub/speech_recognizer_stop.cc
@@ -14,10 +14,8 @@
 
 #include "starboard/speech_recognizer.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 void SbSpeechRecognizerStop(SbSpeechRecognizer /*recognizer*/) {}
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
diff --git a/src/starboard/shared/stub/speech_synthesis_cancel.cc b/src/starboard/shared/stub/speech_synthesis_cancel.cc
index 5a4cf19..5071394 100644
--- a/src/starboard/shared/stub/speech_synthesis_cancel.cc
+++ b/src/starboard/shared/stub/speech_synthesis_cancel.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/speech_synthesis.h"
 
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 #error If speech synthesis not enabled on this platform, please exclude it\
        from the build
 #endif
diff --git a/src/starboard/shared/stub/speech_synthesis_set_language.cc b/src/starboard/shared/stub/speech_synthesis_set_language.cc
index 6cc1b33..8cc1042 100644
--- a/src/starboard/shared/stub/speech_synthesis_set_language.cc
+++ b/src/starboard/shared/stub/speech_synthesis_set_language.cc
@@ -16,7 +16,7 @@
 
 #include "starboard/speech_synthesis.h"
 
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 #error If speech synthesis not enabled on this platform, please exclude it\
        from the build
 #endif
diff --git a/src/starboard/shared/stub/speech_synthesis_speak.cc b/src/starboard/shared/stub/speech_synthesis_speak.cc
index 53cd5f5..a06c9cf 100644
--- a/src/starboard/shared/stub/speech_synthesis_speak.cc
+++ b/src/starboard/shared/stub/speech_synthesis_speak.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/speech_synthesis.h"
 
-#if !SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#if !SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 #error If speech synthesis not enabled on this platform, please exclude it\
        from the build
 #endif
diff --git a/src/starboard/shared/stub/system_raise_platform_error.cc b/src/starboard/shared/stub/system_raise_platform_error.cc
index 13303cb..aad4c25 100644
--- a/src/starboard/shared/stub/system_raise_platform_error.cc
+++ b/src/starboard/shared/stub/system_raise_platform_error.cc
@@ -27,12 +27,14 @@
     case kSbSystemPlatformErrorTypeConnectionError:
       message = "Connection error.";
       break;
+#if SB_API_VERSION < SB_PLATFORM_ERROR_CLEANUP_API_VERSION
     case kSbSystemPlatformErrorTypeUserSignedOut:
       message = "User is not signed in.";
       break;
     case kSbSystemPlatformErrorTypeUserAgeRestricted:
       message = "User is age restricted.";
       break;
+#endif
     default:
       message = "<unknown>";
       break;
diff --git a/src/starboard/shared/uwp/application_uwp.cc b/src/starboard/shared/uwp/application_uwp.cc
index 41ed103..1911a61 100644
--- a/src/starboard/shared/uwp/application_uwp.cc
+++ b/src/starboard/shared/uwp/application_uwp.cc
@@ -14,57 +14,243 @@
 
 #include "starboard/shared/uwp/application_uwp.h"
 
+#include <windows.h>
+#include <WinSock2.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "starboard/event.h"
 #include "starboard/log.h"
 #include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
 #include "starboard/shared/uwp/window_internal.h"
+#include "starboard/shared/win32/thread_private.h"
+#include "starboard/shared/win32/wchar_utils.h"
+#include "starboard/string.h"
 
 using starboard::shared::starboard::Application;
 using starboard::shared::starboard::CommandLine;
 using starboard::shared::uwp::ApplicationUwp;
+using starboard::shared::uwp::GetArgvZero;
+using starboard::shared::win32::wchar_tToUTF8;
+using Windows::ApplicationModel::Activation::ActivationKind;
 using Windows::ApplicationModel::Activation::IActivatedEventArgs;
+using Windows::ApplicationModel::Activation::IProtocolActivatedEventArgs;
 using Windows::ApplicationModel::Core::CoreApplication;
 using Windows::ApplicationModel::Core::CoreApplicationView;
 using Windows::ApplicationModel::Core::IFrameworkView;
 using Windows::ApplicationModel::Core::IFrameworkViewSource;
+using Windows::ApplicationModel::SuspendingEventArgs;
+using Windows::Foundation::EventHandler;
+using Windows::Foundation::TimeSpan;
 using Windows::Foundation::TypedEventHandler;
-using Windows::UI::Core::CoreWindow;
+using Windows::Foundation::Uri;
+using Windows::System::Threading::TimerElapsedHandler;
+using Windows::System::Threading::ThreadPoolTimer;
+using Windows::UI::Core::CoreDispatcherPriority;
+using Windows::System::UserAuthenticationStatus;
 using Windows::UI::Core::CoreProcessEventsOption;
+using Windows::UI::Core::CoreWindow;
+using Windows::UI::Core::DispatchedHandler;
+using Windows::UI::Core::KeyEventArgs;
+
+namespace sbwin32 = starboard::shared::win32;
+
+namespace {
+
+const int kWinSockVersionMajor = 2;
+const int kWinSockVersionMinor = 2;
+
+int main_return_value = 0;
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+
+// Parses a starboard: URI scheme by splitting args at ';' boundries.
+std::vector<std::string> ParseStarboardUri(const std::string& uri) {
+  std::vector<std::string> result;
+  result.push_back(GetArgvZero());
+
+  size_t index = uri.find(':');
+  if (index == std::string::npos) {
+    return result;
+  }
+
+  std::string args = uri.substr(index + 1);
+
+  while (!args.empty()) {
+    size_t next = args.find(';');
+    result.push_back(args.substr(0, next));
+    if (next == std::string::npos) {
+      return result;
+    }
+    args = args.substr(next + 1);
+  }
+  return result;
+}
+
+#endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+
+std::unique_ptr<Application::Event> MakeDeepLinkEvent(
+    const std::string& uri_string) {
+  size_t index = uri_string.find(':');
+  SB_DCHECK(index != std::string::npos);
+
+  std::string uri_protocol_stripped = uri_string.substr(index + 1);
+  SB_LOG(INFO) << "Navigate to: [" << uri_protocol_stripped << "]";
+  const size_t kMaxDeepLinkSize = 128 * 1024;
+  const std::size_t uri_size = uri_protocol_stripped.size();
+  if (uri_size > kMaxDeepLinkSize) {
+    SB_NOTREACHED() << "App launch data too big: " << uri_size;
+    return nullptr;
+  }
+
+  const int kBufferSize = static_cast<int>(uri_protocol_stripped.size()) + 1;
+  char* deep_link = new char[kBufferSize];
+  SB_DCHECK(deep_link);
+  SbStringCopy(deep_link, uri_protocol_stripped.c_str(), kBufferSize);
+
+  return std::unique_ptr<Application::Event>(
+      new Application::Event(kSbEventTypeLink, deep_link,
+                             Application::DeleteArrayDestructor<const char*>));
+}
+
+}  // namespace
 
 ref class App sealed : public IFrameworkView {
  public:
-  App() {}
+  App() : previously_activated_(false) {}
 
   // IFrameworkView methods.
   virtual void Initialize(
-      Windows::ApplicationModel::Core::CoreApplicationView^ applicationView) {
+      CoreApplicationView^ applicationView) {
+    SbAudioSinkPrivate::Initialize();
+    CoreApplication::Suspending +=
+        ref new EventHandler<SuspendingEventArgs^>(this, &App::OnSuspending);
+    CoreApplication::Resuming +=
+        ref new EventHandler<Object^>(this, &App::OnResuming);
     applicationView->Activated +=
         ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(
             this, &App::OnActivated);
   }
-  virtual void SetWindow(CoreWindow^ window) {}
+  virtual void SetWindow(CoreWindow^ window) {
+    ApplicationUwp::Get()->SetCoreWindow(window);
+    window->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(
+      this, &App::OnKeyUp);
+    window->KeyDown += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(
+      this, &App::OnKeyDown);
+  }
   virtual void Load(Platform::String^ entryPoint) {}
   virtual void Run() {
-    CoreWindow ^window = CoreWindow::GetForCurrentThread();
-    window->Activate();
-    window->Dispatcher->ProcessEvents(
-        CoreProcessEventsOption::ProcessUntilQuit);
+    args_.push_back(GetArgvZero());
+    argv_.push_back(args_.begin()->c_str());
+    main_return_value = application_.Run(
+        static_cast<int>(argv_.size()), const_cast<char**>(argv_.data()));
   }
-  virtual void Uninitialize() {
+  virtual void Uninitialize() { SbAudioSinkPrivate::TearDown(); }
+
+  void OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) {
+    SB_DLOG(INFO) << "Suspending";
+    // Note if we dispatch "suspend" here before pause, application.cc
+    // will inject the "pause" which will cause us to go async which
+    // will cause us to not have completed the suspend operation before
+    // returning, which UWP requires.
+    ApplicationUwp::Get()->DispatchAndDelete(
+        new ApplicationUwp::Event(kSbEventTypePause, NULL, NULL));
+    ApplicationUwp::Get()->DispatchAndDelete(
+        new ApplicationUwp::Event(kSbEventTypeSuspend, NULL, NULL));
+  }
+
+  void OnResuming(Platform::Object^ sender, Platform::Object^ args) {
+    SB_DLOG(INFO) << "Resuming";
+    ApplicationUwp::Get()->DispatchAndDelete(
+        new ApplicationUwp::Event(kSbEventTypeResume, NULL, NULL));
+    ApplicationUwp::Get()->DispatchAndDelete(
+        new ApplicationUwp::Event(kSbEventTypeUnpause, NULL, NULL));
+  }
+
+  void OnKeyUp(CoreWindow^ sender, KeyEventArgs^ args) {
+    ApplicationUwp::Get()->OnKeyEvent(sender, args, true);
+  }
+
+  void OnKeyDown(CoreWindow^ sender, KeyEventArgs^ args) {
+    ApplicationUwp::Get()->OnKeyEvent(sender, args, false);
   }
 
   void OnActivated(
       CoreApplicationView^ applicationView, IActivatedEventArgs^ args) {
-    CoreWindow::GetForCurrentThread()->Activate();
-    ApplicationUwp::Get()->DispatchStart();
+    // Please see application lifecyle description:
+    // https://docs.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle
+    // Note that this document was written for Xaml apps not core apps,
+    // so for us the precise API is a little different.
+    // The substance is that, while OnActiviated is definitely called the
+    // first time the application is started, it may additionally called
+    // in other cases while the process is already running. Starboard
+    // applications cannot fully restart in a process lifecycle,
+    // so we interpret the first activation and the subsequent ones differently.
+    if (args->Kind == ActivationKind::Protocol) {
+      Uri^ uri = dynamic_cast<IProtocolActivatedEventArgs^>(args)->Uri;
+
+#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+      // The starboard: scheme provides commandline arguments, but that's
+      // only allowed during a process's first activation.
+      if (!previously_activated_ && uri->SchemeName->Equals("starboard")) {
+        std::string uri_string = wchar_tToUTF8(uri->RawUri->Data());
+        // args_ is a vector of std::string, but argv_ is a vector of
+        // char* into args_ so as to compose a char**.
+        args_ = ParseStarboardUri(uri_string);
+        for (const std::string& arg : args_) {
+          argv_.push_back(arg.c_str());
+        }
+
+        ApplicationUwp::Get()->SetCommandLine(
+          static_cast<int>(argv_.size()), argv_.data());
+      }
+#endif  // defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
+      if (uri->SchemeName->Equals("youtube") ||
+          uri->SchemeName->Equals("ms-xbl-07459769")) {
+        std::string uri_string = sbwin32::platformStringToString(uri->RawUri);
+        if (previously_activated_) {
+          std::unique_ptr<Application::Event> event =
+            MakeDeepLinkEvent(uri_string);
+          SB_DCHECK(event);
+          ApplicationUwp::Get()->Inject(event.release());
+        } else {
+          SB_DCHECK(!uri_string.empty());
+          ApplicationUwp::Get()->SetStartLink(uri_string.c_str());
+        }
+      }
+    }
+    previous_activation_kind_ = args->Kind;
+
+    if (!previously_activated_) {
+      CoreWindow::GetForCurrentThread()->Activate();
+      // Call DispatchStart async so the UWP system thinks we're activated.
+      // Some tools seem to want the application to be activated before
+      // interacting with them, some things are disallowed during activation
+      // (such as exiting), and DispatchStart (for example) runs
+      // automated tests synchronously.
+      CoreWindow::GetForCurrentThread()->Dispatcher->RunAsync(
+          CoreDispatcherPriority::Normal, ref new DispatchedHandler([this]() {
+            ApplicationUwp::Get()->DispatchStart();
+          }));
+    }
+    previously_activated_ = true;
   }
+ private:
+  bool previously_activated_;
+  // Only valid if previously_activated_ is true
+  ActivationKind previous_activation_kind_;
+  std::vector<std::string> args_;
+  std::vector<const char *> argv_;
+
+  starboard::shared::uwp::ApplicationUwp application_;
 };
 
 ref class Direct3DApplicationSource sealed : IFrameworkViewSource {
  public:
-  Direct3DApplicationSource() {
-    SB_LOG(INFO) << "Direct3DApplicationSource";
-  }
+  Direct3DApplicationSource() {}
   virtual IFrameworkView^ CreateView() {
     return ref new App();
   }
@@ -74,6 +260,21 @@
 namespace shared {
 namespace uwp {
 
+// If an argv[0] is required, fill it in with the result of
+// GetModuleFileName()
+std::string GetArgvZero() {
+  const size_t kMaxModuleNameSize = 256;
+  wchar_t buffer[kMaxModuleNameSize];
+  DWORD result = GetModuleFileName(NULL, buffer, kMaxModuleNameSize);
+  std::string arg;
+  if (result == 0) {
+    arg = "unknown";
+  } else {
+    arg = wchar_tToUTF8(buffer, result).c_str();
+  }
+  return arg;
+}
+
 ApplicationUwp::ApplicationUwp() : window_(kSbWindowInvalid) {}
 
 ApplicationUwp::~ApplicationUwp() {}
@@ -87,7 +288,7 @@
   return nullptr;
 }
 
-SbWindow ApplicationUwp::CreateWindow(const SbWindowOptions* options) {
+SbWindow ApplicationUwp::CreateWindowForUWP(const SbWindowOptions* options) {
   // TODO: Determine why SB_DCHECK(IsCurrentThread()) fails in nplb, fix it,
   // and add back this check.
 
@@ -116,22 +317,61 @@
 }
 
 bool ApplicationUwp::DispatchNextEvent() {
-  auto direct3DApplicationSource = ref new Direct3DApplicationSource();
-  CoreApplication::Run(direct3DApplicationSource);
+  core_window_->Activate();
+  core_window_->Dispatcher->ProcessEvents(
+      CoreProcessEventsOption::ProcessUntilQuit);
   return false;
 }
 
 void ApplicationUwp::Inject(Application::Event* event) {
-  // TODO: Implement with CoreWindow->GetForCurrentThread->Dispatcher->RunAsync
-  SB_NOTIMPLEMENTED();
+  CoreWindow::GetForCurrentThread()->Dispatcher->RunAsync(
+    CoreDispatcherPriority::Normal,
+    ref new DispatchedHandler([this, event]() {
+      bool result = DispatchAndDelete(event);
+      if (!result) {
+        CoreApplication::Exit();
+      }
+    }));
 }
 
 void ApplicationUwp::InjectTimedEvent(Application::TimedEvent* timed_event) {
-  SB_NOTIMPLEMENTED();
+  SbTimeMonotonic delay_usec =
+      timed_event->target_time - SbTimeGetMonotonicNow();
+  if (delay_usec < 0) {
+    delay_usec = 0;
+  }
+
+  // TimeSpan ticks are, like FILETIME, 100ns
+  const SbTimeMonotonic kTicksPerUsec = 10;
+
+  TimeSpan timespan;
+  timespan.Duration = delay_usec * kTicksPerUsec;
+
+  ScopedLock lock(mutex_);
+  ThreadPoolTimer^ timer = ThreadPoolTimer::CreateTimer(
+    ref new TimerElapsedHandler([this, timed_event](ThreadPoolTimer^ timer) {
+      core_window_->Dispatcher->RunAsync(
+        CoreDispatcherPriority::Normal,
+        ref new DispatchedHandler([this, timed_event]() {
+          timed_event->callback(timed_event->context);
+          ScopedLock lock(mutex_);
+          auto it = timer_event_map_.find(timed_event->id);
+          if (it != timer_event_map_.end()) {
+            timer_event_map_.erase(it);
+          }
+        }));
+    }), timespan);
+  timer_event_map_.emplace(timed_event->id, timer);
 }
 
 void ApplicationUwp::CancelTimedEvent(SbEventId event_id) {
-  SB_NOTIMPLEMENTED();
+  ScopedLock lock(mutex_);
+  auto it = timer_event_map_.find(event_id);
+  if (it == timer_event_map_.end()) {
+    return;
+  }
+  it->second->Cancel();
+  timer_event_map_.erase(it);
 }
 
 Application::TimedEvent* ApplicationUwp::GetNextDueTimedEvent() {
@@ -147,3 +387,35 @@
 }  // namespace uwp
 }  // namespace shared
 }  // namespace starboard
+
+[Platform::MTAThread]
+int main(Platform::Array<Platform::String^>^ args) {
+  if (!IsDebuggerPresent()) {
+    // By default, a Windows application will display a dialog box
+    // when it crashes. This is extremely undesirable when run offline.
+    // The following configures messages to be print to the console instead.
+    _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);
+  }
+
+  WSAData wsaData;
+  int init_result = WSAStartup(
+      MAKEWORD(kWinSockVersionMajor, kWinSockVersionMajor), &wsaData);
+
+  SB_CHECK(init_result == 0);
+  // WSAStartup returns the highest version that is supported up to the version
+  // we request.
+  SB_CHECK(LOBYTE(wsaData.wVersion) == kWinSockVersionMajor &&
+           HIBYTE(wsaData.wVersion) == kWinSockVersionMinor);
+
+  starboard::shared::win32::RegisterMainThread();
+
+  auto direct3DApplicationSource = ref new Direct3DApplicationSource();
+  CoreApplication::Run(direct3DApplicationSource);
+
+  WSACleanup();
+
+  return main_return_value;
+}
diff --git a/src/starboard/shared/uwp/application_uwp.h b/src/starboard/shared/uwp/application_uwp.h
index f2c9efa..8bdc00d 100644
--- a/src/starboard/shared/uwp/application_uwp.h
+++ b/src/starboard/shared/uwp/application_uwp.h
@@ -15,30 +15,27 @@
 #ifndef STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
 #define STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
 
+#include <agile.h>
+
+#include <string>
+#include <unordered_map>
+
 #include "starboard/configuration.h"
+#include "starboard/mutex.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/command_line.h"
+#include "starboard/shared/uwp/winrt_workaround.h"
 #include "starboard/types.h"
 #include "starboard/window.h"
 
-namespace __winRT {
-// TODO: without this, we get the following error at CoreApplication::Run:
-// 'long __winRT::__getActivationFactoryByPCWSTR(i
-//  void *,Platform::Guid &,void **)':
-//  cannot convert argument 1 from 'const wchar_t [46]' to 'void *'
-inline long __getActivationFactoryByPCWSTR(const wchar_t* a,
-                                           Platform::Guid& b,
-                                           void** c) {
-  return __getActivationFactoryByPCWSTR(
-      static_cast<void*>(const_cast<wchar_t*>(a)), b, c);
-}
-}  // namespace __winRT
-
 namespace starboard {
 namespace shared {
 namespace uwp {
 
+// Returns win32's GetModuleFileName(). For cases where we'd like an argv[0].
+std::string GetArgvZero();
+
 class ApplicationUwp : public shared::starboard::Application {
  public:
   ApplicationUwp();
@@ -48,10 +45,7 @@
     return static_cast<ApplicationUwp*>(shared::starboard::Application::Get());
   }
 
-// Do not use the macro from windows.h.
-#undef CreateWindow
-#undef CreateWindowW
-  SbWindow CreateWindow(const SbWindowOptions* options);
+  SbWindow CreateWindowForUWP(const SbWindowOptions* options);
 
   bool DestroyWindow(SbWindow window);
 
@@ -59,6 +53,34 @@
     shared::starboard::Application::DispatchStart();
   }
 
+  // public for IFrameworkView subclass
+  void SetCommandLine(int argc, const char** argv) {
+    shared::starboard::Application::SetCommandLine(argc, argv);
+  }
+
+  // public for IFrameworkView subclass
+  bool DispatchAndDelete(Application::Event* event) {
+    return shared::starboard::Application::DispatchAndDelete(event);
+  }
+
+  Platform::Agile<Windows::UI::Core::CoreWindow> GetCoreWindow() const {
+    return core_window_;
+  }
+
+  // public for IFrameworkView subclass
+  void SetCoreWindow(Windows::UI::Core::CoreWindow^ window) {
+    core_window_ = window;
+  }
+
+  void OnKeyEvent(Windows::UI::Core::CoreWindow^ sender,
+      Windows::UI::Core::KeyEventArgs^ args, bool up);
+
+  void Inject(Event* event) SB_OVERRIDE;
+
+  void SetStartLink(const char* link) SB_OVERRIDE {
+    shared::starboard::Application::SetStartLink(link);
+  }
+
  private:
   // --- Application overrides ---
   bool IsStartImmediate() SB_OVERRIDE { return false; }
@@ -66,7 +88,6 @@
   void Teardown() SB_OVERRIDE;
   Event* GetNextEvent() SB_OVERRIDE;
   bool DispatchNextEvent() SB_OVERRIDE;
-  void Inject(Event* event) SB_OVERRIDE;
   void InjectTimedEvent(TimedEvent* timed_event) SB_OVERRIDE;
   void CancelTimedEvent(SbEventId event_id) SB_OVERRIDE;
   TimedEvent* GetNextDueTimedEvent() SB_OVERRIDE;
@@ -74,6 +95,12 @@
 
   // The single open window, if any.
   SbWindow window_;
+  Platform::Agile<Windows::UI::Core::CoreWindow> core_window_;
+
+  Mutex mutex_;
+  // Locked by mutex_
+  std::unordered_map<SbEventId, Windows::System::Threading::ThreadPoolTimer^>
+    timer_event_map_;
 };
 
 }  // namespace uwp
diff --git a/src/starboard/shared/uwp/application_uwp_key_event.cc b/src/starboard/shared/uwp/application_uwp_key_event.cc
new file mode 100644
index 0000000..be64071
--- /dev/null
+++ b/src/starboard/shared/uwp/application_uwp_key_event.cc
@@ -0,0 +1,300 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/uwp/application_uwp.h"
+
+#include "starboard/event.h"
+#include "starboard/input.h"
+#include "starboard/key.h"
+
+using Windows::UI::Core::CoreWindow;
+using Windows::UI::Core::KeyEventArgs;
+using Windows::UI::Core::CoreVirtualKeyStates;
+using Windows::Security::ExchangeActiveSyncProvisioning::
+    EasClientDeviceInformation;
+using Windows::System::VirtualKey;
+
+namespace {
+
+SbKey VirtualKeyToSbKey(VirtualKey key) {
+  switch (key) {
+    case VirtualKey::None:
+    case VirtualKey::NavigationView:
+    case VirtualKey::NavigationMenu:
+    case VirtualKey::NavigationUp:
+    case VirtualKey::NavigationDown:
+    case VirtualKey::NavigationLeft:
+    case VirtualKey::NavigationRight:
+    case VirtualKey::NavigationAccept:
+    case VirtualKey::NavigationCancel:
+      return kSbKeyUnknown;
+    case VirtualKey::Cancel: return kSbKeyCancel;
+    case VirtualKey::Back: return kSbKeyBack;
+    case VirtualKey::Tab: return kSbKeyTab;
+    case VirtualKey::Clear: return kSbKeyClear;
+    case VirtualKey::Enter: return kSbKeyReturn;
+    case VirtualKey::Shift: return kSbKeyShift;
+    case VirtualKey::Control: return kSbKeyControl;
+    case VirtualKey::Menu: return kSbKeyMenu;
+    case VirtualKey::Pause: return kSbKeyPause;
+    case VirtualKey::CapitalLock: return kSbKeyCapital;
+    // Hangul and Kana have the same VirtualKey constant
+    case VirtualKey::Kana: return kSbKeyKana;
+    case VirtualKey::Junja: return kSbKeyJunja;
+    case VirtualKey::Final: return kSbKeyFinal;
+    // Hanja and Kanji have the same VirtualKey constant
+    case VirtualKey::Hanja: return kSbKeyHanja;
+    case VirtualKey::Escape: return kSbKeyEscape;
+    case VirtualKey::Convert: return kSbKeyConvert;
+    case VirtualKey::NonConvert: return kSbKeyNonconvert;
+    case VirtualKey::Accept: return kSbKeyAccept;
+    case VirtualKey::ModeChange: return kSbKeyModechange;
+    case VirtualKey::Space: return kSbKeySpace;
+    case VirtualKey::PageUp: return kSbKeyPrior;
+    case VirtualKey::PageDown: return kSbKeyNext;
+    case VirtualKey::End: return kSbKeyEnd;
+    case VirtualKey::Home: return kSbKeyHome;
+    case VirtualKey::Left: return kSbKeyLeft;
+    case VirtualKey::Up: return kSbKeyUp;
+    case VirtualKey::Right: return kSbKeyRight;
+    case VirtualKey::Down: return kSbKeyDown;
+    case VirtualKey::Select: return kSbKeySelect;
+    case VirtualKey::Print: return kSbKeyPrint;
+    case VirtualKey::Execute: return kSbKeyExecute;
+    case VirtualKey::Snapshot: return kSbKeySnapshot;
+    case VirtualKey::Insert: return kSbKeyInsert;
+    case VirtualKey::Delete: return kSbKeyDelete;
+    case VirtualKey::Number0: return kSbKey0;
+    case VirtualKey::Number1: return kSbKey1;
+    case VirtualKey::Number2: return kSbKey2;
+    case VirtualKey::Number3: return kSbKey3;
+    case VirtualKey::Number4: return kSbKey4;
+    case VirtualKey::Number5: return kSbKey5;
+    case VirtualKey::Number6: return kSbKey6;
+    case VirtualKey::Number7: return kSbKey7;
+    case VirtualKey::Number8: return kSbKey8;
+    case VirtualKey::Number9: return kSbKey9;
+    case VirtualKey::A: return kSbKeyA;
+    case VirtualKey::B: return kSbKeyB;
+    case VirtualKey::C: return kSbKeyC;
+    case VirtualKey::D: return kSbKeyD;
+    case VirtualKey::E: return kSbKeyE;
+    case VirtualKey::F: return kSbKeyF;
+    case VirtualKey::G: return kSbKeyG;
+    case VirtualKey::H: return kSbKeyH;
+    case VirtualKey::I: return kSbKeyI;
+    case VirtualKey::J: return kSbKeyJ;
+    case VirtualKey::K: return kSbKeyK;
+    case VirtualKey::L: return kSbKeyL;
+    case VirtualKey::M: return kSbKeyM;
+    case VirtualKey::N: return kSbKeyN;
+    case VirtualKey::O: return kSbKeyO;
+    case VirtualKey::P: return kSbKeyP;
+    case VirtualKey::Q: return kSbKeyQ;
+    case VirtualKey::R: return kSbKeyR;
+    case VirtualKey::S: return kSbKeyS;
+    case VirtualKey::T: return kSbKeyT;
+    case VirtualKey::U: return kSbKeyU;
+    case VirtualKey::V: return kSbKeyV;
+    case VirtualKey::W: return kSbKeyW;
+    case VirtualKey::X: return kSbKeyX;
+    case VirtualKey::Y: return kSbKeyY;
+    case VirtualKey::Z: return kSbKeyZ;
+    case VirtualKey::LeftWindows: return kSbKeyLwin;
+    case VirtualKey::RightWindows: return kSbKeyRwin;
+    case VirtualKey::Application: return kSbKeyApps;
+    case VirtualKey::Sleep: return kSbKeySleep;
+    case VirtualKey::NumberPad0: return kSbKeyNumpad0;
+    case VirtualKey::NumberPad1: return kSbKeyNumpad1;
+    case VirtualKey::NumberPad2: return kSbKeyNumpad2;
+    case VirtualKey::NumberPad3: return kSbKeyNumpad3;
+    case VirtualKey::NumberPad4: return kSbKeyNumpad4;
+    case VirtualKey::NumberPad5: return kSbKeyNumpad5;
+    case VirtualKey::NumberPad6: return kSbKeyNumpad6;
+    case VirtualKey::NumberPad7: return kSbKeyNumpad7;
+    case VirtualKey::NumberPad8: return kSbKeyNumpad8;
+    case VirtualKey::NumberPad9: return kSbKeyNumpad9;
+    case VirtualKey::Multiply: return kSbKeyMultiply;
+    case VirtualKey::Add: return kSbKeyAdd;
+    case VirtualKey::Separator: return kSbKeySeparator;
+    case VirtualKey::Subtract: return kSbKeySubtract;
+    case VirtualKey::Decimal: return kSbKeyDecimal;
+    case VirtualKey::Divide: return kSbKeyDivide;
+    case VirtualKey::F1: return kSbKeyF1;
+    case VirtualKey::F2: return kSbKeyF2;
+    case VirtualKey::F3: return kSbKeyF3;
+    case VirtualKey::F4: return kSbKeyF4;
+    case VirtualKey::F5: return kSbKeyF5;
+    case VirtualKey::F6: return kSbKeyF6;
+    case VirtualKey::F7: return kSbKeyF7;
+    case VirtualKey::F8: return kSbKeyF8;
+    case VirtualKey::F9: return kSbKeyF9;
+    case VirtualKey::F10: return kSbKeyF10;
+    case VirtualKey::F11: return kSbKeyF11;
+    case VirtualKey::F12: return kSbKeyF12;
+    case VirtualKey::F13: return kSbKeyF13;
+    case VirtualKey::F14: return kSbKeyF14;
+    case VirtualKey::F15: return kSbKeyF15;
+    case VirtualKey::F16: return kSbKeyF16;
+    case VirtualKey::F17: return kSbKeyF17;
+    case VirtualKey::F18: return kSbKeyF18;
+    case VirtualKey::F19: return kSbKeyF19;
+    case VirtualKey::F20: return kSbKeyF20;
+    case VirtualKey::F21: return kSbKeyF21;
+    case VirtualKey::F22: return kSbKeyF22;
+    case VirtualKey::F23: return kSbKeyF23;
+    case VirtualKey::F24: return kSbKeyF24;
+    case VirtualKey::NumberKeyLock: return kSbKeyNumlock;
+    case VirtualKey::Scroll: return kSbKeyScroll;
+    case VirtualKey::LeftShift: return kSbKeyLshift;
+    case VirtualKey::RightShift: return kSbKeyRshift;
+    case VirtualKey::LeftControl: return kSbKeyLcontrol;
+    case VirtualKey::RightControl: return kSbKeyRcontrol;
+    case VirtualKey::LeftMenu: return kSbKeyLmenu;
+    case VirtualKey::RightMenu: return kSbKeyRmenu;
+    case VirtualKey::GoBack: return kSbKeyBrowserBack;
+    case VirtualKey::GoForward: return kSbKeyBrowserForward;
+    case VirtualKey::Refresh: return kSbKeyBrowserRefresh;
+    case VirtualKey::Stop: return kSbKeyBrowserStop;
+    case VirtualKey::Search: return kSbKeyBrowserSearch;
+    case VirtualKey::Favorites: return kSbKeyBrowserFavorites;
+    case VirtualKey::GoHome: return kSbKeyBrowserHome;
+    case VirtualKey::LeftButton: return kSbKeyMouse1;
+    case VirtualKey::RightButton: return kSbKeyMouse2;
+    case VirtualKey::MiddleButton: return kSbKeyMouse3;
+    case VirtualKey::XButton1: return kSbKeyMouse4;
+    case VirtualKey::XButton2: return kSbKeyMouse5;
+    case VirtualKey::GamepadA: return kSbKeyGamepad1;
+    case VirtualKey::GamepadB: return kSbKeyGamepad2;
+    case VirtualKey::GamepadX: return kSbKeyGamepad3;
+    case VirtualKey::GamepadY: return kSbKeyGamepad4;
+    case VirtualKey::GamepadRightShoulder: return kSbKeyGamepadRightBumper;
+    case VirtualKey::GamepadLeftShoulder: return kSbKeyGamepadLeftBumper;
+    case VirtualKey::GamepadLeftTrigger: return kSbKeyGamepadLeftTrigger;
+    case VirtualKey::GamepadRightTrigger: return kSbKeyGamepadRightTrigger;
+    case VirtualKey::GamepadDPadUp: return kSbKeyGamepadDPadUp;
+    case VirtualKey::GamepadDPadDown: return kSbKeyGamepadDPadDown;
+    case VirtualKey::GamepadDPadLeft: return kSbKeyGamepadDPadLeft;
+    case VirtualKey::GamepadDPadRight: return kSbKeyGamepadDPadRight;
+    case VirtualKey::GamepadMenu: return kSbKeyGamepad6;
+    case VirtualKey::GamepadView: return kSbKeyGamepad5;
+    case VirtualKey::GamepadLeftThumbstickButton:
+      return kSbKeyGamepadLeftStick;
+    case VirtualKey::GamepadRightThumbstickButton:
+      return kSbKeyGamepadRightStick;
+    case VirtualKey::GamepadLeftThumbstickUp:
+      return kSbKeyGamepadLeftStickUp;
+    case VirtualKey::GamepadLeftThumbstickDown:
+      return kSbKeyGamepadLeftStickDown;
+    case VirtualKey::GamepadLeftThumbstickRight:
+      return kSbKeyGamepadLeftStickRight;
+    case VirtualKey::GamepadLeftThumbstickLeft:
+      return kSbKeyGamepadLeftStickLeft;
+    case VirtualKey::GamepadRightThumbstickUp:
+      return kSbKeyGamepadRightStickUp;
+    case VirtualKey::GamepadRightThumbstickDown:
+      return kSbKeyGamepadRightStickDown;
+    case VirtualKey::GamepadRightThumbstickRight:
+      return kSbKeyGamepadRightStickRight;
+    case VirtualKey::GamepadRightThumbstickLeft:
+      return kSbKeyGamepadRightStickLeft;
+    default:
+      return kSbKeyUnknown;
+  }
+}
+
+// Returns true if a given VirtualKey is currently being held down.
+bool IsDown(CoreWindow^ sender, VirtualKey key) {
+  switch (sender->GetKeyState(key)) {
+    case CoreVirtualKeyStates::Down:
+    case CoreVirtualKeyStates::Locked:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
+namespace starboard {
+namespace shared {
+namespace uwp {
+
+void ApplicationUwp::OnKeyEvent(
+    CoreWindow^ sender, KeyEventArgs^ args, bool up) {
+  args->Handled = true;
+  SbInputData* data = new SbInputData();
+  SbMemorySet(data, 0, sizeof(*data));
+
+  data->window = window_;
+  data->device_type = kSbInputDeviceTypeKeyboard;
+  // TODO: Devices might have colliding hashcodes. Some other unique int
+  // ID generation tool would be better.
+  auto device_information = ref new EasClientDeviceInformation();
+  Platform::String^ device_id_string = device_information->Id.ToString();
+  data->device_id = device_id_string->GetHashCode();
+  data->key = VirtualKeyToSbKey(args->VirtualKey);
+
+  if (up) {
+    data->type = kSbInputEventTypeUnpress;
+  } else {
+    data->type = kSbInputEventTypePress;
+  }
+
+  // Build up key_modifiers
+  if (IsDown(sender, VirtualKey::Menu) ||
+      IsDown(sender, VirtualKey::LeftMenu) ||
+      IsDown(sender, VirtualKey::RightMenu)) {
+    data->key_modifiers |= kSbKeyModifiersAlt;
+  }
+  if (IsDown(sender, VirtualKey::Control) ||
+      IsDown(sender, VirtualKey::LeftControl) ||
+      IsDown(sender, VirtualKey::RightControl)) {
+    data->key_modifiers |= kSbKeyModifiersCtrl;
+  }
+  if (IsDown(sender, VirtualKey::LeftWindows) ||
+      IsDown(sender, VirtualKey::RightWindows)) {
+    data->key_modifiers |= kSbKeyModifiersMeta;
+  }
+  if (IsDown(sender, VirtualKey::Shift) ||
+      IsDown(sender, VirtualKey::LeftShift) ||
+      IsDown(sender, VirtualKey::RightShift)) {
+    data->key_modifiers |= kSbKeyModifiersShift;
+  }
+
+  // Set key_location
+  switch (args->VirtualKey) {
+    case VirtualKey::LeftMenu:
+    case VirtualKey::LeftControl:
+    case VirtualKey::LeftWindows:
+    case VirtualKey::LeftShift:
+      data->key_location = kSbKeyLocationLeft;
+      break;
+    case VirtualKey::RightMenu:
+    case VirtualKey::RightControl:
+    case VirtualKey::RightWindows:
+    case VirtualKey::RightShift:
+      data->key_location = kSbKeyLocationRight;
+      break;
+    default:
+      break;
+  }
+
+  DispatchAndDelete(new Event(kSbEventTypeInput, data,
+      &Application::DeleteDestructor<SbInputData>));
+}
+
+}  // namespace uwp
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/uwp/async_utils.h b/src/starboard/shared/uwp/async_utils.h
new file mode 100644
index 0000000..7e01f82
--- /dev/null
+++ b/src/starboard/shared/uwp/async_utils.h
@@ -0,0 +1,50 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_UWP_ASYNC_UTILS_H_
+#define STARBOARD_SHARED_UWP_ASYNC_UTILS_H_
+
+#include <windows.h>
+
+#include <ppltasks.h>
+
+#include "starboard/log.h"
+
+using Windows::Foundation::IAsyncOperation;
+
+namespace starboard {
+namespace shared {
+namespace uwp {
+
+template <typename TResult>
+TResult WaitForResult(IAsyncOperation<TResult>^ operation) {
+  using concurrency::task_continuation_context;
+  HANDLE event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+  concurrency::create_task(operation,
+                           task_continuation_context::use_arbitrary())
+      .then([&event](TResult result) {
+        BOOL success = SetEvent(event);
+        SB_DCHECK(success);
+      }, task_continuation_context::use_arbitrary());
+  DWORD return_value = WaitForSingleObject(event, INFINITE);
+  SB_DCHECK(return_value == WAIT_OBJECT_0);
+  CloseHandle(event);
+  return operation->GetResults();
+}
+
+}  // namespace uwp
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_UWP_ASYNC_UTILS_H_
diff --git a/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp b/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp
new file mode 100644
index 0000000..9da1070
--- /dev/null
+++ b/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp
@@ -0,0 +1,46 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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': {
+    'sb_pedantic_warnings': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'cobalt_platform',
+      'type': 'static_library',
+      'sources': [
+        'xhr_modify_headers.cc',
+      ],
+      'msvs_settings': {
+        'VCCLCompilerTool': {
+          'AdditionalOptions': [
+          '/ZW',           # Windows Runtime
+          '/ZW:nostdlib',  # Windows Runtime, no default #using
+          '/EHsx',         # C++ exceptions (required with /ZW)
+          '/FU"<(visual_studio_install_path)/lib/x86/store/references/platform.winmd"',
+          '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.FoundationContract/3.0.0.0/Windows.Foundation.FoundationContract.winmd"',
+          '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.UniversalApiContract/4.0.0.0/Windows.Foundation.UniversalApiContract.winmd"',
+          ],
+        },
+      },
+      'defines': [
+        # VS2017 always defines this for UWP apps
+        'WINAPI_FAMILY=WINAPI_FAMILY_APP',
+        # VS2017 always defines this for UWP apps
+        '__WRL_NO_DEFAULT_LIB__',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
new file mode 100644
index 0000000..9497a40
--- /dev/null
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
@@ -0,0 +1,162 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/xhr/xhr_modify_headers.h"
+
+#include <base/logging.h>
+#include "starboard/shared/uwp/async_utils.h"
+#include "starboard/shared/uwp/winrt_workaround.h"
+#include "starboard/shared/win32/wchar_utils.h"
+
+using Windows::Security::Authentication::Web::Core::
+    WebAuthenticationCoreManager;
+using Windows::Security::Authentication::Web::Core::WebTokenRequest;
+using Windows::Security::Authentication::Web::Core::WebTokenResponse;
+using Windows::Security::Authentication::Web::Core::WebTokenRequestResult;
+using Windows::Security::Authentication::Web::Core::WebTokenRequestStatus;
+using Windows::Security::Credentials::WebAccountProvider;
+using Windows::System::UserAuthenticationStatus;
+
+namespace sbwin32 = starboard::shared::win32;
+
+namespace {
+// The name of the header to send the STS token out on.
+const char kXauthHeaderName[] = "Authorization";
+
+// The prefix for the value of the Authorization header when there is an XAuth
+// token to attach to the request. The token itself should directly follow this
+// prefix.
+const char kXauthHeaderPrefix[] = "XBL3.0 x=";
+
+// The name of the header to detect on requests as a trigger to add an STS token
+// for the given RelyingPartyId (the value of the header). The header itself
+// will be removed from the outgoing request, and replaced with an Authorization
+// header with a valid STS token for the current primary user.
+const base::StringPiece kXauthTriggerHeaderName = "X-STS-RelyingPartyId";
+
+inline std::ostream& operator<<(std::ostream& os,
+                                const UserAuthenticationStatus& state) {
+  switch (state) {
+    case UserAuthenticationStatus::Unauthenticated:
+      os << "Unauthenticated";
+      break;
+    case UserAuthenticationStatus::LocallyAuthenticated:
+      os << "LocallyAuthenticated";
+      break;
+    case UserAuthenticationStatus::RemotelyAuthenticated:
+      os << "RemotelyAuthenticated";
+      break;
+    default:
+      os << "Unknown";
+  }
+  return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+                                const WebTokenRequestStatus& status) {
+  switch (status) {
+    case WebTokenRequestStatus::Success:
+      os << "Success";
+      break;
+    case WebTokenRequestStatus::AccountProviderNotAvailable:
+      os << "Account provider is not available.";
+      break;
+    case WebTokenRequestStatus::AccountSwitch:
+      os << "Account associated with the request was switched.";
+      break;
+    case WebTokenRequestStatus::ProviderError:
+      os << "Provider Error.  See Provider's documentation.";
+      break;
+    case WebTokenRequestStatus::UserCancel:
+      os << "User Cancel";
+      break;
+    case WebTokenRequestStatus::UserInteractionRequired:
+      os << "User interaction is required.  Try the request with "
+            "RequestTokenAsync";
+      break;
+    default:
+      os << "Unknown case";
+  }
+  return os;
+}
+
+bool PopulateToken(const std::string& relying_party, std::string* out) {
+  using starboard::shared::uwp::WaitForResult;
+  DCHECK(out);
+  WebAccountProvider^ xbox_provider =
+      WaitForResult(WebAuthenticationCoreManager::FindAccountProviderAsync(
+          "https://xsts.auth.xboxlive.com"));
+  WebTokenRequest^ request = ref new WebTokenRequest(xbox_provider);
+  Platform::String^ relying_party_cx =
+    sbwin32::stringToPlatformString(relying_party);
+  request->Properties->Insert("Url", relying_party_cx);
+  request->Properties->Insert("Target", "xboxlive.signin");
+  request->Properties->Insert("Policy", "DELEGATION");
+
+  WebTokenRequestResult^ token_result = WaitForResult(
+      WebAuthenticationCoreManager::GetTokenSilentlyAsync(request));
+  if (token_result->ResponseStatus ==
+      WebTokenRequestStatus::UserInteractionRequired) {
+    token_result =
+        WaitForResult(WebAuthenticationCoreManager::RequestTokenAsync(request));
+  }
+
+  if (token_result->ResponseStatus == WebTokenRequestStatus::Success) {
+    SB_DCHECK(token_result->ResponseData->Size == 1);
+    if (token_result->ResponseData->Size != 1) {
+      *out = "";
+      return false;
+    }
+    WebTokenResponse^ token_response = token_result->ResponseData->GetAt(0);
+    *out = sbwin32::platformStringToString(token_response->Token);
+    return true;
+  } else {
+    SB_DLOG(INFO) << "Response Status " << token_result->ResponseStatus;
+    if (token_result->ResponseError) {
+      unsigned int error_code = token_result->ResponseError->ErrorCode;
+      Platform::String^ message = token_result->ResponseError->ErrorMessage;
+      SB_DLOG(INFO) << "Error code: " << error_code;
+      SB_DLOG(INFO) << "Error message: "
+        << sbwin32::platformStringToString(message);
+    }
+    *out = "";
+  }
+
+  return false;
+}
+}  // namespace
+
+namespace cobalt {
+namespace xhr {
+
+void CobaltXhrModifyHeader(net::HttpRequestHeaders* headers) {
+  DCHECK(headers);
+
+  std::string relying_party;
+  bool trigger_header_found = headers->GetHeader(kXauthTriggerHeaderName,
+    &relying_party);
+
+  if (!trigger_header_found) {
+    return;
+  }
+  std::string out_string;
+  if (!PopulateToken(relying_party, &out_string)) {
+    return;
+  }
+  headers->RemoveHeader(kXauthTriggerHeaderName);
+  headers->SetHeader(kXauthHeaderName, out_string);
+}
+
+}  // namespace xhr
+}  // namespace cobalt
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc
new file mode 100644
index 0000000..138579a
--- /dev/null
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/xhr/xhr_modify_headers.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace uwp {
+namespace cobalt {
+
+using ::cobalt::xhr::CobaltXhrModifyHeader;
+using net::HttpRequestHeaders;
+
+TEST(XHRModificationTest, EmptyHeaders) {
+  HttpRequestHeaders headers;
+  CobaltXhrModifyHeader(&headers);
+  EXPECT_TRUE(headers.IsEmpty());
+}
+
+TEST(XHRModificationTest, HeaderNotFound) {
+  HttpRequestHeaders headers;
+  headers.SetHeader("Authorization", "ABC");
+  CobaltXhrModifyHeader(&headers);
+  EXPECT_FALSE(!headers.IsEmpty());
+  std::string headers_serialized = headers.ToString();
+  EXPECT_STREQ(headers_serialized.c_str(), "Authorization: ABC\r\n\r\n");
+}
+
+TEST(XHRModificationTest, HeaderReplaced) {
+  HttpRequestHeaders headers;
+  static const char* kXauthTriggerHeaderName = "X-STS-RelyingPartyId";
+  headers.SetHeader(kXauthTriggerHeaderName, "ABC");
+  EXPECT_TRUE(headers.HasHeader(kXauthTriggerHeaderName));
+  CobaltXhrModifyHeader(&headers);
+  EXPECT_FALSE(headers.HasHeader(kXauthTriggerHeaderName));
+  std::string headers_serialized = headers.ToString();
+  EXPECT_TRUE(headers_serialized.find("Authorization: XBL3.0 x=") !=
+              std::string::npos);
+}
+
+TEST(XHRModificationTest, MultipleHeaders) {
+  HttpRequestHeaders headers;
+  static const char* kXauthTriggerHeaderName = "X-STS-RelyingPartyId";
+  headers.SetHeader("H1", "h1");
+  headers.SetHeader("H2", "h2");
+  headers.SetHeader("H3", "h3");
+  EXPECT_TRUE(headers.HasHeader(kXauthTriggerHeaderName));
+  CobaltXhrModifyHeader(&headers);
+  EXPECT_TRUE(headers.HasHeader("H1"));
+  EXPECT_TRUE(headers.HasHeader("H2"));
+  EXPECT_TRUE(headers.HasHeader("H3"));
+  EXPECT_FALSE(headers.HasHeader(kXauthTriggerHeaderName));
+  std::string headers_serialized = headers.ToString();
+  EXPECT_TRUE(headers_serialized.find("Authorization: XBL3.0 x=") !=
+              std::string::npos);
+}
+
+}  // namespace cobalt
+}  // namespace uwp
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi
new file mode 100644
index 0000000..9e107a2
--- /dev/null
+++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -0,0 +1,36 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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_dependent_files': [
+      'application_uwp.cc',
+      'application_uwp.h',
+      'application_uwp_key_event.cc',
+      'async_utils.h',
+      'system_get_property.cc',
+      'window_create.cc',
+      'window_destroy.cc',
+      'window_get_platform_handle.cc',
+      'window_get_size.cc',
+      'window_set_default_options.cc',
+      'window_internal.cc',
+      'window_internal.h',
+      'winrt_workaround.h',
+      '<(DEPTH)/starboard/shared/starboard/system_request_pause.cc',
+      '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+      '<(DEPTH)/starboard/shared/starboard/system_request_suspend.cc',
+      '<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
+    ]
+  }
+}
diff --git a/src/starboard/shared/uwp/system_get_property.cc b/src/starboard/shared/uwp/system_get_property.cc
new file mode 100644
index 0000000..85e85c5
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_property.cc
@@ -0,0 +1,124 @@
+// Copyright 2017 Google Inc. All Rights Reserved.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+//     http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT 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 <string>

+

+#include "starboard/log.h"

+#include "starboard/shared/uwp/application_uwp.h"

+#include "starboard/shared/win32/wchar_utils.h"

+#include "starboard/string.h"

+

+using Windows::Security::ExchangeActiveSyncProvisioning::

+    EasClientDeviceInformation;

+using Windows::System::Profile::AnalyticsInfo;

+using Windows::System::Profile::AnalyticsVersionInfo;

+

+namespace sbwin32 = starboard::shared::win32;

+

+namespace {

+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;

+  }

+

+  using sbwin32::platformStringToString;

+

+  switch (property_id) {

+    case kSbSystemPropertyChipsetModelNumber:

+    case kSbSystemPropertyModelYear:

+    case kSbSystemPropertyNetworkOperatorName:

+    case kSbSystemPropertySpeechApiKey:

+      return false;

+    case kSbSystemPropertyBrandName: {

+      EasClientDeviceInformation^ current_device_info =

+          ref new EasClientDeviceInformation();

+      std::string brand_name =

+          platformStringToString(current_device_info->SystemManufacturer);

+      if (brand_name.empty()) {

+        return false;

+      }

+      return CopyStringAndTestIfSuccess(out_value, value_length,

+                                        brand_name.c_str());

+    }

+    case kSbSystemPropertyFirmwareVersion: {

+      EasClientDeviceInformation ^ current_device_info =

+          ref new EasClientDeviceInformation();

+      std::string firmware_version =

+          platformStringToString(current_device_info->SystemFirmwareVersion);

+      if (firmware_version.empty()) {

+        return false;

+      }

+      return CopyStringAndTestIfSuccess(out_value, value_length,

+                                        firmware_version.c_str());

+    }

+    case kSbSystemPropertyModelName: {

+      EasClientDeviceInformation ^ current_device_info =

+          ref new EasClientDeviceInformation();

+      std::string product_name =

+          platformStringToString(current_device_info->SystemProductName);

+      product_name.erase(

+          std::remove(product_name.begin(), product_name.end(), ' '),

+          product_name.end());

+

+      return CopyStringAndTestIfSuccess(out_value, value_length,

+                                        product_name.c_str());

+    }

+    case kSbSystemPropertyFriendlyName: {

+      EasClientDeviceInformation^ current_device_info =

+          ref new EasClientDeviceInformation();

+      std::string friendly_name =

+          platformStringToString(current_device_info->FriendlyName);

+      if (friendly_name.empty()) {

+        return false;

+      }

+      return CopyStringAndTestIfSuccess(out_value, value_length,

+                                        friendly_name.c_str());

+    }

+

+    case kSbSystemPropertyPlatformName: {

+      AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;

+      std::string platform_str =

+          starboard::shared::win32::platformStringToString(

+            version_info->DeviceFamily);

+      if (platform_str.empty()) {

+        return false;

+      }

+      return CopyStringAndTestIfSuccess(out_value, value_length,

+                                        platform_str.c_str());

+    }

+    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/shared/uwp/window_create.cc b/src/starboard/shared/uwp/window_create.cc
index 70ea0cc..9f9a0fa 100644
--- a/src/starboard/shared/uwp/window_create.cc
+++ b/src/starboard/shared/uwp/window_create.cc
@@ -17,5 +17,6 @@
 #include "starboard/shared/uwp/application_uwp.h"
 
 SbWindow SbWindowCreate(const SbWindowOptions* options) {
-  return starboard::shared::uwp::ApplicationUwp::Get()->CreateWindow(options);
+  return starboard::shared::uwp::ApplicationUwp::Get()->CreateWindowForUWP(
+      options);
 }
diff --git a/src/starboard/shared/uwp/window_get_platform_handle.cc b/src/starboard/shared/uwp/window_get_platform_handle.cc
index 7e19456..ba780bd 100644
--- a/src/starboard/shared/uwp/window_get_platform_handle.cc
+++ b/src/starboard/shared/uwp/window_get_platform_handle.cc
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <EGL/egl.h>
+
+#include "starboard/shared/uwp/window_internal.h"
 #include "starboard/window.h"
 
 void* SbWindowGetPlatformHandle(SbWindow window) {
@@ -19,5 +22,5 @@
     return NULL;
   }
 
-  return static_cast<void*>(window);
+  return reinterpret_cast<EGLNativeWindowType>(window->egl_native_window());
 }
diff --git a/src/starboard/shared/uwp/window_internal.cc b/src/starboard/shared/uwp/window_internal.cc
index 8fb0fc7..0b34b01 100644
--- a/src/starboard/shared/uwp/window_internal.cc
+++ b/src/starboard/shared/uwp/window_internal.cc
@@ -12,11 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <EGL/egl.h>
+#include <windows.h>
+
+// For __getActivationFactoryByPCWSTR custom definition.
+#include "starboard/shared/uwp/application_uwp.h"
 #include "starboard/shared/uwp/window_internal.h"
 
+using Windows::UI::Core::CoreWindow;
+
 // TODO: Make sure the width and height here behave well given that we want
 // 1080 video, but perhaps 4k UI where applicable.
 SbWindowPrivate::SbWindowPrivate(const SbWindowOptions* /*options*/)
-    : width(1920), height(1080) {}
+    : width(1920),
+      height(1080),
+      output_width(1920),
+      output_height(1080) {
+  egl_native_window_ = reinterpret_cast<EGLNativeWindowType>(
+      starboard::shared::uwp::ApplicationUwp::Get()->GetCoreWindow().Get());
+}
 
 SbWindowPrivate::~SbWindowPrivate() {}
diff --git a/src/starboard/shared/uwp/window_internal.h b/src/starboard/shared/uwp/window_internal.h
index 92d567b..ef0777e 100644
--- a/src/starboard/shared/uwp/window_internal.h
+++ b/src/starboard/shared/uwp/window_internal.h
@@ -15,6 +15,8 @@
 #ifndef STARBOARD_SHARED_UWP_WINDOW_INTERNAL_H_
 #define STARBOARD_SHARED_UWP_WINDOW_INTERNAL_H_
 
+#include <EGL/egl.h>
+
 #include "starboard/atomic.h"
 #include "starboard/time.h"
 #include "starboard/window.h"
@@ -33,6 +35,8 @@
   // indicates success.
   bool GetRefreshRate(uint64_t* refresh_rate);
 
+  EGLNativeWindowType egl_native_window() const { return egl_native_window_; }
+
   // The width of this window.
   int width;
 
@@ -46,6 +50,8 @@
   int output_height;
 
  private:
+  EGLNativeWindowType egl_native_window_;
+
   // Open and Initialize the video output port.
   void SetupVideo();
 
diff --git a/src/starboard/shared/uwp/winrt_workaround.h b/src/starboard/shared/uwp/winrt_workaround.h
new file mode 100644
index 0000000..e75b766
--- /dev/null
+++ b/src/starboard/shared/uwp/winrt_workaround.h
@@ -0,0 +1,31 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_UWP_WINRT_WORKAROUND_H_
+#define STARBOARD_SHARED_UWP_WINRT_WORKAROUND_H_
+
+namespace __winRT {
+// TODO: without this, we get the following error at CoreApplication::Run:
+// 'long __winRT::__getActivationFactoryByPCWSTR(i
+//  void *,Platform::Guid &,void **)':
+//  cannot convert argument 1 from 'const wchar_t [46]' to 'void *'
+inline long __getActivationFactoryByPCWSTR(const wchar_t* a,
+                                           Platform::Guid& b,
+                                           void** c) {
+  return __getActivationFactoryByPCWSTR(
+      static_cast<void*>(const_cast<wchar_t*>(a)), b, c);
+}
+}  // namespace __winRT
+
+#endif  // STARBOARD_SHARED_UWP_WINRT_WORKAROUND_H_
diff --git a/src/starboard/shared/wayland/application_wayland.cc b/src/starboard/shared/wayland/application_wayland.cc
index e054624..06b2a28 100644
--- a/src/starboard/shared/wayland/application_wayland.cc
+++ b/src/starboard/shared/wayland/application_wayland.cc
@@ -39,15 +39,17 @@
 
 // Tizen application engine using the generic queue and a tizen implementation.
 
-ApplicationWayland::ApplicationWayland()
-    : seat_(NULL),
+ApplicationWayland::ApplicationWayland(float video_pixel_ratio)
+    : video_pixel_ratio_(video_pixel_ratio),
+      seat_(NULL),
       keyboard_(NULL),
       key_repeat_event_id_(kSbEventIdInvalid),
-      key_repeat_interval_(kKeyHoldTime) {}
+      key_repeat_interval_(kKeyHoldTime),
+      key_modifiers_(0) {}
 
 SbWindow ApplicationWayland::CreateWindow(const SbWindowOptions* options) {
   SB_DLOG(INFO) << "CreateWindow";
-  SbWindow window = new SbWindowPrivate(options);
+  SbWindow window = new SbWindowPrivate(options, video_pixel_ratio_);
   window_ = window;
 
 // Video Plane
@@ -256,11 +258,11 @@
   SbMemorySet(data, 0, sizeof(*data));
   data->window = window_;
   data->type = (state == 0 ? kSbInputEventTypeUnpress : kSbInputEventTypePress);
-  data->device_type = kSbInputDeviceTypeRemote;  // device_type;
+  data->device_type = kSbInputDeviceTypeRemote;
   data->device_id = 1;                           // kKeyboardDeviceId;
   data->key = KeyCodeToSbKey(key);
   data->key_location = KeyCodeToSbKeyLocation(key);
-  data->key_modifiers = 0;  // modifiers;
+  data->key_modifiers = key_modifiers_;
   Inject(new Event(kSbEventTypeInput, data,
                    &Application::DeleteDestructor<SbInputData>));
 
@@ -286,16 +288,16 @@
     wl_shell_surface_set_toplevel(window_->shell_surface);
 }
 
-void ApplicationWayland::Pause() {
-  Application::Pause(NULL, NULL);
+void ApplicationWayland::Pause(void* context, EventHandledCallback callback) {
+  Application::Pause(context, callback);
 
   ScopedLock lock(observers_mutex_);
   std::for_each(observers_.begin(), observers_.end(),
                 [](StateObserver* i) { i->OnAppPause(); });
 }
 
-void ApplicationWayland::Unpause() {
-  Application::Unpause(NULL, NULL);
+void ApplicationWayland::Unpause(void* context, EventHandledCallback callback) {
+  Application::Unpause(context, callback);
 
   ScopedLock lock(observers_mutex_);
   std::for_each(observers_.begin(), observers_.end(),
@@ -317,6 +319,14 @@
   observers_.erase(it);
 }
 
+void ApplicationWayland::Deeplink(char* payload) {
+  const size_t payload_size = strlen(payload) + 1;
+  char* copied_payload = new char[payload_size];
+  snprintf(copied_payload, payload_size, "%s", payload);
+  Inject(new Event(kSbEventTypeLink, copiedPayload,
+                   [](void* data) { delete[] reinterpret_cast<char*>(data); }));
+}
+
 }  // namespace wayland
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/shared/wayland/application_wayland.h b/src/starboard/shared/wayland/application_wayland.h
index e355c8b..c9ae756 100644
--- a/src/starboard/shared/wayland/application_wayland.h
+++ b/src/starboard/shared/wayland/application_wayland.h
@@ -36,7 +36,7 @@
 
 class ApplicationWayland : public shared::starboard::QueueApplication {
  public:
-  ApplicationWayland();
+  explicit ApplicationWayland(float video_pixel_ratio);
   ~ApplicationWayland() SB_OVERRIDE{};
 
   static ApplicationWayland* Get() {
@@ -54,6 +54,7 @@
   void SetPolicy(tizen_policy* policy) { tz_policy_ = policy; }
   tizen_policy* GetPolicy() { return tz_policy_; }
   void WindowRaise();
+  wl_display* GetWLDisplay() { return display_; }
 
   // input devices
   void SetKeyboard(wl_keyboard* keyboard) { keyboard_ = keyboard; }
@@ -62,13 +63,16 @@
   wl_seat* GetSeat() { return seat_; }
 
   // key event
+  void UpdateKeyModifiers(unsigned int modifiers) {
+    key_modifiers_ = modifiers;
+  }
   void CreateRepeatKey();
   void DeleteRepeatKey();
   void CreateKey(int key, int state, bool is_repeat);
 
   // state change
-  void Pause() SB_OVERRIDE;
-  void Unpause() SB_OVERRIDE;
+  void Pause(void* context, EventHandledCallback callback) SB_OVERRIDE;
+  void Unpause(void* context, EventHandledCallback callback) SB_OVERRIDE;
 
   // state change observer
   class StateObserver {
@@ -81,6 +85,9 @@
   void RegisterObserver(StateObserver* observer);
   void UnregisterObserver(StateObserver* observer);
 
+  // deeplink
+  void Deeplink(char* payload);
+
  protected:
   // --- Application overrides ---
   void Initialize() SB_OVERRIDE;
@@ -100,6 +107,7 @@
 
   // window
   SbWindow window_;
+  float video_pixel_ratio_;
   wl_display* display_;
   wl_compositor* compositor_;
   wl_shell* shell_;
@@ -112,6 +120,7 @@
   int key_repeat_state_;
   SbEventId key_repeat_event_id_;
   SbTime key_repeat_interval_;
+  unsigned int key_modifiers_;
 
   // wakeup event
   int wakeup_fd_;
diff --git a/src/starboard/shared/wayland/dev_input.h b/src/starboard/shared/wayland/dev_input.h
index 7bc5450..fbcbf11 100644
--- a/src/starboard/shared/wayland/dev_input.h
+++ b/src/starboard/shared/wayland/dev_input.h
@@ -349,7 +349,7 @@
                                 struct wl_surface* surface) {
   SB_DLOG(INFO) << "[Key] Keyboard lost focus";
   ApplicationWayland* wayland_window_ =
-      reinterpret_cast<ApplicationWayland*> data;
+      reinterpret_cast<ApplicationWayland*>(data);
   wayland_window_->DeleteRepeatKey();
 }
 
@@ -361,7 +361,7 @@
                               uint32_t state) {
   SB_DLOG(INFO) << "[Key] Key :" << key << ", state:" << state;
   ApplicationWayland* wayland_window_ =
-      reinterpret_cast<ApplicationWayland*> data;
+      reinterpret_cast<ApplicationWayland*>(data);
   if (key == KEY_LEFT || key == KEY_RIGHT || key == KEY_UP || key == KEY_DOWN) {
     wayland_window_->CreateKey(key, state, true);
   } else {
@@ -376,9 +376,23 @@
                                     uint32_t mods_latched,
                                     uint32_t mods_locked,
                                     uint32_t group) {
+  ApplicationWayland* wayland_window_ =
+      reinterpret_cast<ApplicationWayland*>(data);
+  // Convert to SbKeyModifiers.
+  unsigned int modifiers = kSbKeyModifiersNone;
+
+  if (mods_depressed & 1)
+    modifiers |= kSbKeyModifiersShift;
+  if (mods_depressed & 4)
+    modifiers |= kSbKeyModifiersCtrl;
+  if (mods_depressed & 8)
+    modifiers |= kSbKeyModifiersAlt;
+
   SB_DLOG(INFO) << "[Key] Modifiers depressed " << mods_depressed
                 << ", latched " << mods_latched << ", locked " << mods_locked
-                << ", group " << group;
+                << ", group " << group << ", key modifiers " << modifiers;
+
+  wayland_window_->UpdateKeyModifiers(modifiers);
 }
 
 static const struct wl_keyboard_listener keyboard_listener_ = {
@@ -391,14 +405,14 @@
                                    struct wl_seat* seat,
                                    unsigned int caps) {
   ApplicationWayland* wayland_window_ =
-      reinterpret_cast<ApplicationWayland*> data;
+      reinterpret_cast<ApplicationWayland*>(data);
   if (!wayland_window_->GetKeyboard()) {
     SB_DLOG(INFO) << "[Key] seat_handle_capabilities caps: " << caps;
     if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
       SB_DLOG(INFO) << "[Key] wl_seat_get_keyboard";
       wayland_window_->SetKeyboard(wl_seat_get_keyboard(seat));
       wl_keyboard_add_listener(wayland_window_->GetKeyboard(),
-                               &keyboard_listener, data);
+                               &keyboard_listener_, data);
     } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
       SB_DLOG(INFO) << "[Key] wl_keyboard_destroy";
       wl_keyboard_destroy(wayland_window_->GetKeyboard());
@@ -418,7 +432,7 @@
                               const char* interface,
                               uint32_t version) {
   ApplicationWayland* wayland_window_ =
-      reinterpret_cast<ApplicationWayland*> data;
+      reinterpret_cast<ApplicationWayland*>(data);
   if (strcmp(interface, "wl_compositor") == 0) {
     wayland_window_->SetCompositor(static_cast<wl_compositor*>(
         wl_registry_bind(registry, name, &wl_compositor_interface, 1)));
@@ -454,7 +468,7 @@
   SB_DLOG(INFO) << "shell_surface_configure width(" << width << "), height("
                 << height << ")";
   if (width && height) {
-    SbWindowPrivate* window = reinterpret_cast<SbWindowPrivate*> data;
+    SbWindowPrivate* window = reinterpret_cast<SbWindowPrivate*>(data);
     wl_egl_window_resize(window->egl_window, width, height, 0, 0);
   } else {
     SB_DLOG(INFO) << "width and height is 0. we don't resize that";
@@ -470,10 +484,17 @@
                                      struct tizen_visibility* tizen_visibility
                                          EINA_UNUSED,
                                      uint32_t visibility) {
+#if SB_HAS(LAZY_SUSPEND)
   if (visibility == TIZEN_VISIBILITY_VISIBILITY_FULLY_OBSCURED)
-    ApplicationWayland::Get()->Pause();
+    ApplicationWayland::Get()->Pause(NULL, NULL);
   else
-    ApplicationWayland::Get()->Unpause();
+    ApplicationWayland::Get()->Unpause(NULL, NULL);
+#else
+  if (visibility == TIZEN_VISIBILITY_VISIBILITY_FULLY_OBSCURED)
+    shared::starboard::Application::Get()->Suspend(NULL, NULL);
+  else
+    shared::starboard::Application::Get()->Unpause(NULL, NULL);
+#endif
 }
 
 static const struct tizen_visibility_listener tizen_visibility_listener = {
diff --git a/src/starboard/shared/wayland/window_get_size.cc b/src/starboard/shared/wayland/window_get_size.cc
index 436eec1..72624c5 100644
--- a/src/starboard/shared/wayland/window_get_size.cc
+++ b/src/starboard/shared/wayland/window_get_size.cc
@@ -25,10 +25,6 @@
 
   size->width = window->width;
   size->height = window->height;
-#if SB_HAS_MEDIA_4K_SUPPORT
-  size->video_pixel_ratio = 2.0f;
-#else
-  size->video_pixel_ratio = 1.0f;
-#endif
+  size->video_pixel_ratio = window->video_pixel_ratio;
   return true;
 }
diff --git a/src/starboard/shared/wayland/window_internal.cc b/src/starboard/shared/wayland/window_internal.cc
index 3b60686..1eb325a 100644
--- a/src/starboard/shared/wayland/window_internal.cc
+++ b/src/starboard/shared/wayland/window_internal.cc
@@ -19,9 +19,11 @@
 const int kWindowHeight = 1080;
 }
 
-SbWindowPrivate::SbWindowPrivate(const SbWindowOptions* options) {
+SbWindowPrivate::SbWindowPrivate(const SbWindowOptions* options,
+                                 float pixel_ratio) {
   width = kWindowWidth;
   height = kWindowHeight;
+  video_pixel_ratio = pixel_ratio;
   if (options && options->size.width > 0 && options->size.height > 0) {
     width = options->size.width;
     height = options->size.height;
diff --git a/src/starboard/shared/wayland/window_internal.h b/src/starboard/shared/wayland/window_internal.h
index 574e2dd..7d2c616 100644
--- a/src/starboard/shared/wayland/window_internal.h
+++ b/src/starboard/shared/wayland/window_internal.h
@@ -24,7 +24,8 @@
 #include "starboard/window.h"
 
 struct SbWindowPrivate {
-  explicit SbWindowPrivate(const SbWindowOptions* options);
+  explicit SbWindowPrivate(const SbWindowOptions* options,
+                           float pixel_ratio = 1.0);
   ~SbWindowPrivate() {}
 
   struct wl_surface* surface;
@@ -38,9 +39,10 @@
   Evas_Object* video_window;
 #endif
 
-  // The width, height of this window.
+  // The width, height, pixel ratio of this window.
   int width;
   int height;
+  float video_pixel_ratio;
 };
 
 #endif  // STARBOARD_SHARED_WAYLAND_WINDOW_INTERNAL_H_
diff --git a/src/starboard/shared/win32/adapter_utils.cc b/src/starboard/shared/win32/adapter_utils.cc
new file mode 100644
index 0000000..d102314
--- /dev/null
+++ b/src/starboard/shared/win32/adapter_utils.cc
@@ -0,0 +1,87 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/win32/adapter_utils.h"
+
+#include <winsock2.h>
+
+#include <iphlpapi.h>
+
+#include <memory>
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/socket_internal.h"
+#include "starboard/socket.h"
+
+namespace {
+const ULONG kDefaultAdapterInfoBufferSizeInBytes = 16 * 1024;
+}  // namespace
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+bool GetAdapters(const SbSocketAddressType address_type,
+                 std::unique_ptr<char[]>* adapter_info) {
+  SB_DCHECK(adapter_info);
+
+  ULONG family = 0;
+  int address_length_bytes = 0;
+
+  switch (address_type) {
+    case kSbSocketAddressTypeIpv4:
+      family = AF_INET;
+      address_length_bytes = kAddressLengthIpv4;
+      break;
+    case kSbSocketAddressTypeIpv6:
+      family = AF_INET6;
+      address_length_bytes = kAddressLengthIpv6;
+      break;
+    default:
+      SB_NOTREACHED() << "Invalid address type: " << address_type;
+      return false;
+  }
+
+  ULONG adapter_addresses_number_bytes = kDefaultAdapterInfoBufferSizeInBytes;
+
+  for (int try_count = 0; try_count != 2; ++try_count) {
+    // Using auto for return value here, since different versions of windows use
+    // slightly different datatypes.  These differences do not matter to us, but
+    // the compiler might warn on them.
+    adapter_info->reset(new char[adapter_addresses_number_bytes]);
+    PIP_ADAPTER_ADDRESSES adapter_addresses =
+        reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info->get());
+
+    // Note: If |GetAdapterAddresses| deems that buffer supplied is not enough,
+    // it will return the recommended number of bytes in
+    // |adapter_addresses_number_bytes|.
+    auto retval = GetAdaptersAddresses(
+        family, GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_SERVER, nullptr,
+        adapter_addresses, &adapter_addresses_number_bytes);
+
+    if (retval == ERROR_SUCCESS) {
+      return true;
+    }
+    if (retval != ERROR_BUFFER_OVERFLOW) {
+      // Only retry with more memory if the error says so.
+      break;
+    }
+    SB_LOG(ERROR) << "GetAdapterAddresses() failed with error code " << retval;
+  }
+  return false;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/adapter_utils.h b/src/starboard/shared/win32/adapter_utils.h
new file mode 100644
index 0000000..bbe11fa
--- /dev/null
+++ b/src/starboard/shared/win32/adapter_utils.h
@@ -0,0 +1,52 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_WIN32_ADAPTER_UTILS_H_
+#define STARBOARD_SHARED_WIN32_ADAPTER_UTILS_H_
+
+#include <winsock2.h>
+
+#include <ifdef.h>
+#include <iphlpapi.h>
+
+#include <memory>
+
+#include "starboard/socket.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// Returns all of the results for wi32's
+bool GetAdapters(const SbSocketAddressType address_type,
+                 std::unique_ptr<char[]>* adapter_info);
+
+// Returns true if a IP_ADAPTER_ADDRESSES IfType is a
+// non-loopback Ethernet interface.
+inline bool IsIfTypeEthernet(DWORD iftype) {
+  switch (iftype) {
+    case IF_TYPE_ETHERNET_CSMACD:
+    case IF_TYPE_IEEE80211:
+      return true;
+    case IF_TYPE_SOFTWARE_LOOPBACK:
+    default:
+      return false;
+  }
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_ADAPTER_UTILS_H_
diff --git a/src/starboard/shared/win32/audio_sink.cc b/src/starboard/shared/win32/audio_sink.cc
new file mode 100644
index 0000000..66597c9
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink.cc
@@ -0,0 +1,304 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/win32/audio_sink.h"
+
+#include <basetyps.h>
+#include <wrl.h>
+#include <xaudio2.h>
+
+#include <limits>
+
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
+
+using Microsoft::WRL::ComPtr;
+
+namespace {
+// Fails an SB_DCHECK if an HRESULT is not S_OK
+void CHECK_HRESULT_OK(HRESULT hr) {
+  SB_DCHECK(SUCCEEDED(hr)) << std::hex << hr;
+}
+}
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class XAudioAudioSink : public SbAudioSinkPrivate {
+ public:
+  XAudioAudioSink(Type* type,
+                  const WAVEFORMATEX& wfx,
+                  SbAudioSinkFrameBuffers frame_buffers,
+                  int frame_buffers_size_in_frames,
+                  SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+                  SbAudioSinkConsumeFramesFunc consume_frame_func,
+                  void* context);
+  ~XAudioAudioSink() SB_OVERRIDE;
+
+  bool IsType(Type* type) SB_OVERRIDE { return type_ == type; }
+  void SetPlaybackRate(double playback_rate) SB_OVERRIDE {
+    SB_DCHECK(playback_rate >= 0.0);
+    if (playback_rate != 0.0 && playback_rate != 1.0) {
+      SB_NOTIMPLEMENTED() << "TODO: Only playback rates of 0.0 and 1.0 are "
+                             "currently supported.";
+      playback_rate = (playback_rate > 0.0) ? 1.0 : 0.0;
+    }
+    ScopedLock lock(mutex_);
+    playback_rate_ = playback_rate;
+  }
+
+ private:
+  static void* ThreadEntryPoint(void* context);
+  void AudioThreadFunc();
+  void SubmitSourceBuffer(int offset_in_frames, int count_frames);
+
+  XAudioAudioSinkType* type_;
+  SbAudioSinkUpdateSourceStatusFunc update_source_status_func_;
+  SbAudioSinkConsumeFramesFunc consume_frame_func_;
+  void* context_;
+
+  SbThread audio_out_thread_;
+
+  SbAudioSinkFrameBuffers frame_buffers_;
+  int frame_buffers_size_in_frames_;
+  WAVEFORMATEX wfx_;
+
+  // Note: despite some documentation to the contrary, it appears
+  // that IXAudio2SourceVoice cannot be a ComPtr.
+  IXAudio2SourceVoice* source_voice_;
+
+  // mutex_ protects only destroying_ and playback_rate_.
+  // Everything else is immutable
+  // after the constructor.
+  ::starboard::Mutex mutex_;
+  bool destroying_;
+  double playback_rate_;
+};
+
+XAudioAudioSink::XAudioAudioSink(
+    Type* type,
+    const WAVEFORMATEX& wfx,
+    SbAudioSinkFrameBuffers frame_buffers,
+    int frame_buffers_size_in_frames,
+    SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+    SbAudioSinkConsumeFramesFunc consume_frame_func,
+    void* context)
+    : type_(static_cast<XAudioAudioSinkType*>(type)),
+      update_source_status_func_(update_source_status_func),
+      consume_frame_func_(consume_frame_func),
+      context_(context),
+      audio_out_thread_(kSbThreadInvalid),
+      frame_buffers_(frame_buffers),
+      frame_buffers_size_in_frames_(frame_buffers_size_in_frames),
+      wfx_(wfx),
+      destroying_(false),
+      playback_rate_(1.0) {
+  // TODO: Check MaxFrequencyRadio
+  CHECK_HRESULT_OK(
+      type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
+                                          /*MaxFrequencyRadio = */ 1.0));
+
+  CHECK_HRESULT_OK(source_voice_->Start(0));
+
+  audio_out_thread_ =
+      SbThreadCreate(0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
+                     "audio_out", &XAudioAudioSink::ThreadEntryPoint, this);
+  SB_DCHECK(SbThreadIsValid(audio_out_thread_));
+}
+
+XAudioAudioSink::~XAudioAudioSink() {
+  {
+    ScopedLock lock(mutex_);
+    destroying_ = true;
+  }
+  SbThreadJoin(audio_out_thread_, nullptr);
+  source_voice_->DestroyVoice();
+}
+
+// static
+void* XAudioAudioSink::ThreadEntryPoint(void* context) {
+  SB_DCHECK(context);
+  XAudioAudioSink* sink = static_cast<XAudioAudioSink*>(context);
+  sink->AudioThreadFunc();
+
+  return nullptr;
+}
+
+void XAudioAudioSink::SubmitSourceBuffer(int offset_in_frames,
+                                         int count_frames) {
+  XAUDIO2_BUFFER audio_buffer_info;
+
+  audio_buffer_info.Flags = 0;
+  audio_buffer_info.AudioBytes = wfx_.nChannels *
+                                 frame_buffers_size_in_frames_ *
+                                 (wfx_.wBitsPerSample / 8);
+  audio_buffer_info.pAudioData = static_cast<const BYTE*>(frame_buffers_[0]);
+  audio_buffer_info.PlayBegin = offset_in_frames;
+  audio_buffer_info.PlayLength = count_frames;
+  audio_buffer_info.LoopBegin = 0;
+  audio_buffer_info.LoopLength = 0;
+  audio_buffer_info.LoopCount = 0;
+  audio_buffer_info.pContext = nullptr;
+  CHECK_HRESULT_OK(source_voice_->SubmitSourceBuffer(&audio_buffer_info));
+}
+
+void XAudioAudioSink::AudioThreadFunc() {
+  const int kMaxFramesToConsumePerRequest = 1024;
+
+  int submitted_frames = 0;
+  uint64_t samples_played = 0;
+  for (;;) {
+    {
+      ScopedLock lock(mutex_);
+      if (destroying_) {
+        break;
+      }
+    }
+    int frames_in_buffer, offset_in_frames;
+    bool is_playing, is_eos_reached;
+    bool is_playback_rate_zero;
+    {
+      ScopedLock lock(mutex_);
+      is_playback_rate_zero = playback_rate_ == 0.0;
+    }
+    update_source_status_func_(&frames_in_buffer, &offset_in_frames,
+                               &is_playing, &is_eos_reached, context_);
+
+    // TODO: make sure that frames_in_buffer is large enough
+    // that it exceeds the voice state pool interval
+    if (!is_playing || frames_in_buffer == 0 || is_playback_rate_zero) {
+      SbThreadSleep(kSbTimeMillisecond * 5);
+      continue;
+    }
+    int unsubmitted_frames = frames_in_buffer - submitted_frames;
+    int unsubmitted_start =
+        (offset_in_frames + submitted_frames) % frame_buffers_size_in_frames_;
+    if (unsubmitted_frames == 0) {
+      // submit nothing
+    } else if (unsubmitted_start + unsubmitted_frames <=
+               frame_buffers_size_in_frames_) {
+      SubmitSourceBuffer(unsubmitted_start, unsubmitted_frames);
+    } else {
+      int count_tail_frames = frame_buffers_size_in_frames_ - unsubmitted_start;
+      SubmitSourceBuffer(unsubmitted_start, count_tail_frames);
+      SubmitSourceBuffer(0, unsubmitted_frames - count_tail_frames);
+    }
+    submitted_frames = frames_in_buffer;
+
+    SbThreadSleep(kSbTimeMillisecond);
+
+    XAUDIO2_VOICE_STATE voice_state;
+    source_voice_->GetState(&voice_state);
+
+    int64_t consumed_frames = voice_state.SamplesPlayed - samples_played;
+    SB_DCHECK(consumed_frames >= 0);
+    SB_DCHECK(consumed_frames < std::numeric_limits<int>::max());
+    int consumed_frames_int = static_cast<int>(consumed_frames);
+
+    consume_frame_func_(consumed_frames_int, context_);
+    submitted_frames -= consumed_frames_int;
+    samples_played = voice_state.SamplesPlayed;
+  }
+}
+
+namespace {
+
+WORD SampleTypeToFormatTag(SbMediaAudioSampleType type) {
+  switch (type) {
+    case kSbMediaAudioSampleTypeInt16:
+      return WAVE_FORMAT_PCM;
+    case kSbMediaAudioSampleTypeFloat32:
+      return WAVE_FORMAT_IEEE_FLOAT;
+    default:
+      SB_NOTREACHED();
+      return 0;
+  }
+}
+
+WORD SampleTypeToBitsPerSample(SbMediaAudioSampleType type) {
+  switch (type) {
+    case kSbMediaAudioSampleTypeInt16:
+      return 16;
+    case kSbMediaAudioSampleTypeFloat32:
+      return 32;
+    default:
+      SB_NOTREACHED();
+      return 0;
+  }
+}
+
+}  // namespace
+
+XAudioAudioSinkType::XAudioAudioSinkType() {
+  CHECK_HRESULT_OK(XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR));
+  CHECK_HRESULT_OK(x_audio2_->CreateMasteringVoice(&mastering_voice_));
+}
+
+SbAudioSink XAudioAudioSinkType::Create(
+    int channels,
+    int sampling_frequency_hz,
+    SbMediaAudioSampleType audio_sample_type,
+    SbMediaAudioFrameStorageType audio_frame_storage_type,
+    SbAudioSinkFrameBuffers frame_buffers,
+    int frame_buffers_size_in_frames,
+    SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+    SbAudioSinkConsumeFramesFunc consume_frames_func,
+    void* context) {
+  SB_DCHECK(audio_frame_storage_type ==
+            kSbMediaAudioFrameStorageTypeInterleaved);
+
+  WAVEFORMATEX wfx;
+
+  wfx.wFormatTag = SampleTypeToFormatTag(audio_sample_type);
+  wfx.nChannels = channels;
+  wfx.nSamplesPerSec = sampling_frequency_hz;
+  wfx.nAvgBytesPerSec = channels *
+                        SampleTypeToBitsPerSample(audio_sample_type) *
+                        sampling_frequency_hz / 8;
+  wfx.wBitsPerSample = SampleTypeToBitsPerSample(audio_sample_type);
+  wfx.nBlockAlign = (channels * wfx.wBitsPerSample) / 8;
+  wfx.cbSize = 0;
+
+  return new XAudioAudioSink(
+      this, wfx, frame_buffers, frame_buffers_size_in_frames,
+      update_source_status_func, consume_frames_func, context);
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+namespace {
+SbAudioSinkPrivate::Type* audio_sink_;
+}  // namespace
+
+// static
+void SbAudioSinkPrivate::PlatformInitialize() {
+  SB_DCHECK(!audio_sink_);
+  audio_sink_ = new starboard::shared::win32::XAudioAudioSinkType();
+  SetPrimaryType(audio_sink_);
+  EnableFallbackToStub();
+}
+
+// static
+void SbAudioSinkPrivate::PlatformTearDown() {
+  SB_DCHECK(audio_sink_ == GetPrimaryType());
+  SetPrimaryType(nullptr);
+  delete audio_sink_;
+  audio_sink_ = nullptr;
+}
diff --git a/src/starboard/shared/win32/audio_sink.h b/src/starboard/shared/win32/audio_sink.h
new file mode 100644
index 0000000..f8820f7
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink.h
@@ -0,0 +1,67 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_WIN32_AUDIO_SINK_H_
+#define STARBOARD_SHARED_WIN32_AUDIO_SINK_H_
+
+#include <wrl.h>
+#include <xaudio2.h>
+
+#include "starboard/log.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class XAudioAudioSinkType : public SbAudioSinkPrivate::Type {
+  friend class XAudioAudioSink;
+
+ public:
+  XAudioAudioSinkType();
+
+  SbAudioSink Create(
+      int channels,
+      int sampling_frequency_hz,
+      SbMediaAudioSampleType audio_sample_type,
+      SbMediaAudioFrameStorageType audio_frame_storage_type,
+      SbAudioSinkFrameBuffers frame_buffers,
+      int frame_buffers_size_in_frames,
+      SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+      SbAudioSinkConsumeFramesFunc consume_frames_func,
+      void* context);
+
+  bool IsValid(SbAudioSink audio_sink) SB_OVERRIDE {
+    return audio_sink != kSbAudioSinkInvalid && audio_sink->IsType(this);
+  }
+
+  void Destroy(SbAudioSink audio_sink) SB_OVERRIDE {
+    if (audio_sink != kSbAudioSinkInvalid && !IsValid(audio_sink)) {
+      SB_LOG(WARNING) << "audio_sink is invalid.";
+      return;
+    }
+    delete audio_sink;
+  }
+
+ private:
+  Microsoft::WRL::ComPtr<IXAudio2> x_audio2_;
+  IXAudio2MasteringVoice* mastering_voice_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_AUDIO_SINK_H_
diff --git a/src/starboard/shared/win32/audio_sink_get_max_channels.cc b/src/starboard/shared/win32/audio_sink_get_max_channels.cc
new file mode 100644
index 0000000..00d7f93
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink_get_max_channels.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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"
+
+int SbAudioSinkGetMaxChannels() {
+  return 6;
+}
diff --git a/src/starboard/shared/win32/audio_sink_get_nearest_supported_sample_frequency.cc b/src/starboard/shared/win32/audio_sink_get_nearest_supported_sample_frequency.cc
new file mode 100644
index 0000000..c9d3740
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink_get_nearest_supported_sample_frequency.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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"
+
+#include "starboard/log.h"
+
+int SbAudioSinkGetNearestSupportedSampleFrequency(int sampling_frequency_hz) {
+  if (sampling_frequency_hz <= 0) {
+    SB_LOG(ERROR) << "Invalid audio sampling frequency "
+                  << sampling_frequency_hz;
+    return 1;
+  }
+  return sampling_frequency_hz;
+}
diff --git a/src/starboard/shared/win32/audio_sink_is_audio_frame_storage_type_supported.cc b/src/starboard/shared/win32/audio_sink_is_audio_frame_storage_type_supported.cc
new file mode 100644
index 0000000..16a603a
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink_is_audio_frame_storage_type_supported.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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"
+
+#include "starboard/log.h"
+
+bool SbAudioSinkIsAudioFrameStorageTypeSupported(
+    SbMediaAudioFrameStorageType audio_frame_storage_type) {
+  switch (audio_frame_storage_type) {
+    case kSbMediaAudioFrameStorageTypeInterleaved:
+      return true;
+    case kSbMediaAudioFrameStorageTypePlanar:
+      return false;
+    default:
+      SB_NOTREACHED();
+      return false;
+  }
+}
diff --git a/src/starboard/shared/win32/audio_sink_is_audio_sample_type_supported.cc b/src/starboard/shared/win32/audio_sink_is_audio_sample_type_supported.cc
new file mode 100644
index 0000000..07bfe28
--- /dev/null
+++ b/src/starboard/shared/win32/audio_sink_is_audio_sample_type_supported.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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"
+
+#include "starboard/log.h"
+
+bool SbAudioSinkIsAudioSampleTypeSupported(
+    SbMediaAudioSampleType audio_sample_type) {
+  switch (audio_sample_type) {
+    case kSbMediaAudioSampleTypeInt16:
+      return true;
+    case kSbMediaAudioSampleTypeFloat32:
+      return true;
+    default:
+      SB_NOTREACHED();
+      return false;
+  }
+}
diff --git a/src/starboard/shared/win32/file_internal.cc b/src/starboard/shared/win32/file_internal.cc
index a2d7621..e6f7927 100644
--- a/src/starboard/shared/win32/file_internal.cc
+++ b/src/starboard/shared/win32/file_internal.cc
@@ -17,8 +17,11 @@
 #include <windows.h>
 
 #include "starboard/log.h"
+#include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/wchar_utils.h"
 
+namespace sbwin32 = starboard::shared::win32;
+
 namespace starboard {
 namespace shared {
 namespace win32 {
@@ -113,11 +116,16 @@
     }
   }
 
+  const DWORD last_error = GetLastError();
+  if (!starboard::shared::win32::IsValidHandle(file_handle)) {
+    SB_DLOG(INFO) << "CreateFile2 failed for " << path << ":"
+                  << sbwin32::Win32ErrorCode(last_error);
+  }
+
   if (out_error) {
     if (starboard::shared::win32::IsValidHandle(file_handle)) {
       *out_error = kSbFileOk;
     } else {
-      const DWORD last_error = GetLastError();
       switch (last_error) {
         case ERROR_ACCESS_DENIED:
           *out_error = kSbFileErrorAccessDenied;
diff --git a/src/starboard/shared/win32/get_home_directory.cc b/src/starboard/shared/win32/get_home_directory.cc
new file mode 100644
index 0000000..aa4272f
--- /dev/null
+++ b/src/starboard/shared/win32/get_home_directory.cc
@@ -0,0 +1,44 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <string>
+
+#include "starboard/log.h"
+#include "starboard/shared/nouser/user_internal.h"
+#include "starboard/shared/uwp/winrt_workaround.h"
+#include "starboard/shared/win32/wchar_utils.h"
+#include "starboard/string.h"
+#include "starboard/system.h"
+
+using Windows::Storage::ApplicationData;
+
+namespace sbwin32 = starboard::shared::win32;
+
+namespace starboard {
+namespace shared {
+namespace nouser {
+
+bool GetHomeDirectory(SbUser user, char* out_path, int path_size) {
+  if (user != SbUserGetCurrent()) {
+    return false;
+  }
+  std::string home_directory =
+      sbwin32::platformStringToString(
+          ApplicationData::Current->LocalFolder->Path);
+  return SbStringCopy(out_path, home_directory.c_str(), path_size);
+}
+
+}  // namespace nouser
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/gyp_configuration.py b/src/starboard/shared/win32/gyp_configuration.py
index 46fa1a7..0bf0e26 100644
--- a/src/starboard/shared/win32/gyp_configuration.py
+++ b/src/starboard/shared/win32/gyp_configuration.py
@@ -13,106 +13,48 @@
 # limitations under the License.
 import logging
 import os
+import sys
 
 import config.starboard
-import gyp_utils
 
-required_sdk_version = '10.0.15063.0'
+from starboard.tools.paths import STARBOARD_ROOT
 
-# Default Windows SDK bin directory.
-windows_sdk_bin_dir = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin'
-
-# Maybe override Windows SDK bin directory with environment variable.
-windows_sdk_bin_var = 'WindowsSdkBinPath'
-if windows_sdk_bin_var in os.environ:
-  windows_sdk_bin_dir = os.environ[windows_sdk_bin_var]
-elif not os.path.exists(windows_sdk_bin_dir):
-  # If the above fails, this is our last guess.
-  windows_sdk_bin_dir = windows_sdk_bin_dir.replace('Program Files (x86)',
-                                                    'mappedProgramFiles')
-
-# Default Visual Studio Install directory.
-vs_install_dir = ('C:\\Program Files (x86)\\Microsoft Visual Studio'
-                  + '\\2017\\Professional')
-# Maybe override Visual Studio install directory with environment variable.
-vs_install_dir_var = 'VSINSTALLDIR'
-if vs_install_dir_var in os.environ:
-  vs_install_dir = os.environ[vs_install_dir_var]
-elif not os.path.exists(vs_install_dir):
-  # If the above fails, this is our last guess.
-  vs_install_dir = vs_install_dir.replace('Program Files (x86)',
-                                          'mappedProgramFiles')
-
-
-vs_install_dir_with_version = (vs_install_dir + '\\VC\\Tools\\MSVC'
-                               + '\\14.10.25017')
-vs_cl_path = vs_install_dir_with_version + '\\bin\\HostX64\\x64'
-
-
-
-def _CheckVisualStudioVersion():
-  if os.path.exists(vs_cl_path):
-    return True
-  logging.critical('Expected Visual Studio path \"%s\" not found.',
-                   vs_cl_path)
+import sdk_configuration
 
 def _QuotePath(path):
   return '"' + path + '"'
 
-def _CheckWindowsSdkVersion():
-  required_sdk_bin_dir = os.path.join(windows_sdk_bin_dir,
-                                      required_sdk_version)
-  if os.path.exists(required_sdk_bin_dir):
-    return True
-
-  if os.path.exists(windows_sdk_bin_dir):
-    contents = os.listdir(windows_sdk_bin_dir)
-    contents = [content for content in contents
-                if os.path.isdir(os.path.join(windows_sdk_bin_dir, content))]
-    non_sdk_dirs = ['arm', 'arm64', 'x64', 'x86']
-    installed_sdks = [content for content in contents
-                      if content not in non_sdk_dirs]
-    logging.critical('Windows SDK versions \"%s\" found." \"%s\" required.',
-                     installed_sdks, required_sdk_version)
-  else:
-    logging.critical('Windows SDK versions \"%s\" required.',
-                     required_sdk_version)
-  return False
-
-
 class PlatformConfig(config.starboard.PlatformConfigStarboard):
   """Starboard Microsoft Windows platform configuration."""
 
   def __init__(self, platform):
     super(PlatformConfig, self).__init__(platform)
-    _CheckWindowsSdkVersion()
-    _CheckVisualStudioVersion()
+    self.sdk = sdk_configuration.SdkConfiguration()
 
   def GetVariables(self, configuration):
+    sdk = self.sdk
     variables = super(PlatformConfig, self).GetVariables(configuration)
-    windows_sdk_path = os.path.abspath(os.path.join(windows_sdk_bin_dir,
-                                                    os.pardir))
     variables.update({
-      'visual_studio_install_path': vs_install_dir_with_version,
-      'windows_sdk_path': windows_sdk_path,
-      'windows_sdk_version': required_sdk_version,
+      'visual_studio_install_path': sdk.vs_install_dir_with_version,
+      'windows_sdk_path': sdk.windows_sdk_path,
+      'windows_sdk_version': sdk.required_sdk_version,
       })
     return variables
 
   def GetEnvironmentVariables(self):
-    cl = _QuotePath(os.path.join(vs_cl_path, 'cl.exe'))
-    lib = _QuotePath(os.path.join(vs_cl_path, 'lib.exe'))
-    link = _QuotePath(os.path.join(vs_cl_path, 'link.exe'))
-    rc = _QuotePath(os.path.join(windows_sdk_bin_dir, required_sdk_version,
-                                 'x64', 'rc.exe'))
+    sdk = self.sdk
+    cl = _QuotePath(os.path.join(sdk.vs_host_tools_path, 'cl.exe'))
+    lib = _QuotePath(os.path.join(sdk.vs_host_tools_path, 'lib.exe'))
+    link = _QuotePath(os.path.join(sdk.vs_host_tools_path, 'link.exe'))
+    rc = _QuotePath(os.path.join(sdk.windows_sdk_host_tools, 'rc.exe'))
     env_variables = {
-        'AR' : lib,
-        'AR_HOST' : lib,
+        'AR': lib,
+        'AR_HOST': lib,
         'CC': cl,
         'CXX': cl,
         'LD': link,
         'RC': rc,
-        'VS_INSTALL_DIR': vs_install_dir,
+        'VS_INSTALL_DIR': sdk.vs_install_dir,
         'CC_HOST': cl,
         'CXX_HOST': cl,
         'LD_HOST': link,
@@ -139,3 +81,9 @@
         'qtcreator_session_name_prefix': 'cobalt',
     }
     return generator_variables
+
+  def GetToolchain(self):
+    sys.path.append(
+        os.path.join(STARBOARD_ROOT, 'shared', 'msvc', 'uwp'))
+    from toolchain import MSVCUWPToolchain  # pylint: disable=g-import-not-at-top,g-bad-import-order
+    return MSVCUWPToolchain()
diff --git a/src/starboard/shared/win32/memory_get_stack_bounds.cc b/src/starboard/shared/win32/memory_get_stack_bounds.cc
index 5a92b61..fec877e 100644
--- a/src/starboard/shared/win32/memory_get_stack_bounds.cc
+++ b/src/starboard/shared/win32/memory_get_stack_bounds.cc
@@ -14,10 +14,10 @@
 
 #include "starboard/memory.h"
 
-#include "starboard/log.h"
+#include <windows.h>
 
-void SbMemoryGetStackBounds(void** /*out_high*/, void** /*out_low*/) {
-  // TODO the common way to do this is with NtQueryInformationThread
-  // for ThreadBasicInformation but that may not be available on UWP.
-  SB_NOTIMPLEMENTED();
+void SbMemoryGetStackBounds(void** out_high, void** out_low) {
+  _NT_TIB* tib = reinterpret_cast<_NT_TIB*>(NtCurrentTeb());
+  *out_high = tib->StackBase;
+  *out_low = tib->StackLimit;
 }
diff --git a/src/starboard/shared/win32/sdk_configuration.py b/src/starboard/shared/win32/sdk_configuration.py
new file mode 100644
index 0000000..6b21f39
--- /dev/null
+++ b/src/starboard/shared/win32/sdk_configuration.py
@@ -0,0 +1,94 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+
+class SdkConfiguration:
+  required_sdk_version = '10.0.15063.0'
+
+  # Default Windows SDK bin directory.
+  windows_sdk_bin_dir = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin'
+
+  # windows_sdk_host_tools will be set to, eg,
+  # 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\10.0.15063.0'
+
+  # windows_sdk_path will be set to, eg
+  # 'C:\\Program Files (x86)\\Windows Kits\\10'
+
+  # Default Visual Studio Install directory.
+  vs_install_dir = ('C:\\Program Files (x86)\\Microsoft Visual Studio'
+                    + '\\2017\\Professional')
+
+  # vs_install_dir_with_version will be set to, eg
+  # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools
+  # \MSVC\14.10.25017
+
+  # vs_host_tools_path will be set to, eg
+  # "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools
+  # \MSVC\14.10.25017\bin\HostX64\x64
+
+  def __init__(self):
+    # Maybe override Windows SDK bin directory with environment variable.
+    windows_sdk_bin_var = 'WindowsSdkBinPath'
+    if windows_sdk_bin_var in os.environ:
+      self.windows_sdk_bin_dir = os.environ[windows_sdk_bin_var]
+    elif not os.path.exists(self.windows_sdk_bin_dir):
+      # If the above fails, this is our last guess.
+      self.windows_sdk_bin_dir = self.windows_sdk_bin_dir.replace(
+        'Program Files (x86)', 'mappedProgramFiles')
+
+    self.windows_sdk_host_tools = os.path.join(
+        self.windows_sdk_bin_dir, self.required_sdk_version, 'x64')
+
+    self.windows_sdk_path = os.path.dirname(self.windows_sdk_bin_dir)
+
+    # Maybe override Visual Studio install directory with environment variable.
+    vs_install_dir_var = 'VSINSTALLDIR'
+    if vs_install_dir_var in os.environ:
+      self.vs_install_dir = os.environ[vs_install_dir_var]
+    elif not os.path.exists(self.vs_install_dir):
+      # If the above fails, this is our last guess.
+      self.vs_install_dir = self.vs_install_dir.replace('Program Files (x86)',
+                                                        'mappedProgramFiles')
+
+    self.vs_install_dir_with_version = (self.vs_install_dir
+        + '\\VC\\Tools\\MSVC' + '\\14.10.25017')
+    self.vs_host_tools_path = (self.vs_install_dir_with_version
+        + '\\bin\\HostX64\\x64')
+
+    if not os.path.exists(self.vs_host_tools_path):
+      logging.critical('Expected Visual Studio path \"%s\" not found.',
+                       self.vs_cl_path)
+    self._CheckWindowsSdkVersion()
+
+  def _CheckWindowsSdkVersion(self):
+    if os.path.exists(self.windows_sdk_host_tools):
+      return True
+
+    if os.path.exists(self.windows_sdk_bin_dir):
+      contents = os.listdir(self.windows_sdk_bin_dir)
+      contents = [content for content in contents
+                  if os.path.isdir(
+                    os.path.join(self.windows_sdk_bin_dir, content))]
+      non_sdk_dirs = ['arm', 'arm64', 'x64', 'x86']
+      installed_sdks = [content for content in contents
+                        if content not in non_sdk_dirs]
+      logging.critical('Windows SDK versions \"%s\" found." \"%s\" required.',
+                       installed_sdks, self.required_sdk_version)
+    else:
+      logging.critical('Windows SDK versions \"%s\" required.',
+                       self.required_sdk_version)
+    return False
+
diff --git a/src/starboard/shared/win32/socket_get_interface_address.cc b/src/starboard/shared/win32/socket_get_interface_address.cc
index 22e2bb9..fa0ded7 100644
--- a/src/starboard/shared/win32/socket_get_interface_address.cc
+++ b/src/starboard/shared/win32/socket_get_interface_address.cc
@@ -25,6 +25,7 @@
 #include "starboard/byte_swap.h"
 #include "starboard/log.h"
 #include "starboard/memory.h"
+#include "starboard/shared/win32/adapter_utils.h"
 #include "starboard/shared/win32/socket_internal.h"
 
 namespace sbwin32 = starboard::shared::win32;
@@ -118,61 +119,11 @@
   return true;
 }
 
-bool GetAdapters(const SbSocketAddressType address_type,
-                 std::unique_ptr<char[]>* adapter_info) {
-  SB_DCHECK(adapter_info);
-
-  ULONG family = 0;
-  int address_length_bytes = 0;
-
-  switch (address_type) {
-    case kSbSocketAddressTypeIpv4:
-      family = AF_INET;
-      address_length_bytes = sbwin32::kAddressLengthIpv4;
-      break;
-    case kSbSocketAddressTypeIpv6:
-      family = AF_INET6;
-      address_length_bytes = sbwin32::kAddressLengthIpv6;
-      break;
-    default:
-      SB_NOTREACHED() << "Invalid address type: " << address_type;
-      return false;
-  }
-
-  ULONG adapter_addresses_number_bytes = kDefaultAdapterInfoBufferSizeInBytes;
-
-  for (int try_count = 0; try_count != 2; ++try_count) {
-    // Using auto for return value here, since different versions of windows use
-    // slightly different datatypes.  These differences do not matter to us, but
-    // the compiler might warn on them.
-    adapter_info->reset(new char[adapter_addresses_number_bytes]);
-    PIP_ADAPTER_ADDRESSES adapter_addresses =
-        reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info->get());
-
-    // Note: If |GetAdapterAddresses| deems that buffer supplied is not enough,
-    // it will return the recommended number of bytes in
-    // |adapter_addresses_number_bytes|.
-    auto retval = GetAdaptersAddresses(
-        family, GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_SERVER, nullptr,
-        adapter_addresses, &adapter_addresses_number_bytes);
-
-    if (retval == ERROR_SUCCESS) {
-      return true;
-    }
-    if (retval != ERROR_BUFFER_OVERFLOW) {
-      // Only retry with more memory if the error says so.
-      break;
-    }
-    SB_LOG(ERROR) << "GetAdapterAddresses() failed with error code " << retval;
-  }
-
-  return false;
-}
-
 bool GetNetmaskForInterfaceAddress(const SbSocketAddress& interface_address,
                                    SbSocketAddress* out_netmask) {
   std::unique_ptr<char[]> adapter_info_memory_block;
-  if (!GetAdapters(interface_address.type, &adapter_info_memory_block)) {
+  if (!sbwin32::GetAdapters(
+      interface_address.type, &adapter_info_memory_block)) {
     return false;
   }
   const void* const interface_address_buffer =
@@ -181,8 +132,7 @@
            adapter_info_memory_block.get());
        adapter != nullptr; adapter = adapter->Next) {
     if ((adapter->OperStatus != IfOperStatusUp) ||
-        (adapter->IfType != IF_TYPE_ETHERNET_CSMACD)) {
-      // Note: IfType == IF_TYPE_SOFTWARE_LOOPBACK is also skipped.
+        !sbwin32::IsIfTypeEthernet(adapter->IfType)) {
       continue;
     }
 
@@ -242,7 +192,7 @@
   }
 
   std::unique_ptr<char[]> adapter_info_memory_block;
-  if (!GetAdapters(address_type, &adapter_info_memory_block)) {
+  if (!sbwin32::GetAdapters(address_type, &adapter_info_memory_block)) {
     return false;
   }
 
@@ -250,8 +200,7 @@
            adapter_info_memory_block.get());
        adapter != nullptr; adapter = adapter->Next) {
     if ((adapter->OperStatus != IfOperStatusUp) ||
-        (adapter->IfType != IF_TYPE_ETHERNET_CSMACD)) {
-      // Note: IfType == IF_TYPE_SOFTWARE_LOOPBACK is also skipped.
+        !sbwin32::IsIfTypeEthernet(adapter->IfType)) {
       continue;
     }
 
diff --git a/src/starboard/shared/win32/socket_waiter_internal.cc b/src/starboard/shared/win32/socket_waiter_internal.cc
index 2d10a70..a5b289c 100644
--- a/src/starboard/shared/win32/socket_waiter_internal.cc
+++ b/src/starboard/shared/win32/socket_waiter_internal.cc
@@ -283,7 +283,6 @@
 
   const SbTimeMonotonic start_time = SbTimeGetMonotonicNow();
   int64_t duration_left = duration;
-  SbSocketWaiterResult result = kSbSocketWaiterResultInvalid;
 
   while (true) {
     // |waitees_| could have been modified in the last loop iteration, so
@@ -293,21 +292,16 @@
 
     const DWORD millis = sbwin32::ConvertSbTimeToMillisRoundUp(duration_left);
 
-    bool woke_up = false;
     {
       starboard::ScopedLock lock(unhandled_wakeup_count_mutex_);
       if (unhandled_wakeup_count_ > 0) {
         --unhandled_wakeup_count_;
-        woke_up = true;
+        // The signaling thread also set the event, so reset it.
+        ResetWakeupEvent();
+        return kSbSocketWaiterResultWokenUp;
       }
     }
 
-    if (woke_up) {
-      // The signaling thread also set the event, so reset it.
-      ResetWakeupEvent();
-      return kSbSocketWaiterResultWokenUp;
-    }
-
     // There should always be a wakeup event.
     SB_DCHECK(number_events > 0);
 
@@ -328,20 +322,14 @@
       SB_DCHECK(wakeup_event_token_ >= 0);
 
       if (socket_index == wakeup_event_token_) {
-        {
-          starboard::ScopedLock lock(unhandled_wakeup_count_mutex_);
-          SB_DCHECK(unhandled_wakeup_count_ > 0);
+        starboard::ScopedLock lock(unhandled_wakeup_count_mutex_);
+        SB_DCHECK(unhandled_wakeup_count_ > 0);
 
-          if (unhandled_wakeup_count_ > 0) {
-            --unhandled_wakeup_count_;
-            // This was a dummy event.  We were woken up.
-            // Note that we do not need to reset the event here,
-            // since it was created using an auto-reset flag.
-            return kSbSocketWaiterResultWokenUp;
-          }
-        }
-
-        ResetWakeupEvent();
+        --unhandled_wakeup_count_;
+        // This was a dummy event.  We were woken up.
+        // Note that we do not need to reset the event here,
+        // since it was created using an auto-reset flag.
+        return kSbSocketWaiterResultWokenUp;
       } else {
         Waitee* waitee = waitees_.GetWaiteeByIndex(socket_index);
 
@@ -387,10 +375,13 @@
 }
 
 void SbSocketWaiterPrivate::WakeUp() {
-  {
-    starboard::ScopedLock lock(unhandled_wakeup_count_mutex_);
-    ++unhandled_wakeup_count_;
-  }
+  // Increasing unhandled_wakeup_count_mutex_ and calling SignalWakeupEvent
+  // atomically helps add additional guarantees of when the waiter can be
+  // woken up.  While we can code around this easily, having a stronger
+  // coupling enables us to add DCHECKs for |unhandled_wakeup_count_| in other
+  // parts of the code.
+  starboard::ScopedLock lock(unhandled_wakeup_count_mutex_);
+  ++unhandled_wakeup_count_;
   SignalWakeupEvent();
 }
 
diff --git a/src/starboard/shared/win32/socket_waiter_internal.h b/src/starboard/shared/win32/socket_waiter_internal.h
index 193361b..a2147fa 100644
--- a/src/starboard/shared/win32/socket_waiter_internal.h
+++ b/src/starboard/shared/win32/socket_waiter_internal.h
@@ -132,10 +132,10 @@
   WaiteeRegistry waitees_;
   WaiteeRegistry::LookupToken wakeup_event_token_;
 
-  // Number of times wake up has been called, and not handled.
+  // This mutex covers the next two variables.
   starboard::Mutex unhandled_wakeup_count_mutex_;
+  // Number of times wake up has been called, and not handled.
   std::int32_t unhandled_wakeup_count_;
-
   // The WSAEvent that is set by Wakeup();
   sbwin32::AutoEventHandle wakeup_event_;
 };
diff --git a/src/starboard/shared/win32/system_clear_last_error.cc b/src/starboard/shared/win32/system_clear_last_error.cc
new file mode 100644
index 0000000..9b1423d
--- /dev/null
+++ b/src/starboard/shared/win32/system_clear_last_error.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <windows.h>
+
+void SbSystemClearLastError() {
+  SetLastError(0);
+}
diff --git a/src/starboard/shared/win32/system_get_connection_type.cc b/src/starboard/shared/win32/system_get_connection_type.cc
new file mode 100644
index 0000000..399e2b2
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_connection_type.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <winsock2.h>
+
+#include <ifdef.h>
+#include <iphlpapi.h>
+
+#include "starboard/shared/win32/adapter_utils.h"
+
+namespace sbwin32 = ::starboard::shared::win32;
+
+namespace {
+// Return the connection type of the first "up" ethernet interface,
+// or unknown if none found.
+SbSystemConnectionType FindConnectionType(PIP_ADAPTER_ADDRESSES adapter) {
+  for (; adapter != nullptr; adapter = adapter->Next) {
+    if ((adapter->OperStatus != IfOperStatusUp) ||
+        !sbwin32::IsIfTypeEthernet(adapter->IfType)) {
+      continue;
+    }
+    if (adapter->IfType == IF_TYPE_IEEE80211) {
+      return kSbSystemConnectionTypeWireless;
+    }
+    return kSbSystemConnectionTypeWired;
+  }
+  return kSbSystemConnectionTypeUnknown;
+}
+}  // namespace
+
+SbSystemConnectionType SbSystemGetConnectionType() {
+  std::unique_ptr<char[]> buffer;
+  if (!sbwin32::GetAdapters(kSbSocketAddressTypeIpv4, &buffer)) {
+      return kSbSystemConnectionTypeUnknown;
+  }
+  SbSystemConnectionType result = FindConnectionType(
+      reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.get()));
+  if (result != kSbSystemConnectionTypeUnknown) {
+    return result;
+  }
+  if (!sbwin32::GetAdapters(kSbSocketAddressTypeIpv6, &buffer)) {
+      return kSbSystemConnectionTypeUnknown;
+  }
+  return FindConnectionType(
+      reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.get()));
+}
diff --git a/src/starboard/shared/win32/system_get_device_type.cc b/src/starboard/shared/win32/system_get_device_type.cc
new file mode 100644
index 0000000..0957c7a
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_device_type.cc
@@ -0,0 +1,39 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <string>
+
+#include "starboard/log.h"
+#include "starboard/shared/uwp/winrt_workaround.h"
+#include "starboard/shared/win32/wchar_utils.h"
+
+using Windows::System::Profile::AnalyticsInfo;
+using Windows::System::Profile::AnalyticsVersionInfo;
+
+SbSystemDeviceType SbSystemGetDeviceType() {
+  AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;
+  std::string family = starboard::shared::win32::platformStringToString(
+      version_info->DeviceFamily);
+
+  if (family.compare("Windows.Desktop") == 0) {
+    return kSbSystemDeviceTypeDesktopPC;
+  }
+  if (family.compare("Windows.Xbox") == 0) {
+    return kSbSystemDeviceTypeGameConsole;
+  }
+  SB_NOTREACHED();
+  return kSbSystemDeviceTypeUnknown;
+}
diff --git a/src/starboard/shared/win32/system_get_error_string.cc b/src/starboard/shared/win32/system_get_error_string.cc
new file mode 100644
index 0000000..5aa5074
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_error_string.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <sstream>
+
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/string.h"
+
+int SbSystemGetErrorString(SbSystemError error,
+                           char* out_string,
+                           int string_length) {
+  std::ostringstream out;
+  out << starboard::shared::win32::Win32ErrorCode(error);
+  if (out_string != nullptr) {
+    SbStringCopy(out_string, out.str().c_str(), string_length);
+  }
+  return static_cast<int>(out.str().size());
+}
diff --git a/src/starboard/shared/win32/system_get_last_error.cc b/src/starboard/shared/win32/system_get_last_error.cc
new file mode 100644
index 0000000..aa76753
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_last_error.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <windows.h>
+
+SbSystemError SbSystemGetLastError() {
+  return GetLastError();
+}
diff --git a/src/starboard/shared/win32/system_get_locale_id.cc b/src/starboard/shared/win32/system_get_locale_id.cc
new file mode 100644
index 0000000..99dbf34
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_locale_id.cc
@@ -0,0 +1,54 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <string>
+
+#include "starboard/log.h"
+#include "starboard/once.h"
+
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/wchar_utils.h"
+
+using starboard::shared::win32::DebugLogWinError;
+using starboard::shared::win32::wchar_tToUTF8;
+
+namespace {
+class LocaleString {
+ public:
+  static LocaleString* Get();
+  const char* value() const { return value_.c_str(); }
+
+ private:
+  LocaleString() {
+    wchar_t name[LOCALE_NAME_MAX_LENGTH];
+    int result = GetUserDefaultLocaleName(name, LOCALE_NAME_MAX_LENGTH);
+    if (result != 0) {
+      value_ = wchar_tToUTF8(name);
+    } else {
+      SB_LOG(ERROR) << "Error retrieving GetUserDefaultLocaleName";
+      DebugLogWinError();
+      value_ ="en-US";
+    }
+  }
+  std::string value_;
+};
+
+SB_ONCE_INITIALIZE_FUNCTION(LocaleString, LocaleString::Get);
+}  // namespace
+
+const char* SbSystemGetLocaleId() {
+  return LocaleString::Get()->value();
+}
diff --git a/src/starboard/shared/win32/system_get_number_of_processors.cc b/src/starboard/shared/win32/system_get_number_of_processors.cc
new file mode 100644
index 0000000..59ec4e0
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_number_of_processors.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <windows.h>
+
+int SbSystemGetNumberOfProcessors() {
+  // Note that this returns the number of logical processors.
+  SYSTEM_INFO system_info = {0};
+  GetSystemInfo(&system_info);
+  return system_info.dwNumberOfProcessors;
+}
diff --git a/src/starboard/shared/win32/system_get_total_cpu_memory.cc b/src/starboard/shared/win32/system_get_total_cpu_memory.cc
new file mode 100644
index 0000000..7c91399
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_total_cpu_memory.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <windows.h>
+
+int64_t SbSystemGetTotalCPUMemory() {
+  MEMORYSTATUSEX statex = {0};
+  statex.dwLength = sizeof(statex);
+  GlobalMemoryStatusEx(&statex);
+  return static_cast<int64_t>(statex.ullTotalPhys);
+}
diff --git a/src/starboard/shared/win32/system_get_used_cpu_memory.cc b/src/starboard/shared/win32/system_get_used_cpu_memory.cc
new file mode 100644
index 0000000..7cddea1
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_used_cpu_memory.cc
@@ -0,0 +1,29 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <windows.h>
+
+#include "starboard/log.h"
+
+int64_t SbSystemGetUsedCPUMemory() {
+  MEMORYSTATUSEX statex = {0};
+  statex.dwLength = sizeof(statex);
+  GlobalMemoryStatusEx(&statex);
+  int64_t remaining_bytes = static_cast<int64_t>(statex.ullTotalPhys) -
+                            static_cast<int64_t>(statex.ullAvailPhys);
+  SB_DCHECK(remaining_bytes >= 0);
+  return remaining_bytes;
+}
diff --git a/src/starboard/shared/win32/thread_private.cc b/src/starboard/shared/win32/thread_private.cc
index e0ee6b1..e7e17cb 100644
--- a/src/starboard/shared/win32/thread_private.cc
+++ b/src/starboard/shared/win32/thread_private.cc
@@ -52,8 +52,17 @@
 }
 
 SbThreadPrivate* GetCurrentSbThreadPrivate() {
-  return static_cast<SbThreadPrivate*>(SbThreadGetLocalValue(
-      GetThreadSubsystemSingleton()->thread_private_key_));
+  SbThreadPrivate* sb_thread_private =
+      static_cast<SbThreadPrivate*>(SbThreadGetLocalValue(
+          GetThreadSubsystemSingleton()->thread_private_key_));
+  if (sb_thread_private == nullptr) {
+    // We are likely on a thread we did not create, so TLS needs to be setup.
+    RegisterMainThread();
+    sb_thread_private = static_cast<SbThreadPrivate*>(SbThreadGetLocalValue(
+        GetThreadSubsystemSingleton()->thread_private_key_));
+    // TODO: Clean up TLS storage for threads we do not create.
+  }
+  return sb_thread_private;
 }
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/wchar_utils.h b/src/starboard/shared/win32/wchar_utils.h
index 948b025..304e7d7 100644
--- a/src/starboard/shared/win32/wchar_utils.h
+++ b/src/starboard/shared/win32/wchar_utils.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN32_WCHAR_UTILS_H_
-#define STARBOARD_WIN32_WCHAR_UTILS_H_
+#ifndef STARBOARD_SHARED_WIN32_WCHAR_UTILS_H_
+#define STARBOARD_SHARED_WIN32_WCHAR_UTILS_H_
 
 #include <codecvt>
 #include <cwchar>
@@ -40,8 +40,19 @@
   return converter.from_bytes(str);
 }
 
+#if defined(__cplusplus_winrt)
+inline std::string platformStringToString(Platform::String^ to_convert) {
+  std::wstring ws(to_convert->Begin(), to_convert->End());
+  return wchar_tToUTF8(ws.data(), ws.size());
+}
+
+inline Platform::String^ stringToPlatformString(const std::string& to_convert) {
+  return ref new Platform::String(CStringToWString(to_convert.c_str()).c_str());
+}
+#endif
+
 }  // namespace win32
 }  // namespace shared
 }  // namespace starboard
 
-#endif  // STARBOARD_WIN32_WCHAR_UTILS_H_
+#endif  // STARBOARD_SHARED_WIN32_WCHAR_UTILS_H_
diff --git a/src/starboard/shared/x11/application_x11.cc b/src/starboard/shared/x11/application_x11.cc
index 369c2de..9e11f7c 100644
--- a/src/starboard/shared/x11/application_x11.cc
+++ b/src/starboard/shared/x11/application_x11.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/shared/x11/application_x11.h"
 
+#include <math.h>
 #include <stdlib.h>
 #include <unistd.h>
 #define XK_3270  // for XK_3270_BackTab
@@ -41,7 +42,12 @@
 namespace shared {
 namespace x11 {
 namespace {
-const int kKeyboardDeviceId = 1;
+
+enum {
+  kNoneDeviceId,
+  kKeyboardDeviceId,
+  kMouseDeviceId,
+};
 
 // Key translation taken from cobalt/system_window/linux/keycode_conversion.cc
 // Eventually, that code should be removed in favor of this code.
@@ -522,6 +528,47 @@
   return key;
 }
 
+bool XButtonEventIsWheelEvent(XButtonEvent* event) {
+  // Buttons 4, 5, 6, and 7 are wheel events.
+  return event->button >= 4 && event->button <= 7;
+}
+
+enum {
+  kWheelUpButton = 4,
+  kWheelDownButton = 5,
+  kWheelLeftButton = 6,
+  kWheelRightButton = 7,
+  kPointerBackButton = 8,
+  kPointerForwardButton = 9,
+};
+
+SbKey XButtonEventToSbKey(XButtonEvent* event) {
+  SbKey key;
+  switch (event->button) {
+    case Button1:
+      return kSbKeyMouse1;
+    case Button2:
+      return kSbKeyMouse2;
+    case Button3:
+      return kSbKeyMouse3;
+    case kWheelUpButton:
+      return kSbKeyUp;
+    case kWheelDownButton:
+      return kSbKeyDown;
+    case kWheelLeftButton:
+      return kSbKeyLeft;
+    case kWheelRightButton:
+      return kSbKeyRight;
+    case kPointerBackButton:
+      return kSbKeyBrowserBack;
+    case kPointerForwardButton:
+      return kSbKeyBrowserForward;
+    default:
+      return kSbKeyUnknown;
+  }
+  return key;
+}
+
 // Get a SbKeyLocation from an XKeyEvent.
 SbKeyLocation XKeyEventToSbKeyLocation(XKeyEvent* event) {
   KeySym keysym = XK_VoidSymbol;
@@ -543,20 +590,61 @@
 }
 
 // Get an SbKeyModifiers from an XKeyEvent.
-unsigned int XKeyEventToSbKeyModifiers(XKeyEvent* event) {
+unsigned int XEventStateToSbKeyModifiers(unsigned int state) {
   unsigned int key_modifiers = kSbKeyModifiersNone;
-  if (event->state & Mod1Mask) {
+  if (state & Mod1Mask) {
     key_modifiers |= kSbKeyModifiersAlt;
   }
-  if (event->state & ControlMask) {
+  if (state & ControlMask) {
     key_modifiers |= kSbKeyModifiersCtrl;
   }
-  if (event->state & ShiftMask) {
+  if (state & Mod4Mask) {
+    key_modifiers |= kSbKeyModifiersMeta;
+  }
+  if (state & ShiftMask) {
     key_modifiers |= kSbKeyModifiersShift;
   }
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  if (state & Button1Mask) {
+    key_modifiers |= kSbKeyModifiersPointerButtonLeft;
+  }
+  if (state & Button2Mask) {
+    key_modifiers |= kSbKeyModifiersPointerButtonMiddle;
+  }
+  if (state & Button3Mask) {
+    key_modifiers |= kSbKeyModifiersPointerButtonRight;
+  }
+#endif
+  // Note: Button 4 and button 5 represent vertical wheel motion. As a result,
+  // Button4Mask and Button5Mask do not represent a useful mouse button state
+  // since the wheel up and wheel down do not have 'buttons' that can be held
+  // down. The state of the Back and Forward mouse buttons is not reported to
+  // X11 clients. This is not a hardware limitation, but a result of historical
+  // Xorg X11 mouse driver button mapping choices.
   return key_modifiers;
 }
 
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+SbInputVector XButtonEventToSbInputVectorDelta(XButtonEvent* event) {
+  SbInputVector delta = {0, 0};
+  switch (event->button) {
+    case kWheelUpButton:
+      delta.y = -1;
+      break;
+    case kWheelDownButton:
+      delta.y = 1;
+      break;
+    case kWheelLeftButton:
+      delta.x = -1;
+      break;
+    case kWheelRightButton:
+      delta.x = 1;
+      break;
+  }
+  return delta;
+}
+#endif
+
 bool XNextEventTimed(Display* display, XEvent* out_event, SbTime duration) {
   if (XPending(display) == 0) {
     if (duration <= SbTime()) {
@@ -855,7 +943,63 @@
       data->device_id = kKeyboardDeviceId;
       data->key = XKeyEventToSbKey(x_key_event);
       data->key_location = XKeyEventToSbKeyLocation(x_key_event);
-      data->key_modifiers = XKeyEventToSbKeyModifiers(x_key_event);
+      data->key_modifiers = XEventStateToSbKeyModifiers(x_key_event->state);
+      data->position.x = x_key_event->x;
+      data->position.y = x_key_event->y;
+      return new Event(kSbEventTypeInput, data, &DeleteDestructor<SbInputData>);
+    }
+    case ButtonPress:
+    case ButtonRelease: {
+      XButtonEvent* x_button_event = reinterpret_cast<XButtonEvent*>(x_event);
+      SbInputData* data = new SbInputData();
+      SbMemorySet(data, 0, sizeof(*data));
+      data->window = FindWindow(x_button_event->window);
+      SB_DCHECK(SbWindowIsValid(data->window));
+      data->key = XButtonEventToSbKey(x_button_event);
+      bool is_press_event = ButtonPress == x_event->type;
+      data->type =
+          is_press_event ? kSbInputEventTypePress : kSbInputEventTypeUnpress;
+      data->device_type = kSbInputDeviceTypeMouse;
+      if (XButtonEventIsWheelEvent(x_button_event)) {
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+        if (!is_press_event) {
+          // unpress events from the wheel are discarded.
+          return NULL;
+        }
+        data->pressure = NAN;
+        data->size = {NAN, NAN};
+        data->tilt = {NAN, NAN};
+        data->type = kSbInputEventTypeWheel;
+        data->delta = XButtonEventToSbInputVectorDelta(x_button_event);
+#else
+        // This version of Starboard does not support wheel event types, send
+        // keyboard event types instead.
+        data->device_type = kSbInputDeviceTypeKeyboard;
+#endif
+      }
+      data->device_id = kMouseDeviceId;
+      data->key_modifiers = XEventStateToSbKeyModifiers(x_button_event->state);
+      data->position.x = x_button_event->x;
+      data->position.y = x_button_event->y;
+      return new Event(kSbEventTypeInput, data, &DeleteDestructor<SbInputData>);
+    }
+    case MotionNotify: {
+      XMotionEvent* x_motion_event = reinterpret_cast<XMotionEvent*>(x_event);
+      SbInputData* data = new SbInputData();
+      SbMemorySet(data, 0, sizeof(*data));
+      data->window = FindWindow(x_motion_event->window);
+      SB_DCHECK(SbWindowIsValid(data->window));
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+      data->pressure = NAN;
+      data->size = {NAN, NAN};
+      data->tilt = {NAN, NAN};
+#endif
+      data->type = kSbInputEventTypeMove;
+      data->device_type = kSbInputDeviceTypeMouse;
+      data->device_id = kMouseDeviceId;
+      data->key_modifiers = XEventStateToSbKeyModifiers(x_motion_event->state);
+      data->position.x = x_motion_event->x;
+      data->position.y = x_motion_event->y;
       return new Event(kSbEventTypeInput, data, &DeleteDestructor<SbInputData>);
     }
     case FocusIn: {
@@ -866,14 +1010,6 @@
       Pause(NULL, NULL);
       return NULL;
     }
-    case MapNotify: {
-      Resume(NULL, NULL);
-      return NULL;
-    }
-    case UnmapNotify: {
-      Suspend(NULL, NULL);
-      return NULL;
-    }
     case ConfigureNotify: {
       // Ignore window size, position, border, and stacking order events.
       return NULL;
diff --git a/src/starboard/shared/x11/application_x11.h b/src/starboard/shared/x11/application_x11.h
index 182f58c..f61f8ba 100644
--- a/src/starboard/shared/x11/application_x11.h
+++ b/src/starboard/shared/x11/application_x11.h
@@ -54,6 +54,11 @@
                    int width,
                    int height) SB_OVERRIDE;
 
+#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
+  bool IsStartImmediate() SB_OVERRIDE { return !HasPreloadSwitch(); }
+  bool IsPreloadImmediate() SB_OVERRIDE { return HasPreloadSwitch(); }
+#endif  // SB_API_VERSION >= SB_PRELOAD_API_VERSION
+
  protected:
   // --- Application overrides ---
   void Initialize() SB_OVERRIDE;
diff --git a/src/starboard/shared/x11/window_internal.cc b/src/starboard/shared/x11/window_internal.cc
index 8235d61..108da65 100644
--- a/src/starboard/shared/x11/window_internal.cc
+++ b/src/starboard/shared/x11/window_internal.cc
@@ -131,6 +131,10 @@
 #endif  // SB_API_VERSION >= 4 ||
         // SB_IS(PLAYER_PUNCHED_OUT)
 
+  XSelectInput(display, window,
+               VisibilityChangeMask | ExposureMask | FocusChangeMask |
+                   StructureNotifyMask | KeyPressMask | KeyReleaseMask |
+                   ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
   XMapWindow(display, window);
 }
 
diff --git a/src/starboard/speech_recognizer.h b/src/starboard/speech_recognizer.h
index 48af10c..7b1bb59 100644
--- a/src/starboard/speech_recognizer.h
+++ b/src/starboard/speech_recognizer.h
@@ -35,8 +35,7 @@
 #include "starboard/export.h"
 #include "starboard/types.h"
 
-#if SB_HAS(SPEECH_RECOGNIZER) && \
-    SB_API_VERSION >= SB_SPEECH_RECOGNIZER_API_VERSION
+#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #ifdef __cplusplus
 extern "C" {
@@ -195,7 +194,6 @@
 }  // extern "C"
 #endif
 
-#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >=
-        // SB_SPEECH_RECOGNIZER_API_VERSION
+#endif  // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
 
 #endif  // STARBOARD_SPEECH_RECOGNIZER_H_
diff --git a/src/starboard/speech_synthesis.h b/src/starboard/speech_synthesis.h
index fe0de16..a8a46d6 100644
--- a/src/starboard/speech_synthesis.h
+++ b/src/starboard/speech_synthesis.h
@@ -29,7 +29,7 @@
 #include "starboard/export.h"
 #include "starboard/types.h"
 
-#if SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#if SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 
 #ifdef __cplusplus
 extern "C" {
@@ -64,6 +64,6 @@
 }  // extern "C"
 #endif
 
-#endif  // SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3)
+#endif  // SB_HAS(SPEECH_SYNTHESIS) && SB_API_VERSION >= 3
 
 #endif  // STARBOARD_SPEECH_SYNTHESIS_H_
diff --git a/src/starboard/stub/gyp_configuration.gypi b/src/starboard/stub/gyp_configuration.gypi
index 10e4c16..b93baf9 100644
--- a/src/starboard/stub/gyp_configuration.gypi
+++ b/src/starboard/stub/gyp_configuration.gypi
@@ -141,7 +141,7 @@
       },
     }, # end of configurations
     'target_conditions': [
-      ['cobalt_code==1', {
+      ['sb_pedantic_warnings==1', {
         'cflags': [
           '-Wall',
           '-Wextra',
diff --git a/src/starboard/system.h b/src/starboard/system.h
index a56d034..dd08f44 100644
--- a/src/starboard/system.h
+++ b/src/starboard/system.h
@@ -109,17 +109,17 @@
   // A universally-unique ID for the current user.
   kSbSystemPropertyPlatformUuid,
 
-#if SB_VERSION(2)
+#if SB_API_VERSION >= 2
   // The Google Speech API key. The platform manufacturer is responsible
   // for registering a Google Speech API key for their products. In the API
   // Console (http://developers.google.com/console), you can enable the
   // Speech APIs and generate a Speech API key.
   kSbSystemPropertySpeechApiKey,
 #endif  // SB_VERSION(2)
-#if SB_API_VERSION >= SB_USER_AGENT_AUX_SYSTEM_PROPERTY_API_VERSION
+#if SB_API_VERSION >= 5
   // A field that, if available, is appended to the user agent
   kSbSystemPropertyUserAgentAuxField,
-#endif  // SB_API_VERSION >= SB_USER_AGENT_AUX_SYSTEM_PROPERTY_API_VERSION
+#endif  // SB_API_VERSION >= 5
 } SbSystemPropertyId;
 
 // Enumeration of device types.
@@ -185,14 +185,18 @@
 // |SbSystemRaisePlatformError| function.
 typedef enum SbSystemPlatformErrorType {
   // Cobalt received a network connection error, or a network disconnection
-  // event.
+  // event. If the |response| passed to |SbSystemPlatformErrorCallback| is
+  // |kSbSystemPlatformErrorResponsePositive| then the request should be
+  // retried, otherwise the app should be stopped.
   kSbSystemPlatformErrorTypeConnectionError,
 
-  // The current user is not signed in (e.g. to PSN network).
+#if SB_API_VERSION < SB_PLATFORM_ERROR_CLEANUP_API_VERSION
+  // The current user is not signed in.
   kSbSystemPlatformErrorTypeUserSignedOut,
 
   // The current user does not meet the age requirements to use the app.
   kSbSystemPlatformErrorTypeUserAgeRestricted
+#endif
 } SbSystemPlatformErrorType;
 
 // Possible responses for |SbSystemPlatformErrorCallback|.
diff --git a/src/starboard/time.h b/src/starboard/time.h
index bae41d4..c9d2a3f 100644
--- a/src/starboard/time.h
+++ b/src/starboard/time.h
@@ -91,7 +91,7 @@
 // Gets a monotonically increasing time representing right now.
 SB_EXPORT SbTimeMonotonic SbTimeGetMonotonicNow();
 
-#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW)
 // Gets a monotonically increasing time representing how long the current
 // thread has been in the executing state (i.e. not pre-empted nor waiting
 // on an event). This is not necessarily total time and is intended to allow
diff --git a/src/starboard/tizen/armv7l/configuration_public.h b/src/starboard/tizen/armv7l/configuration_public.h
index f27972c..3c2d612 100644
--- a/src/starboard/tizen/armv7l/configuration_public.h
+++ b/src/starboard/tizen/armv7l/configuration_public.h
@@ -22,6 +22,20 @@
 #ifndef STARBOARD_TIZEN_ARMV7L_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_TIZEN_ARMV7L_CONFIGURATION_PUBLIC_H_
 
+// The API version implemented by this platform.
+#define SB_API_VERSION 4
+
+// --- System Header Configuration -------------------------------------------
+
+// Whether the current platform has microphone supported.
+#define SB_HAS_MICROPHONE 0
+
+// Whether the current platform has speech recognizer.
+#define SB_HAS_SPEECH_RECOGNIZER 0
+
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 0
+
 // --- Architecture Configuration --------------------------------------------
 
 // Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
@@ -100,64 +114,15 @@
 // 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
+// The maximum audio bitrate the platform can decode.  The following value
+// equals to 2M bytes per seconds which is more than enough for compressed
+// audio.
+#define SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND (16 * 1024 * 1024)
 
-// 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
-
-#if SB_API_VERSION < 4
-
-// 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 (80U * 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
-
-#endif  // SB_API_VERSION < 4
+// The maximum video bitrate the platform can decode.  The following value
+// equals to 25M bytes per seconds which is more than enough for compressed
+// video.
+#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
 
 // Specifies whether this platform has webm/vp9 support.  This should be set to
 // non-zero on platforms with webm/vp9 support.
@@ -197,4 +162,15 @@
 // Include the Tizen configuration that's common between all Tizen.
 #include "starboard/tizen/shared/configuration_public.h"
 
+// --- 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
+
 #endif  // STARBOARD_TIZEN_ARMV7L_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/tizen/armv7l/gyp_configuration.gypi b/src/starboard/tizen/armv7l/gyp_configuration.gypi
index 711f40a..9377135 100644
--- a/src/starboard/tizen/armv7l/gyp_configuration.gypi
+++ b/src/starboard/tizen/armv7l/gyp_configuration.gypi
@@ -15,8 +15,6 @@
 {
   'variables': {
     'target_arch': 'arm',
-    'target_os': 'linux',
-    'tizen_os': 1,
 
     'gl_type': 'system_gles2',
 
@@ -25,25 +23,25 @@
     'armv7': 1,
     'arm_neon': 0,
 
-    # Some package use tizen system instead of third_party
-    'use_system_icu': 1,
-    'use_system_libxml': 1,
-
-    # scratch surface cache is designed to choose large offscreen surfaces so
-    # that they can be maximally reused, it is not a very good fit for a tiled
-    # renderer.
-    '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,
-
     # Reduce garbage collection threshold from the default of 8MB in order to
     # save on memory.  This will mean that garbage collection occurs more
     # frequently.
     'mozjs_garbage_collection_threshold_in_bytes%': 4 * 1024 * 1024,
 
+    # The rasterizer does not benefit much from rendering only the dirty
+    # region. Disable this option since it costs GPU time.
+    'render_dirty_region_only': 0,
+
+    # Use media source extension implementation that is conformed to the
+    # Candidate Recommandation of July 5th 2016.
+    'cobalt_media_source_2016': 1,
+    'cobalt_media_buffer_storage_type': 'memory',
+    'cobalt_media_buffer_initial_capacity': 26 * 1024 * 1024,
+    'cobalt_media_buffer_allocation_unit': 0 * 1024 * 1024,
+    'cobalt_media_buffer_non_video_budget': 5 * 1024 * 1024,
+    'cobalt_media_buffer_video_budget_1080p': 16 * 1024 * 1024,
+    'cobalt_media_buffer_video_budget_4k': 60 * 1024 * 1024,
+
     'platform_libraries': [
       '-lasound',
       '-lavcodec',
@@ -51,82 +49,9 @@
       '-lavutil',
       '-ldlog',
     ],
-    'linker_flags': [
-    ],
-    'linker_flags_gold': [
-      '-O3',
-      '-flto',
-    ],
-    'compiler_flags_debug': [
-      '-O0',
-    ],
-    'compiler_flags_devel': [
-      '-O2',
-    ],
-    'compiler_flags_cc_qa': [
-      '-fno-rtti',
-    ],
-    'compiler_flags_qa': [
-      '-O3',
-    ],
-    'compiler_flags_cc_gold': [
-      '-fno-rtti',
-    ],
-    'compiler_flags_gold': [
-      '-O3',
-    ],
-    'conditions': [
-      ['cobalt_fastbuild==0', {
-        'compiler_flags_debug': [
-          '-g',
-        ],
-        'compiler_flags_devel': [
-          '-g',
-        ],
-        'compiler_flags_qa': [
-        ],
-        'compiler_flags_gold': [
-          '-flto',
-        ],
-      }],
-    ],
   },
 
   'target_defaults': {
-    'defines': [
-      # Cobalt on Tizen flag
-      'COBALT_TIZEN',
-      'PNG_SKIP_SETJMP_CHECK',
-      '__STDC_FORMAT_MACROS', # so that we get PRI*
-      # Enable GNU extensions to get prototypes like ffsl.
-      '_GNU_SOURCE=1',
-    ],
-    'cflags': [
-      '-pthread',
-      # Do not warn about locally defined but not used.
-      '-Wno-unused-local-typedefs',
-      # Do not warn about XXX is deprecated.
-      '-Wno-deprecated-declarations',
-      # Do not warn about missing initializer for member XXX.
-      '-Wno-missing-field-initializers',
-      # Do not warn about unused functions.
-      '-Wno-unused-function',
-      # Do not warn about type qualifiers ignored on function return type.
-      '-Wno-ignored-qualifiers',
-      # Do not warn about the use of multi-line comments.
-      '-Wno-comment',
-      # Do not warn about sign compares.
-      '-Wno-sign-compare',
-    ],
-    'cflags_c': [
-      '-std=c11',
-    ],
-    'cflags_cc': [
-      '-std=gnu++11',
-    ],
-    'ldflags': [
-      '-pthread',
-    ],
     'default_configuration': 'tizen-armv7l_debug',
     'configurations': {
       'tizen-armv7l_debug': {
@@ -143,4 +68,8 @@
       },
     }, # end of configurations
   }, # end of target_defaults
+
+  'includes': [
+    '../shared/gyp_configuration.gypi',
+  ],
 }
diff --git a/src/starboard/tizen/armv7l/starboard_common.gyp b/src/starboard/tizen/armv7l/starboard_common.gyp
index 6da8396..1837af9 100644
--- a/src/starboard/tizen/armv7l/starboard_common.gyp
+++ b/src/starboard/tizen/armv7l/starboard_common.gyp
@@ -73,6 +73,7 @@
         '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
         '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
         '<(DEPTH)/starboard/shared/linux/page_internal.cc',
+        '<(DEPTH)/starboard/shared/linux/socket_get_interface_address.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',
@@ -104,7 +105,6 @@
         '<(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',
@@ -160,9 +160,7 @@
         '<(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',
@@ -224,18 +222,18 @@
         '<(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/wayland/application_wayland.cc',
         '<(DEPTH)/starboard/shared/wayland/window_create.cc',
         '<(DEPTH)/starboard/shared/wayland/window_destroy.cc',
         '<(DEPTH)/starboard/shared/wayland/window_get_platform_handle.cc',
         '<(DEPTH)/starboard/shared/wayland/window_get_size.cc',
         '<(DEPTH)/starboard/shared/wayland/window_internal.cc',
         '<(DEPTH)/starboard/tizen/shared/system_get_device_type.cc',
-        '<(DEPTH)/starboard/tizen/shared/system_get_path.cc',
+        '<(DEPTH)/starboard/tizen/shared/thread_create.cc',
         '<(DEPTH)/starboard/tizen/shared/audio/audio_sink_adaptor.cc',
         '<(DEPTH)/starboard/tizen/shared/audio/audio_sink_private.cc',
         '<(DEPTH)/starboard/tizen/shared/log/log.cc',
         '<(DEPTH)/starboard/tizen/shared/get_home_directory.cc',
+        '<(DEPTH)/starboard/tizen/shared/memory_flush.cc',
       ],
       'defines': [
         # This must be defined when building Starboard, and must not when
diff --git a/src/starboard/tizen/armv7l/starboard_platform.gyp b/src/starboard/tizen/armv7l/starboard_platform.gyp
index 8cc53ab..01aa556 100644
--- a/src/starboard/tizen/armv7l/starboard_platform.gyp
+++ b/src/starboard/tizen/armv7l/starboard_platform.gyp
@@ -24,35 +24,55 @@
         '<(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/starboard/media/codec_util.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/codec_util.h',
         '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_get_audio_configuration_stereo_only.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_get_audio_output_count_stereo_only.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_is_video_supported_h264_1080p_sfr_only.cc',
         '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_util.cc',
+        '<(DEPTH)/starboard/shared/starboard/media/media_util.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/decoded_audio_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_frame_tracker.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.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/player_components.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_internal.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_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/job_queue.cc',
         '<(DEPTH)/starboard/shared/starboard/player/job_queue.h',
         '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_get_current_frame.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.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_playback_rate.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',
@@ -60,21 +80,39 @@
         '<(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/stub/accessibility_get_display_settings.cc',
+        '<(DEPTH)/starboard/shared/stub/accessibility_get_text_to_speech_settings.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_create_transformer.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_destroy_transformer.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_get_tag.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_set_authenticated_data.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_set_initialization_vector.cc',
+        '<(DEPTH)/starboard/shared/stub/cryptography_transform.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_create_egl.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_get_info.cc',
+        '<(DEPTH)/starboard/shared/stub/decode_target_release.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/media_is_transfer_characteristics_supported.cc',
+        '<(DEPTH)/starboard/shared/wayland/application_wayland.cc',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.cc',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.h',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_resampler.cc',
+        '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_resampler.h',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_common.cc',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_common.h',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc',
         '<(DEPTH)/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.h',
         '<(DEPTH)/starboard/tizen/shared/main.cc',
         '<(DEPTH)/starboard/tizen/shared/player/filter/ffmpeg_player_components_impl.cc',
+        '<(DEPTH)/starboard/tizen/shared/system_get_path.cc',
         'atomic_public.h',
         'configuration_public.h',
         'system_get_property.cc',
diff --git a/src/starboard/tizen/packaging/com.youtube.cobalt.spec b/src/starboard/tizen/packaging/com.youtube.cobalt.spec
index de10239..c42faff 100644
--- a/src/starboard/tizen/packaging/com.youtube.cobalt.spec
+++ b/src/starboard/tizen/packaging/com.youtube.cobalt.spec
@@ -31,8 +31,12 @@
 BuildRequires: pkgconfig(wayland-client)
 BuildRequires: pkgconfig(wayland-egl)
 %if "%{?target}" == "samsungtv"
+BuildRequires: pkgconfig(appcore-common)
+BuildRequires: pkgconfig(app-control-api)
 BuildRequires: pkgconfig(capi-media-player)
 BuildRequires: pkgconfig(drmdecrypt)
+BuildRequires: pkgconfig(soc-pq-interface)
+BuildRequires: pkgconfig(vconf-internal-keys-tv)
 %else
 BuildRequires: pkgconfig(libavcodec)
 BuildRequires: pkgconfig(libavformat)
@@ -46,6 +50,13 @@
 %setup -q
 
 #define specific parameters
+
+%if "%{?bin_name}"
+%define _name %{bin_name}
+%else
+%define _name cobalt
+%endif
+
 %if "%{?build_type}"
 %define _build_type %{build_type}
 %else
@@ -64,7 +75,6 @@
 %define _chipset armv7l
 %endif
 
-%define _name cobalt
 %define _pkgname com.youtube.cobalt
 %define _outdir src/out/%{_target}-%{_chipset}_%{_build_type}
 %define _manifestdir /usr/share/packages
@@ -100,16 +110,46 @@
 install -d %{buildroot}%{_bindir}
 install -d %{buildroot}%{_manifestdir}
 install -d %{buildroot}%{_contentdir}/data/fonts/
+%if "%{_name}" == "all"
+install -m 0755 %{_outdir}/accessibility_test %{_outdir}/audio_test %{_outdir}/base_test \
+                %{_outdir}/base_unittests %{_outdir}/bindings_sandbox %{_outdir}/bindings_test \
+                %{_outdir}/browser_test %{_outdir}/cobalt %{_outdir}/crypto_unittests %{_outdir}/csp_test \
+                %{_outdir}/cssom_test %{_outdir}/css_parser_test %{_outdir}/dom_parser_test \
+                %{_outdir}/dom_test %{_outdir}/eztime_test %{_outdir}/image_decoder_sandbox \
+                %{_outdir}/layout_benchmarks %{_outdir}/layout_test %{_outdir}/layout_tests \
+                %{_outdir}/loader_test %{_outdir}/math_test %{_outdir}/media2_sandbox \
+                %{_outdir}/media_source_sandbox %{_outdir}/mozjs %{_outdir}/mozjs_engine_test \
+                %{_outdir}/mozjs_keyword_header_gen %{_outdir}/mozjs_opcode_length_header_gen \
+                %{_outdir}/nb_test %{_outdir}/network_test %{_outdir}/nplb %{_outdir}/nplb_blitter_pixel_tests \
+                %{_outdir}/poem_unittests %{_outdir}/renderer_benchmark %{_outdir}/renderer_sandbox \
+                %{_outdir}/renderer_test %{_outdir}/render_tree_test %{_outdir}/reuse_allocator_benchmark \
+                %{_outdir}/sample_benchmark %{_outdir}/scaling_text_sandbox %{_outdir}/simple_example \
+                %{_outdir}/simple_example_test %{_outdir}/snapshot_app_stats %{_outdir}/speech_sandbox \
+                %{_outdir}/sql_unittests %{_outdir}/starboard_blitter_example %{_outdir}/starboard_glclear_example \
+                %{_outdir}/starboard_window_example %{_outdir}/storage_test %{_outdir}/trace_event_test \
+                %{_outdir}/web_animations_test %{_outdir}/webdriver_test %{_outdir}/web_media_player_sandbox \
+                %{_outdir}/web_platform_tests %{_outdir}/websocket_test %{_outdir}/xhr_test \
+                %{buildroot}%{_bindir}
+%else
 install -m 0755 %{_outdir}/%{_name} %{buildroot}%{_bindir}
+%endif
+
 %if "%{?target}" == "samsungtv"
-cp -rd %{_outdir}/content/data/fonts/fonts.xml %{buildroot}%{_contentdir}/data/fonts/
+cp -rd src/third_party/starboard/samsungtv/%{_chipset}/fonts/fonts.xml %{buildroot}%{_contentdir}/data/fonts/
 %else
 cp -rd %{_outdir}/content/data/fonts %{buildroot}%{_contentdir}/data/
 %endif
+
 cp -rd %{_outdir}/content/data/ssl %{buildroot}%{_contentdir}/data/
+
 %if %{_build_type} != "gold"
 cp -rd %{_outdir}/content/data/web %{buildroot}%{_contentdir}/data/
 %endif
+
+%if %{_name} != "cobalt"
+cp -rd %{_outdir}/content/dir_source_root %{buildroot}%{_contentdir}/
+%endif
+
 cp src/starboard/tizen/packaging/%{_pkgname}.xml %{buildroot}%{_manifestdir}
 
 %post
@@ -122,7 +162,11 @@
 %files
 %manifest src/starboard/tizen/packaging/%{_pkgname}.manifest
 %defattr(-,root,root,-)
+%if "%{_name}" == "all"
+%{_bindir}/*
+%else
 %{_bindir}/%{_name}
+%endif
 %{_contentdir}/*
 %{_manifestdir}/*
 
diff --git a/src/starboard/tizen/packaging/gbs.conf b/src/starboard/tizen/packaging/gbs.conf
index 39eaa67..04754b0 100644
--- a/src/starboard/tizen/packaging/gbs.conf
+++ b/src/starboard/tizen/packaging/gbs.conf
@@ -21,7 +21,7 @@
 url=~/GBS-ROOT-COBALT/local/repos/cobalt/
 
 [repo.cobalt_base]
-url = http://download.tizen.org/snapshots/tizen/base/latest/repos/arm/packages/
+url = http://download.tizen.org/snapshots/tizen/3.0-base/latest/repos/arm/packages/
 
 [repo.cobalt_tizen3.0]
 url = http://download.tizen.org/snapshots/tizen/3.0-tv/latest/repos/arm-wayland/packages/
diff --git a/src/starboard/tizen/shared/audio/audio_sink_private.cc b/src/starboard/tizen/shared/audio/audio_sink_private.cc
index 4d6440a..52c3b95 100644
--- a/src/starboard/tizen/shared/audio/audio_sink_private.cc
+++ b/src/starboard/tizen/shared/audio/audio_sink_private.cc
@@ -51,9 +51,8 @@
                 << "channels " << channels << ", frequency "
                 << sampling_frequency_hz << ", sample_type "
                 << audio_sample_type << ", storage_type "
-                << audio_frame_storage_type << ", frame_buffers "
-                << static_cast<int>(frame_buffers) << ", frame_buff_sz "
-                << frames_per_channel;
+                << audio_frame_storage_type << ", frame_buffers " << std::hex
+                << frame_buffers << ", frame_buff_sz " << frames_per_channel;
 
   int capi_ret;
   capi_ret = audio_out_create_new(sampling_frequency_hz, AUDIO_CHANNEL_STEREO,
@@ -191,11 +190,14 @@
       }
       consumed_frames = bytes_written / bytes_per_frame;
 
+      // This is commented : Sleep can cause 'underrun'
+      // update_source_status_func controls data's timing.
       // SbThreadSleep(consumed_frames * kSbTimeSecond /
       // sampling_frequency_hz_);
       consume_frames_func_(consumed_frames, context_);
     } else {
       if (!is_paused_) {
+        audio_out_drain(capi_audio_out_);
         audio_out_unprepare(capi_audio_out_);
         is_paused_ = true;
         SB_DLOG(INFO) << "[MEDIA] audio_out_pause";
diff --git a/src/starboard/tizen/shared/configuration_public.h b/src/starboard/tizen/shared/configuration_public.h
index ff7d28d..9109655 100644
--- a/src/starboard/tizen/shared/configuration_public.h
+++ b/src/starboard/tizen/shared/configuration_public.h
@@ -17,9 +17,6 @@
 #ifndef STARBOARD_TIZEN_SHARED_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_TIZEN_SHARED_CONFIGURATION_PUBLIC_H_
 
-// The API version implemented by this platform.
-#define SB_API_VERSION 1
-
 // --- System Header Configuration -------------------------------------------
 
 // Any system headers listed here that are not provided by the platform will be
@@ -49,9 +46,6 @@
 // Whether the current platform provides the standard header float.h.
 #define SB_HAS_FLOAT_H 1
 
-// Whether the current platform provides ssize_t.
-#define SB_HAS_SSIZE_T 1
-
 // Type detection for wchar_t.
 #if defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
@@ -251,7 +245,7 @@
 // --- Network Configuration -------------------------------------------------
 
 // Specifies whether this platform supports IPV6.
-#define SB_HAS_IPV6 1
+#define SB_HAS_IPV6 0
 
 // Specifies whether this platform supports pipe.
 #define SB_HAS_PIPE 1
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.cc
index dec844c..ebe970b 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -22,14 +22,31 @@
 namespace shared {
 namespace ffmpeg {
 
+namespace {
+
+AVCodecID GetFfmpegCodecIdByMediaCodec(SbMediaAudioCodec audio_codec) {
+  switch (audio_codec) {
+    case kSbMediaAudioCodecAac:
+      return AV_CODEC_ID_AAC;
+    case kSbMediaAudioCodecOpus:
+      return AV_CODEC_ID_OPUS;
+    default:
+      return AV_CODEC_ID_NONE;
+  }
+}
+
+}  // namespace
+
 AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
                            const SbMediaAudioHeader& audio_header)
-    : sample_type_(kSbMediaAudioSampleTypeInt16),
+    : audio_codec_(audio_codec),
+      sample_type_(kSbMediaAudioSampleTypeInt16),
       codec_context_(NULL),
       av_frame_(NULL),
       stream_ended_(false),
       audio_header_(audio_header) {
-  SB_DCHECK(audio_codec == kSbMediaAudioCodecAac);
+  SB_DCHECK(GetFfmpegCodecIdByMediaCodec(audio_codec) != AV_CODEC_ID_NONE)
+      << "Unsupported audio codec " << audio_codec;
 
   InitializeCodec();
 }
@@ -70,10 +87,11 @@
 
   if (decoded_audio_size > 0) {
     scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+        codec_context_->channels, sample_type_, GetStorageType(),
         input_buffer.pts(),
         codec_context_->channels * av_frame_->nb_samples *
             (sample_type_ == kSbMediaAudioSampleTypeInt16 ? 2 : 4));
-    SbMemoryCopy(decoded_audio->buffer(), av_frame_->extended_data,
+    SbMemoryCopy(decoded_audio->buffer(), *av_frame_->extended_data,
                  decoded_audio->size());
     decoded_audios_.push(decoded_audio);
   } else {
@@ -125,7 +143,7 @@
   }
 
   codec_context_->codec_type = AVMEDIA_TYPE_AUDIO;
-  codec_context_->codec_id = AV_CODEC_ID_AAC;
+  codec_context_->codec_id = GetFfmpegCodecIdByMediaCodec(audio_codec_);
   // Request_sample_fmt is set by us, but sample_fmt is set by the decoder.
   if (sample_type_ == kSbMediaAudioSampleTypeInt16) {
     codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16;
@@ -147,7 +165,7 @@
     return;
   }
 
-  int rv = avcodec_open2(codec_context_, codec, NULL);
+  int rv = OpenCodec(codec_context_, codec);
   if (rv < 0) {
     SB_LOG(ERROR) << "Unable to open codec";
     TeardownCodec();
@@ -163,7 +181,7 @@
 
 void AudioDecoder::TeardownCodec() {
   if (codec_context_) {
-    avcodec_close(codec_context_);
+    CloseCodec(codec_context_);
     av_free(codec_context_);
     codec_context_ = NULL;
   }
@@ -174,6 +192,5 @@
 }
 
 }  // namespace ffmpeg
-
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.h b/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.h
index cd88d46..7b74d28 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.h
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_audio_decoder.h
@@ -42,6 +42,9 @@
   void Reset() SB_OVERRIDE;
   SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE;
   int GetSamplesPerSecond() const SB_OVERRIDE;
+  bool CanAcceptMoreData() const SB_OVERRIDE {
+    return !stream_ended_ && decoded_audios_.size() <= kMaxDecodedAudiosSize;
+  }
 
   bool is_valid() const { return codec_context_ != NULL; }
 
@@ -49,6 +52,9 @@
   void InitializeCodec();
   void TeardownCodec();
 
+  static const int kMaxDecodedAudiosSize = 64;
+
+  SbMediaAudioCodec audio_codec_;
   SbMediaAudioSampleType sample_type_;
   AVCodecContext* codec_context_;
   AVFrame* av_frame_;
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.cc b/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.cc
index 3667141..7dc091c 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.cc
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.cc
@@ -15,6 +15,7 @@
 #include "starboard/tizen/shared/ffmpeg/ffmpeg_common.h"
 
 #include "starboard/log.h"
+#include "starboard/mutex.h"
 #include "starboard/once.h"
 
 namespace starboard {
@@ -24,6 +25,7 @@
 namespace {
 
 SbOnceControl ffmpeg_initialization_once = SB_ONCE_INITIALIZER;
+SbMutex codec_mutex = SB_MUTEX_INITIALIZER;
 
 }  // namespace
 
@@ -32,6 +34,19 @@
   SB_DCHECK(initialized);
 }
 
+int OpenCodec(AVCodecContext* codec_context, const AVCodec* codec) {
+  SbMutexAcquire(&codec_mutex);
+  int result = avcodec_open2(codec_context, codec, NULL);
+  SbMutexRelease(&codec_mutex);
+  return result;
+}
+
+void CloseCodec(AVCodecContext* codec_context) {
+  SbMutexAcquire(&codec_mutex);
+  avcodec_close(codec_context);
+  SbMutexRelease(&codec_mutex);
+}
+
 }  // namespace ffmpeg
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.h b/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.h
index 3bd1ea9..3db515e 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.h
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_common.h
@@ -32,6 +32,14 @@
 
 void InitializeFfmpeg();
 
+// In Ffmpeg, the calls to avcodec_open2() and avcodec_close() are not
+// synchronized internally so it is the responsibility of its user to ensure
+// that these calls don't overlap.  The following functions acquires a lock
+// internally before calling avcodec_open2() and avcodec_close() to enforce
+// this.
+int OpenCodec(AVCodecContext* codec_context, const AVCodec* codec);
+void CloseCodec(AVCodecContext* codec_context);
+
 }  // namespace ffmpeg
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
index 02aaa9e..9ccc5ce 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -265,7 +265,7 @@
     return;
   }
 
-  int rv = avcodec_open2(codec_context_, codec, NULL);
+  int rv = OpenCodec(codec_context_, codec);
   if (rv < 0) {
     SB_LOG(ERROR) << "Unable to open codec";
     TeardownCodec();
@@ -281,7 +281,7 @@
 
 void VideoDecoder::TeardownCodec() {
   if (codec_context_) {
-    avcodec_close(codec_context_);
+    CloseCodec(codec_context_);
     av_free(codec_context_);
     codec_context_ = NULL;
   }
diff --git a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.h b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.h
index 09ad253..9f8f961 100644
--- a/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.h
+++ b/src/starboard/tizen/shared/ffmpeg/ffmpeg_video_decoder.h
@@ -29,7 +29,7 @@
 namespace shared {
 namespace ffmpeg {
 
-class VideoDecoder : public starboard::player::filter::VideoDecoder {
+class VideoDecoder : public starboard::player::filter::HostedVideoDecoder {
  public:
   typedef starboard::player::InputBuffer InputBuffer;
   typedef starboard::player::VideoFrame VideoFrame;
diff --git a/src/starboard/tizen/shared/get_home_directory.cc b/src/starboard/tizen/shared/get_home_directory.cc
index fd5c728..5daf338 100644
--- a/src/starboard/tizen/shared/get_home_directory.cc
+++ b/src/starboard/tizen/shared/get_home_directory.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <app_common.h>
+#include <unistd.h>
 
 #include "starboard/log.h"
 #include "starboard/shared/nouser/user_internal.h"
@@ -24,19 +25,27 @@
 
 bool GetHomeDirectory(SbUser user, char* out_path, int path_size) {
   if (user != SbUserGetCurrent()) {
+    SB_DLOG(ERROR) << "Invalid User";
     return false;
   }
 
-  const char* home_directory = app_get_data_path();
-
-  if (!home_directory) {
-    SB_DLOG(WARNING) << "No HOME environment variable.";
-    return false;
+  const char* data_path = app_get_data_path();
+  if (data_path) {
+    SB_DLOG(INFO) << "App data path [" << data_path << "]";
+    SbStringCopy(out_path, data_path, path_size);
+    return true;
   }
 
-  SB_DLOG(INFO) << "Tizen Home Directory[" << home_directory << "]";
-  SbStringCopy(out_path, home_directory, path_size);
-  return true;
+  // for nplb, unittest, ...
+  const char* curr_dir = getcwd(NULL, 0);
+  if (curr_dir) {
+    SB_DLOG(WARNING) << "No data path. Set CWD " << curr_dir;
+    SbStringCopy(out_path, curr_dir, path_size);
+    return true;
+  }
+
+  SB_DLOG(ERROR) << "No home directory variable";
+  return false;
 }
 
 }  // namespace nouser
diff --git a/src/starboard/tizen/shared/gyp_configuration.gypi b/src/starboard/tizen/shared/gyp_configuration.gypi
new file mode 100644
index 0000000..385c3d9
--- /dev/null
+++ b/src/starboard/tizen/shared/gyp_configuration.gypi
@@ -0,0 +1,113 @@
+# Copyright 2016 Samsung Electronics. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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_os': 'linux',
+    'tizen_os': 1,
+
+    # Some package use tizen system instead of third_party
+    'use_system_icu': 1,
+    'use_system_libxml': 1,
+
+    # scratch surface cache is designed to choose large offscreen surfaces so
+    # that they can be maximally reused, it is not a very good fit for a tiled
+    # renderer.
+    '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,
+
+    'linker_flags': [
+    ],
+    'linker_flags_gold': [
+      '-O3',
+      '-flto',
+    ],
+    'compiler_flags_debug': [
+      '-O0',
+    ],
+    'compiler_flags_devel': [
+      '-O2',
+    ],
+    'compiler_flags_cc_qa': [
+      '-fno-rtti',
+    ],
+    'compiler_flags_qa': [
+      '-O3',
+    ],
+    'compiler_flags_cc_gold': [
+      '-fno-rtti',
+    ],
+    'compiler_flags_gold': [
+      '-O3',
+    ],
+    'conditions': [
+      ['cobalt_fastbuild==0', {
+        'compiler_flags_debug': [
+          '-g',
+        ],
+        'compiler_flags_devel': [
+          '-g',
+        ],
+        'compiler_flags_qa': [
+        ],
+        'compiler_flags_gold': [
+          '-flto',
+        ],
+      }],
+    ],
+  },
+
+  'target_defaults': {
+    'defines': [
+      # Cobalt on Tizen flag
+      'COBALT_TIZEN',
+      'PNG_SKIP_SETJMP_CHECK',
+      '__STDC_FORMAT_MACROS', # so that we get PRI*
+      # Enable GNU extensions to get prototypes like ffsl.
+      '_GNU_SOURCE=1',
+    ],
+    'cflags': [
+      '-pthread',
+      # Force char to be signed.
+      '-fsigned-char',
+      # Do not warn about locally defined but not used.
+      '-Wno-unused-local-typedefs',
+      # Do not warn about XXX is deprecated.
+      '-Wno-deprecated-declarations',
+      # Do not warn about missing initializer for member XXX.
+      '-Wno-missing-field-initializers',
+      # Do not warn about unused functions.
+      '-Wno-unused-function',
+      # Do not warn about type qualifiers ignored on function return type.
+      '-Wno-ignored-qualifiers',
+      # Do not warn about the use of multi-line comments.
+      '-Wno-comment',
+      # Do not warn about sign compares.
+      '-Wno-sign-compare',
+    ],
+    'cflags_c': [
+      '-std=c11',
+    ],
+    'cflags_cc': [
+      '-std=gnu++11',
+    ],
+    'ldflags': [
+      '-pthread',
+    ],
+  }, # end of target_defaults
+}
diff --git a/src/starboard/tizen/shared/memory_flush.cc b/src/starboard/tizen/shared/memory_flush.cc
new file mode 100644
index 0000000..641d74e
--- /dev/null
+++ b/src/starboard/tizen/shared/memory_flush.cc
@@ -0,0 +1,35 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <sys/mman.h>
+#include <iomanip>
+
+#if !SB_CAN(MAP_EXECUTABLE_MEMORY)
+#error \
+    "You shouldn't implement SbMemoryFlush unless you can map " \
+       "memory pages as executable"
+#endif
+
+void SbMemoryFlush(void* virtual_address, int64_t size_bytes) {
+  char* memory = reinterpret_cast<char*>(virtual_address);
+#if !SB_IS(ARCH_ARM)
+  int result = msync(memory, size_bytes, MS_SYNC);
+  SB_DCHECK(result == 0) << "msync failed: 0x" << std::hex << result << " ("
+                         << std::dec << result << "d)";
+#else
+  __clear_cache(memory, memory + size_bytes);
+#endif
+}
diff --git a/src/starboard/tizen/shared/player/filter/ffmpeg_player_components_impl.cc b/src/starboard/tizen/shared/player/filter/ffmpeg_player_components_impl.cc
index d747e00..1081d37 100644
--- a/src/starboard/tizen/shared/player/filter/ffmpeg_player_components_impl.cc
+++ b/src/starboard/tizen/shared/player/filter/ffmpeg_player_components_impl.cc
@@ -50,8 +50,8 @@
       new AudioRendererImpl(audio_parameters.job_queue,
                             scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
                             audio_parameters.audio_header);
-  VideoRendererImpl* video_renderer =
-      new VideoRendererImpl(scoped_ptr<VideoDecoder>(video_decoder).Pass());
+  VideoRendererImpl* video_renderer = new VideoRendererImpl(
+      scoped_ptr<HostedVideoDecoder>(video_decoder).Pass());
 
   return scoped_ptr<PlayerComponents>(
       new PlayerComponents(audio_renderer, video_renderer));
diff --git a/src/starboard/tizen/shared/system_get_path.cc b/src/starboard/tizen/shared/system_get_path.cc
index b20b051..f3f7c6d 100644
--- a/src/starboard/tizen/shared/system_get_path.cc
+++ b/src/starboard/tizen/shared/system_get_path.cc
@@ -153,10 +153,6 @@
                              path_size);
     case kSbSystemPathExecutableFile:
       return GetExecutablePath(out_path, path_size);
-
-    default:
-      SB_NOTIMPLEMENTED();
-      return false;
   }
 
   int length = strlen(path);
diff --git a/src/starboard/tizen/shared/thread_create.cc b/src/starboard/tizen/shared/thread_create.cc
new file mode 100644
index 0000000..30c4c0e
--- /dev/null
+++ b/src/starboard/tizen/shared/thread_create.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/thread.h"
+
+#include <Eina.h>
+
+#include "starboard/log.h"
+#include "starboard/shared/pthread/is_success.h"
+#include "starboard/string.h"
+
+namespace {
+
+struct ThreadParams {
+  SbThreadEntryPoint entry_point;
+  char name[128];
+  void* context;
+};
+
+void* ThreadFunc(void* context, Eina_Thread thread) {
+  ThreadParams* thread_params = static_cast<ThreadParams*>(context);
+  SbThreadEntryPoint entry_point = thread_params->entry_point;
+  void* real_context = thread_params->context;
+
+  if (thread_params->name[0] != '\0') {
+    SbThreadSetName(thread_params->name);
+  }
+
+  delete thread_params;
+
+  return entry_point(real_context);
+}
+
+Eina_Thread_Priority StarboardThreadPriorityToEina(SbThreadPriority priority) {
+  switch (priority) {
+    case kSbThreadPriorityLowest:
+    case kSbThreadPriorityLow:
+      return EINA_THREAD_IDLE;
+    case kSbThreadNoPriority:
+    case kSbThreadPriorityNormal:
+      return EINA_THREAD_BACKGROUND;
+    case kSbThreadPriorityHigh:
+    case kSbThreadPriorityHighest:
+    case kSbThreadPriorityRealTime:
+      return EINA_THREAD_URGENT;
+    default:
+      SB_NOTREACHED();
+      break;
+  }
+}
+
+}  // namespace
+
+SbThread SbThreadCreate(int64_t stack_size,
+                        SbThreadPriority priority,
+                        SbThreadAffinity /*affinity*/,
+                        bool joinable,
+                        const char* name,
+                        SbThreadEntryPoint entry_point,
+                        void* context) {
+  if (stack_size < 0 || !entry_point) {
+    return kSbThreadInvalid;
+  }
+
+  ThreadParams* params = new ThreadParams();
+  params->entry_point = entry_point;
+  params->context = context;
+  if (name) {
+    SbStringCopy(params->name, name, sizeof(params->name));
+  } else {
+    params->name[0] = '\0';
+  }
+
+  Eina_Thread thread = kSbThreadInvalid;
+  if (eina_thread_create(&thread, StarboardThreadPriorityToEina(priority), -1,
+                         &ThreadFunc, params)) {
+    // Fix nplb SbThreadCreateTest.SunnyDayDetached
+    // We can not pass |joinable| info when call eina_thread_create
+    // If |joinable| is false, we need to detach thread immediately
+    if (!joinable)
+      SbThreadDetach(thread);
+    return thread;
+  }
+
+  return kSbThreadInvalid;
+}
diff --git a/src/starboard/tools/extract_starboard_versions.py b/src/starboard/tools/extract_starboard_versions.py
new file mode 100644
index 0000000..ed03af5
--- /dev/null
+++ b/src/starboard/tools/extract_starboard_versions.py
@@ -0,0 +1,174 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Extracts the API versions for all available builds.
+
+  Example usage:
+    cd cobalt/src/
+    extract_starboard_versions.py
+"""
+
+from __future__ import print_function
+
+import fnmatch
+import os
+import re
+import sys
+import platform
+import paths
+
+# Sometimes files have weird encodings. This function will use a variety of
+# hand selected encoders that work on the starboard codebase.
+def AutoDecodeString(file_data):
+  for encoding in {'UTF-8', 'utf_16', 'windows-1253', 'iso-8859-7', 'macgreek'}:
+    try:
+      return file_data.decode(encoding)
+    except:
+      continue
+  raise IOError('Could not read file')
+
+# Given a search_term, this will open the file_path and return the first
+# line that contains the search term. This will ignore C-style comments.
+def SearchInFileReturnFirstMatchingLine(file_path, search_term):
+  try:
+    lines = OpenFileAndDecodeLinesAndRemoveComments(file_path)
+    for line in lines:
+      if search_term in line:
+        return line
+    return None
+  except IOError:
+    print ('  error while reading file ', file_path)
+
+# Opens a header or cc file and decodes it to utf8 using a variety
+# of decoders. All lines will have their comments stripped out.
+# This will return the lines of the given file.
+def OpenFileAndDecodeLinesAndRemoveComments(file_path):
+  with open(file_path, 'rb+') as fd:
+    lines = AutoDecodeString(fd.read()).splitlines()
+    # remove c-style comments.
+    lines = [ re.sub('//.*', '', line) for line in lines]
+    return lines
+
+
+# Given a file_path, return all include files that it contains.
+def FindIncludeFiles(file_path):
+  try:
+    output_list = []
+    lines = OpenFileAndDecodeLinesAndRemoveComments(file_path)
+    for line in lines:
+      # Remove c-style comments.
+      if '#include' in line:
+        line = re.sub('#include ', '', line).replace('"', '')
+        output_list.append(line)
+    return output_list
+  except IOError:
+    print ('  error while reading file ', file_path)
+
+# Searches from the search_location for a configuration.h file that
+# contains the definition of the SB_EXPERIMENTAL_API_VERSION and then
+# returns that as type int.
+def ExtractExperimentalApiVersion(config_file_path):
+  needle = '#define SB_EXPERIMENTAL_API_VERSION'
+  line = SearchInFileReturnFirstMatchingLine(
+      config_file_path,
+      '#define SB_EXPERIMENTAL_API_VERSION')
+  if not line:
+    raise ValueError('Could not find ' + needle + ' in ' + config_file_path)
+
+  elements = line.split(' ')
+  exp_api_version = int(elements[2])
+  return exp_api_version
+
+# Given platform path, this function will try and find the version. Returns
+# either the version if found, or None.
+# If the version string is returned, note that it could be
+# 'SB_EXPERIMENTAL_VERSION' or a number string.
+def FindVersion(platform_path):
+  api_version_str = '#define SB_API_VERSION'
+  result = SearchInFileReturnFirstMatchingLine(platform_path, api_version_str)
+  if not result:
+    return None
+  version_str = result.replace(api_version_str, '')
+  return version_str.strip()
+
+# Given the path to the platform_include_file, this will find the include
+# files with "configuration_public.h" in the name and return those.
+def FindConfigIncludefile(platform_path_config_file):
+  include_files = FindIncludeFiles(platform_path_config_file)
+  include_files = filter(lambda x: 'configuration_public.h' in x, include_files)
+  return include_files
+
+# Given the input starboard directory, this will return a map of
+# 'platform_name' -> 'full_path_to_platform'
+def GeneratePlatformPathMap(starboard_dir):
+  ports = [p for p in platform.PlatformInfo.EnumeratePorts(starboard_dir)]
+  def GenPath(p):
+    full_path = os.path.abspath(os.path.join(p.path, 'configuration_public.h'))
+    if not os.path.exists(full_path):
+      raise IOError("Could not find path " + full_path)
+    return full_path
+
+  port_dict = {}
+  for p in ports:
+    port_dict[p.port_name] = GenPath(p)
+  return port_dict
+
+# Given the root starboard directory, and the full path to the platform,
+# this function will search for the API_VERSION of the platform. It will
+# first see if the version is defined within the include file, if it is
+# not then the include paths for shared platform configurations are
+# searched in the recursive step.
+def FindVersionRecursive(starboard_dir, platform_path):
+  version_str = FindVersion(platform_path)
+  if version_str:
+    return version_str
+  else:
+    config_include_paths = FindConfigIncludefile(platform_path)
+    if len(config_include_paths) == 0:
+      return "<UNKNOWN>"
+    elif len(config_include_paths) > 1:
+      return "<AMBIGUIOUS>"
+    else:
+      include_path = config_include_paths[0]
+      include_path = re.sub(r'^starboard/', '', include_path)
+      full_include_path = os.path.join(starboard_dir, include_path)
+      return FindVersionRecursive(starboard_dir, full_include_path)
+
+def Main():
+  print('\n***** Listing the api versions of all first party ports. *****\n')
+
+  port_dict = GeneratePlatformPathMap(paths.STARBOARD_ROOT)
+
+  experimental_api_version = ExtractExperimentalApiVersion(
+      os.path.join(paths.STARBOARD_ROOT, 'configuration.h'))
+
+  path_map = {}
+
+  print('Experimental API Version: ' + str(experimental_api_version) + '\n')
+
+  for platform_name, platform_path in port_dict.iteritems():
+    version_str = FindVersionRecursive(paths.STARBOARD_ROOT, platform_path)
+    if 'SB_EXPERIMENTAL_API_VERSION' in version_str:
+      version_str = str(experimental_api_version)
+    path_map[platform_name] = version_str
+
+  for platform_name, api_version in sorted(path_map.iteritems()):
+    print(platform_name + ': ' + api_version)
+
+  sys.exit(0)
+
+if __name__ == '__main__':
+  # All functionality stored in Main() to avoid py-lint from warning about
+  # about shadowing global variables in local functions.
+  Main()
diff --git a/src/starboard/tools/paths.py b/src/starboard/tools/paths.py
index 78179fd..db8c372 100644
--- a/src/starboard/tools/paths.py
+++ b/src/starboard/tools/paths.py
@@ -15,8 +15,18 @@
 #
 """Constants for commonly referenced paths."""
 
+from __future__ import print_function
+
 import os
 
-import starboard
+STARBOARD_ROOT = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), os.path.pardir))
 
-STARBOARD_ROOT = os.path.abspath(os.path.dirname(starboard.__file__))
+if __name__ == '__main__':
+  # All functionality stored in TestRunPaths() to avoid py-lint from warning'
+  # about shadowing global variables in local functions.
+  def TestRunPaths():
+    print('STARBOARD_ROOT: ' + STARBOARD_ROOT)
+    assert(os.path.isdir(STARBOARD_ROOT))
+
+  TestRunPaths()
diff --git a/src/starboard/tools/raspi/run_test.py b/src/starboard/tools/raspi/run_test.py
index b5420bc..58c925c 100755
--- a/src/starboard/tools/raspi/run_test.py
+++ b/src/starboard/tools/raspi/run_test.py
@@ -35,10 +35,22 @@
 _RASPI_PASSWORD = 'raspberry'
 
 # Timeouts are in seconds
-_PEXPECT_DEFAULT_TIMEOUT = 300
+_PEXPECT_DEFAULT_TIMEOUT = 600
 _PEXPECT_EXPECT_TIMEOUT = 60
 
 
+def _CleanupProcess(process):
+  """Closes current pexpect process.
+
+  Args:
+    process: Current pexpect process.
+  """
+  if process is not None and process.isalive():
+    # Send ctrl-c to the raspi.
+    process.sendline(chr(3))
+    process.close()
+
+
 # pylint: disable=unused-argument
 def _SigIntOrSigTermHandler(process, signum, frame):
   """Clean up and exit with status |signum|.
@@ -50,14 +62,11 @@
     frame: Current stack frame.  Passed in when the signal handler is called by
       python runtime.
   """
-  if process.isalive():
-    # Send ctrl-c to the raspi.
-    process.sendline(chr(3))
-    process.close()
+  _CleanupProcess(process)
   sys.exit(signum)
 
 
-def RunTest(test_path, raspi_ip, flags):
+def _RunTest(test_path, raspi_ip, flags):
   """Run a test on a Raspi machine and print relevant stdout.
 
   Args:
@@ -67,76 +76,97 @@
 
   Returns:
     Exit status of running the test.
-
-  Raises:
-    ValueError: Raised if test_path is invalid.
   """
-  if not os.path.isfile(test_path):
-    raise ValueError('test_path ({}) must be a file.'.format(test_path))
+  return_value = 1
 
-  # This is used to strip ansi color codes from output.
-  sanitize_line_re = re.compile(r'\x1b[^m]*m')
+  try:
+    process = None
 
-  sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
-  sys.stdout.flush()
+    if not os.path.isfile(test_path):
+      raise ValueError('test_path ({}) must be a file.'.format(test_path))
 
-  test_dir_path, test_file = os.path.split(test_path)
-  test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
+    # This is used to strip ansi color codes from output.
+    sanitize_line_re = re.compile(r'\x1b[^m]*m')
 
-  raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
-  raspi_test_path = os.path.join(test_base_dir, test_file)
+    sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
+    sys.stdout.flush()
 
-  # rsync the test files to the raspi
-  options = '-avzh --exclude obj*'
-  source = test_dir_path
-  destination = raspi_user_hostname + ':~/'
-  rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
-  rsync_process = pexpect.spawn(rsync_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+    test_dir_path, test_file = os.path.split(test_path)
+    test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
 
-  signal.signal(signal.SIGINT,
-                functools.partial(_SigIntOrSigTermHandler, rsync_process))
-  signal.signal(signal.SIGTERM,
-                functools.partial(_SigIntOrSigTermHandler, rsync_process))
+    raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
+    raspi_test_path = os.path.join(test_base_dir, test_file)
 
-  rsync_process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
-  rsync_process.sendline(_RASPI_PASSWORD)
+    # rsync the test files to the raspi
+    options = '-avzh --exclude obj*'
+    source = test_dir_path
+    destination = raspi_user_hostname + ':~/'
+    rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
+    process = pexpect.spawn(rsync_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
 
-  while True:
-    line = sanitize_line_re.sub('', rsync_process.readline())
-    if line:
+    signal.signal(signal.SIGINT,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+    signal.signal(signal.SIGTERM,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+
+    process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+    process.sendline(_RASPI_PASSWORD)
+
+    while True:
+      line = sanitize_line_re.sub('', process.readline())
+      if line:
+        sys.stdout.write(line)
+        sys.stdout.flush()
+      else:
+        break
+
+    # ssh into the raspi and run the test
+    ssh_command = 'ssh ' + raspi_user_hostname
+    process = pexpect.spawn(ssh_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+
+    signal.signal(signal.SIGINT,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+    signal.signal(signal.SIGTERM,
+                  functools.partial(_SigIntOrSigTermHandler, process))
+
+    process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+    process.sendline(_RASPI_PASSWORD)
+
+    test_command = raspi_test_path + ' ' + flags
+    test_time_tag = 'TEST-{time}'.format(time=time.time())
+    test_success_tag = 'succeeded'
+    test_failure_tag = 'failed'
+    test_success_output = ' && echo ' + test_time_tag + ' ' + test_success_tag
+    test_failure_output = ' || echo ' + test_time_tag + ' ' + test_failure_tag
+    process.sendline(test_command + test_success_output + test_failure_output)
+
+    while True:
+      line = sanitize_line_re.sub('', process.readline())
+      if not line:
+        break
       sys.stdout.write(line)
       sys.stdout.flush()
-    else:
-      break
+      if line.startswith(test_time_tag):
+        if line.find(test_success_tag) != -1:
+          return_value = 0
+        break
 
-  # ssh into the raspi and run the test
-  ssh_command = 'ssh ' + raspi_user_hostname
-  ssh_process = pexpect.spawn(ssh_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+  except ValueError:
+    logging.exception('Test path invalid.')
+  except pexpect.EOF:
+    logging.exception('pexpect encountered EOF while reading line.')
+  except pexpect.TIMEOUT:
+    logging.exception('pexpect timed out while reading line.')
+  # pylint: disable=W0703
+  except Exception:
+    logging.exception('Error occured while running test.')
+  finally:
+    _CleanupProcess(process)
 
-  signal.signal(signal.SIGINT,
-                functools.partial(_SigIntOrSigTermHandler, ssh_process))
-  signal.signal(signal.SIGTERM,
-                functools.partial(_SigIntOrSigTermHandler, ssh_process))
-
-  ssh_process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
-  ssh_process.sendline(_RASPI_PASSWORD)
-
-  test_command = raspi_test_path + ' ' + flags
-  test_end_tag = 'END_TEST-{time}'.format(time=time.time())
-  ssh_process.sendline(test_command + '; echo ' + test_end_tag)
-
-  while True:
-    line = sanitize_line_re.sub('', ssh_process.readline())
-    if line and not line.startswith(test_end_tag):
-      sys.stdout.write(line)
-      sys.stdout.flush()
-    else:
-      break
-
-  return 0
+  return return_value
 
 
-def AddTargetFlag(parser):
+def _AddTargetFlag(parser):
   """Add target to argument parser."""
   parser.add_argument(
       '-t',
@@ -150,7 +180,7 @@
       nargs='?')
 
 
-def AddFlagsFlag(parser, default=None):
+def _AddFlagsFlag(parser, default=None):
   """Add flags to argument parser.
 
   Args:
@@ -185,21 +215,13 @@
       formatter_class=argparse.ArgumentDefaultsHelpFormatter,
       description=textwrap.dedent(__doc__))
 
-  AddFlagsFlag(parser)
-  AddTargetFlag(parser)
+  _AddFlagsFlag(parser)
+  _AddTargetFlag(parser)
   parser.add_argument('test_path', help='Path of test to be run.', type=str)
 
   args = parser.parse_args()
 
-  try:
-    return_value = RunTest(
-        args.test_path, raspi_ip=args.target, flags=args.flags)
-  # pylint: disable=W0703
-  except Exception:
-    logging.exception('Error occured while running binary.')
-    return_value = 1
-
-  return return_value
+  return _RunTest(args.test_path, raspi_ip=args.target, flags=args.flags)
 
 
 if __name__ == '__main__':
diff --git a/src/starboard/tools/toolchain.py b/src/starboard/tools/toolchain.py
new file mode 100644
index 0000000..5400a1d
--- /dev/null
+++ b/src/starboard/tools/toolchain.py
@@ -0,0 +1,166 @@
+# Copyright (c) 2017 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Starboard's abstract Toolchain which is implemented for each platform."""
+
+import abc
+import imp
+import logging
+import os
+
+import starboard
+from starboard.tools import paths
+from starboard.tools import platform
+
+
+class ABCMetaSingleton(abc.ABCMeta):
+  instances = {}
+
+  def __call__(self, *args, **kwargs):
+    if self not in self.instances:
+      self.instances[self] = super(ABCMetaSingleton, self).__call__(
+          *args, **kwargs)
+    return self.instances[self]
+
+
+class Toolchain(object):
+  """This is an abstract interface of the Toolchain."""
+  """ TODO: move variables / commands
+        AR / _HOST
+        ARFLAGS / _HOST
+        ARTHINFLAGS / _HOST
+        CC / CC_HOST
+        CXX / CXX_HOST
+        LD / LD_HOST
+        RC / RC_HOST
+     And / or implement NinjaWriter.WriteSources & GenerateOutputForConfig body
+     here.
+"""
+  __metaclass__ = ABCMetaSingleton
+
+  @abc.abstractmethod
+  def Define(self, d):
+    pass
+
+  @abc.abstractmethod
+  def EncodeRspFileList(self, args):
+    pass
+
+  @abc.abstractmethod
+  def ExpandEnvVars(self, path, env):
+    pass
+
+  @abc.abstractmethod
+  def ExpandRuleVariables(self, path, root, dirname, source, ext, name):
+    pass
+
+  @abc.abstractmethod
+  def GenerateEnvironmentFiles(self, toplevel_build_dir, generator_flags,
+                               open_out):
+    pass
+
+  @abc.abstractmethod
+  def GetCompilerSettings(self):
+    pass
+
+  @abc.abstractmethod
+  def GetPrecompiledHeader(self, **kwargs):
+    pass
+
+  @abc.abstractmethod
+  def InitCompilerSettings(self, spec, **kwargs):
+    pass
+
+  @abc.abstractmethod
+  def SetAdditionalGypVariables(self, default_variables, **kwargs):
+    pass
+
+  @abc.abstractmethod
+  def VerifyMissingSources(self, sources, **kwargs):
+    pass
+
+  @abc.abstractmethod
+  def QuoteForRspFile(self, arg):
+    pass
+
+
+class PrecompiledHeader:
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def __init__(self):
+    pass
+
+  @abc.abstractmethod
+  def GetFlagsModifications(self, input_flags, output, implicit, command,
+                            cflags_c, cflags_cc, expand_special):
+    pass
+
+  @abc.abstractmethod
+  def GetPchBuildCommands(self):
+    pass
+
+  @abc.abstractmethod
+  def GetInclude(self):
+    pass
+
+  @abc.abstractmethod
+  def GetObjDependencies(self, sources, output):
+    pass
+
+
+class CompilerSettings:
+  """Abstract Compiler Settings class."""
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def __init__(self):
+    pass
+
+  @abc.abstractmethod
+  def GetArch(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetCflags(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetCflagsC(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetCflagsCC(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetCflagsObjectiveC(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetCflagsObjectiveCC(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetDefines(self, config):
+    pass
+
+  @abc.abstractmethod
+  def GetLdFlags(self, config, **kwargs):
+    pass
+
+  @abc.abstractmethod
+  def GetLibFlags(self, config, gyp_path_to_ninja):
+    pass
+
+  @abc.abstractmethod
+  def GetRcFlags(self, config, gyp_path_to_ninja):
+    pass
+
+  @abc.abstractmethod
+  def ProcessIncludeDirs(self, include_dirs, config_name):
+    pass
+
+  @abc.abstractmethod
+  def ProcessLibraries(self, libraries, config_name):
+    pass
\ No newline at end of file
diff --git a/src/starboard/win/console/starboard_platform.gyp b/src/starboard/win/console/starboard_platform.gyp
index 653db66..134c3b2 100644
--- a/src/starboard/win/console/starboard_platform.gyp
+++ b/src/starboard/win/console/starboard_platform.gyp
@@ -14,17 +14,24 @@
 {
   'includes': [ '../shared/starboard_platform.gypi' ],
   'variables': {
-    'starboard_platform_dependent_sources': [
+    'starboard_platform_dependent_files': [
       'atomic_public.h',
       'configuration_public.h',
       'main.cc',
       'thread_types_public.h',
       '../shared/system_get_path.cc',
+      '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+      '<(DEPTH)/starboard/shared/starboard/queue_application.h',
+      '<(DEPTH)/starboard/shared/stub/system_request_pause.cc',
+      '<(DEPTH)/starboard/shared/stub/system_request_stop.cc',
+      '<(DEPTH)/starboard/shared/stub/system_request_suspend.cc',
+      '<(DEPTH)/starboard/shared/stub/system_request_unpause.cc',
       '<(DEPTH)/starboard/shared/stub/window_create.cc',
       '<(DEPTH)/starboard/shared/stub/window_destroy.cc',
       '<(DEPTH)/starboard/shared/stub/window_get_platform_handle.cc',
       '<(DEPTH)/starboard/shared/stub/window_get_size.cc',
       '<(DEPTH)/starboard/shared/stub/window_set_default_options.cc',
+      '<(DEPTH)/starboard/shared/uwp/system_get_property.cc',
       '<(DEPTH)/starboard/stub/application_stub.cc',
       '<(DEPTH)/starboard/stub/application_stub.h',
     ],
diff --git a/src/starboard/win/console/starboard_platform_tests.gyp b/src/starboard/win/console/starboard_platform_tests.gyp
index 93f812b..1eb83ba 100644
--- a/src/starboard/win/console/starboard_platform_tests.gyp
+++ b/src/starboard/win/console/starboard_platform_tests.gyp
@@ -12,6 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
   'targets': [
     {
       'target_name': 'starboard_platform_tests',
diff --git a/src/starboard/win/lib/starboard_platform.gyp b/src/starboard/win/lib/starboard_platform.gyp
index f6dfb74..d61cc40 100644
--- a/src/starboard/win/lib/starboard_platform.gyp
+++ b/src/starboard/win/lib/starboard_platform.gyp
@@ -12,21 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
-  'includes': [ '../shared/starboard_platform.gypi' ],
-  'sources': [
-    'atomic_public.h',
-    'configuration_public.h',
-    'thread_types_public.h',
+  'includes': [
+    '../shared/starboard_platform.gypi',
+    '../../shared/uwp/starboard_platform.gypi'
   ],
   'variables': {
-    'starboard_platform_dependent_sources': [
+    'starboard_platform_dependent_files': [
       'atomic_public.h',
       'configuration_public.h',
       'thread_types_public.h',
-      'main.cc',
       '../shared/system_get_path.cc',
-      '<(DEPTH)/starboard/shared/uwp/application_uwp.cc',
-      '<(DEPTH)/starboard/shared/uwp/application_uwp.h',
     ],
-  },
+  }
 }
diff --git a/src/starboard/win/lib/starboard_platform_tests.gyp b/src/starboard/win/lib/starboard_platform_tests.gyp
index 93f812b..1eb83ba 100644
--- a/src/starboard/win/lib/starboard_platform_tests.gyp
+++ b/src/starboard/win/lib/starboard_platform_tests.gyp
@@ -12,6 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
   'targets': [
     {
       'target_name': 'starboard_platform_tests',
diff --git a/src/starboard/win/shared/configuration_public.h b/src/starboard/win/shared/configuration_public.h
index 47521b3..1d66e6c 100644
--- a/src/starboard/win/shared/configuration_public.h
+++ b/src/starboard/win/shared/configuration_public.h
@@ -245,7 +245,7 @@
 // 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 ':'
+#define SB_PATH_SEP_CHAR ';'
 
 // The string form of SB_FILE_SEP_CHAR.
 #define SB_FILE_SEP_STRING "\\"
@@ -254,7 +254,7 @@
 #define SB_FILE_ALT_SEP_STRING "/"
 
 // The string form of SB_PATH_SEP_CHAR.
-#define SB_PATH_SEP_STRING ":"
+#define SB_PATH_SEP_STRING ";"
 
 // On some platforms the file system stores access times at a coarser
 // granularity than other times. When this quirk is defined, we assume the
diff --git a/src/starboard/win/shared/gyp_configuration.gypi b/src/starboard/win/shared/gyp_configuration.gypi
index 62a2713..8a2ebfd 100644
--- a/src/starboard/win/shared/gyp_configuration.gypi
+++ b/src/starboard/win/shared/gyp_configuration.gypi
@@ -24,35 +24,6 @@
 
     'cobalt_media_source_2016': 1,
 
-    # 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__',
-    ],
-    'linker_flags': [
-    ],
-    'compiler_flags_debug': [
-      '-frtti',
-      '-O0',
-    ],
-    'compiler_flags_devel': [
-      '-frtti',
-      '-O2',
-    ],
-    'compiler_flags_qa': [
-      '-fno-rtti',
-      '-O2',
-      '-gline-tables-only',
-    ],
-    'compiler_flags_gold': [
-      '-fno-rtti',
-      '-O2',
-      '-gline-tables-only',
-    ],
     'conditions': [
       ['cobalt_fastbuild==0', {
         'msvs_settings': {
@@ -115,6 +86,15 @@
         'msvs_target_platform': 'x64',
         # Add the default import libs.
         'conditions': [
+          ['sb_pedantic_warnings==1', {
+            'msvs_settings': {
+              'VCCLCompilerTool': {
+                # Enable some warnings, even those that are disabled by default.
+                # See https://msdn.microsoft.com/en-us/library/23k5d385.aspx
+                'WarningLevel': '4',
+              },
+            },
+          }],
           ['cobalt_fastbuild==0', {
             'msvs_settings': {
               'VCCLCompilerTool': {
@@ -198,6 +178,11 @@
        },
     },
     'defines': [
+      # Disable warnings.  These options were inherited from Chromium.
+      '_CRT_SECURE_NO_DEPRECATE',
+      '_CRT_NONSTDC_NO_WARNINGS',
+      '_CRT_NONSTDC_NO_DEPRECATE',
+      '_SCL_SECURE_NO_DEPRECATE',
       # Disable suggestions to switch to Microsoft-specific secure CRT.
       '_CRT_SECURE_NO_WARNINGS',
       # Disable support for exceptions in STL in order to detect their use
@@ -217,30 +202,37 @@
     'msvs_settings': {
       'VCCLCompilerTool': {
         'ForcedIncludeFiles': [],
+
         # Check for buffer overruns.
         'BufferSecurityCheck': 'true',
+
         'Conformance': [
           # "for" loop's initializer go out of scope after the for loop.
           'forScope',
           # wchar_t is treated as a built-in type.
           'wchar_t',
         ],
+
         # Check for 64-bit portability issues.
         'Detect64BitPortabilityProblems': 'true',
+
         # Disable Microsoft-specific header dependency tracking.
         # Incremental linker does not support the Windows metadata included
         # in .obj files compiled with C++/CX support (/ZW).
         'MinimalRebuild': 'false',
+
         # Treat warnings as errors.
         'WarnAsError': 'false',
+
         # Enable some warnings, even those that are disabled by default.
         # See https://msdn.microsoft.com/en-us/library/23k5d385.aspx
-        'WarningLevel': '3',
+        'WarningLevel': '2',
 
         'AdditionalOptions': [
-          '/errorReport:none', # don't send error reports to MS.
+          '/errorReport:none', # Don't send error reports to MS.
           '/permissive-', # Visual C++ conformance mode.
           '/FS', # Force sync PDB updates for parallel compile.
+          '/w14389', # Turn on warnings for signed/unsigned mismatch.
         ],
       },
       'VCLinkerTool': {
@@ -257,7 +249,7 @@
         'TargetMachine': '17', # x86 - 64
         'AdditionalOptions': [
           '/WINMD:NO', # Do not generate a WinMD file.
-          '/errorReport:none', # don't send error reports to MS.
+          '/errorReport:none', # Don't send error reports to MS.
         ],
       },
       'VCLibrarianTool': {
@@ -338,15 +330,5 @@
       # https://connect.microsoft.com/VisualStudio/feedback/details/783808/static-analyzer-warning-c28285-for-std-min-and-std-max
       28285,
     ],
-    'target_conditions': [
-      ['cobalt_code==1', {
-        'cflags': [
-          '-Wall',
-          '-Wextra',
-          '-Wunreachable-code',
-        ],
-      },
-      ],
-    ],
   }, # end of target_defaults
 }
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi
index 6d28bf6..2b520d0 100644
--- a/src/starboard/win/shared/starboard_platform.gypi
+++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -12,12 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 {
+  'variables': {
+    'sb_pedantic_warnings': 1,
+  },
   'targets': [
     {
       'target_name': 'starboard_platform',
       'type': 'static_library',
       'msvs_settings': {
         'VCCLCompilerTool': {
+          'AdditionalIncludeDirectories': [
+            '<(DEPTH)/third_party/angle/include',
+            '<(DEPTH)/third_party/angle/include/EGL',
+            '<(DEPTH)/third_party/angle/include/GLES2',
+            '<(DEPTH)/third_party/angle/include/KHR',
+          ],
           'AdditionalOptions': [
             '/ZW',           # Windows Runtime
             '/ZW:nostdlib',  # Windows Runtime, no default #using
@@ -58,11 +67,30 @@
         '<(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/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/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/application.cc',
         '<(DEPTH)/starboard/shared/starboard/command_line.cc',
         '<(DEPTH)/starboard/shared/starboard/command_line.h',
         '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
         '<(DEPTH)/starboard/shared/starboard/event_schedule.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/file_mode_string_to_flags.cc',
         '<(DEPTH)/starboard/shared/starboard/log_message.cc',
         '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
@@ -72,16 +100,8 @@
         '<(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/queue_application.cc',
         '<(DEPTH)/starboard/shared/stub/accessibility_get_display_settings.cc',
         '<(DEPTH)/starboard/shared/stub/accessibility_get_text_to_speech_settings.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_create.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_destroy.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_get_max_channels.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_get_nearest_supported_sample_frequency.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_is_audio_frame_storage_type_supported.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_is_audio_sample_type_supported.cc',
-        '<(DEPTH)/starboard/shared/stub/audio_sink_is_valid.cc',
         '<(DEPTH)/starboard/shared/stub/cryptography_create_transformer.cc',
         '<(DEPTH)/starboard/shared/stub/cryptography_destroy_transformer.cc',
         '<(DEPTH)/starboard/shared/stub/cryptography_get_tag.cc',
@@ -125,42 +145,25 @@
         '<(DEPTH)/starboard/shared/stub/player_set_volume.cc',
         '<(DEPTH)/starboard/shared/stub/player_write_end_of_stream.cc',
         '<(DEPTH)/starboard/shared/stub/player_write_sample.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_close_record.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_delete_record.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_get_record_size.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_open_record.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_read_record.cc',
-        '<(DEPTH)/starboard/shared/stub/storage_write_record.cc',
-        '<(DEPTH)/starboard/shared/stub/system_binary_search.cc',
-        '<(DEPTH)/starboard/shared/stub/system_clear_last_error.cc',
         '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_connection_type.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_device_type.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_error_string.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_last_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_locale_id.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_number_of_processors.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_property.cc',
         '<(DEPTH)/starboard/shared/stub/system_get_stack.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_total_cpu_memory.cc',
         '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
-        '<(DEPTH)/starboard/shared/stub/system_get_used_cpu_memory.cc',
         '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
         '<(DEPTH)/starboard/shared/stub/system_has_capability.cc',
         '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc',
         '<(DEPTH)/starboard/shared/stub/system_is_debugger_attached.cc',
         '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc',
-        '<(DEPTH)/starboard/shared/stub/system_request_pause.cc',
-        '<(DEPTH)/starboard/shared/stub/system_request_stop.cc',
-        '<(DEPTH)/starboard/shared/stub/system_request_suspend.cc',
-        '<(DEPTH)/starboard/shared/stub/system_request_unpause.cc',
-        '<(DEPTH)/starboard/shared/stub/system_sort.cc',
         '<(DEPTH)/starboard/shared/stub/system_symbolize.cc',
         '<(DEPTH)/starboard/shared/stub/time_zone_get_dst_name.cc',
-        '<(DEPTH)/starboard/shared/stub/user_get_current.cc',
-        '<(DEPTH)/starboard/shared/stub/user_get_property.cc',
-        '<(DEPTH)/starboard/shared/stub/user_get_signed_in.cc',
+        '<(DEPTH)/starboard/shared/win32/audio_sink.cc',
+        '<(DEPTH)/starboard/shared/win32/audio_sink.h',
+        '<(DEPTH)/starboard/shared/win32/adapter_utils.cc',
+        '<(DEPTH)/starboard/shared/win32/adapter_utils.h',
         '<(DEPTH)/starboard/shared/win32/atomic_public.h',
+        '<(DEPTH)/starboard/shared/win32/audio_sink_get_max_channels.cc',
+        '<(DEPTH)/starboard/shared/win32/audio_sink_get_nearest_supported_sample_frequency.cc',
+        '<(DEPTH)/starboard/shared/win32/audio_sink_is_audio_frame_storage_type_supported.cc',
+        '<(DEPTH)/starboard/shared/win32/audio_sink_is_audio_sample_type_supported.cc',
         '<(DEPTH)/starboard/shared/win32/auto_event_handle.h',
         '<(DEPTH)/starboard/shared/win32/byte_swap.cc',
         '<(DEPTH)/starboard/shared/win32/condition_variable_broadcast.cc',
@@ -191,6 +194,7 @@
         '<(DEPTH)/starboard/shared/win32/file_seek.cc',
         '<(DEPTH)/starboard/shared/win32/file_truncate.cc',
         '<(DEPTH)/starboard/shared/win32/file_write.cc',
+        '<(DEPTH)/starboard/shared/win32/get_home_directory.cc',
         '<(DEPTH)/starboard/shared/win32/log.cc',
         '<(DEPTH)/starboard/shared/win32/log_flush.cc',
         '<(DEPTH)/starboard/shared/win32/log_format.cc',
@@ -233,6 +237,12 @@
         '<(DEPTH)/starboard/shared/win32/socket_receive_from.cc',
         '<(DEPTH)/starboard/shared/win32/socket_resolve.cc',
         '<(DEPTH)/starboard/shared/win32/socket_send_to.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_connection_type.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_device_type.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_error_string.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_number_of_processors.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_total_cpu_memory.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_used_cpu_memory.cc',
         '<(DEPTH)/starboard/shared/win32/socket_set_broadcast.cc',
         '<(DEPTH)/starboard/shared/win32/socket_set_receive_buffer_size.cc',
         '<(DEPTH)/starboard/shared/win32/socket_set_reuse_address.cc',
@@ -254,8 +264,11 @@
         '<(DEPTH)/starboard/shared/win32/string_format.cc',
         '<(DEPTH)/starboard/shared/win32/string_format_wide.cc',
         '<(DEPTH)/starboard/shared/win32/system_break_into_debugger.cc',
+        '<(DEPTH)/starboard/shared/win32/system_clear_last_error.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_locale_id.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_random_data.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_random_uint64.cc',
+        '<(DEPTH)/starboard/shared/win32/system_get_last_error.cc',
         '<(DEPTH)/starboard/shared/win32/thread_create.cc',
         '<(DEPTH)/starboard/shared/win32/thread_create_local_key.cc',
         '<(DEPTH)/starboard/shared/win32/thread_detach.cc',
@@ -283,7 +296,7 @@
         'configuration_public.h',
         # Include private stubs, if present.
         '<!@(python "<(DEPTH)/starboard/tools/find_private_files.py" "<(DEPTH)" "shared/stub/*.cc")',
-        '<@(starboard_platform_dependent_sources)',
+        '<@(starboard_platform_dependent_files)',
       ],
       'defines': [
         # This must be defined when building Starboard, and must not when
diff --git a/src/starboard/win/shared/system_get_path.cc b/src/starboard/win/shared/system_get_path.cc
index 21350df2..9165e0a 100644
--- a/src/starboard/win/shared/system_get_path.cc
+++ b/src/starboard/win/shared/system_get_path.cc
@@ -35,7 +35,7 @@
 // status. The result being greater than |path_size| - 1 characters is a
 // failure. |out_path| may be written to in unsuccessful cases.
 bool GetExecutablePath(char* out_path, int path_size) {
-  if (!out_path || !path_size) {
+  if (!out_path || (path_size <= 0)) {
     return false;
   }
 
@@ -59,7 +59,7 @@
 // |path_size| - 1 characters is a failure. |out_path| may be written to in
 // unsuccessful cases.
 bool GetExecutableDirectory(char* out_path, int path_size) {
-  if (!out_path || !path_size) {
+  if (!out_path || (path_size <= 0)) {
     return false;
   }
 
@@ -83,7 +83,7 @@
 // status. The result being greater than |path_size| - 1 characters is a
 // failure. |out_path| may be written to in unsuccessful cases.
 bool GetContentPath(char* out_path, int path_size) {
-  if (!out_path || !path_size) {
+  if (!out_path || (path_size <= 0)) {
     return false;
   }
   char file_path[SB_FILE_MAX_PATH];
@@ -99,13 +99,14 @@
 }
 
 bool CreateAndGetTempPath(char* out_path, int path_size) {
-  if (!out_path || !path_size) {
+  if (!out_path || (path_size <= 0)) {
     return false;
   }
   wchar_t w_file_path[SB_FILE_MAX_PATH];
   w_file_path[0] = L'\0';
 
-  DWORD characters_written = GetTempPathW(SB_FILE_MAX_PATH, w_file_path);
+  int64_t characters_written =
+      static_cast<int>(GetTempPathW(SB_FILE_MAX_PATH, w_file_path));
   if (characters_written >= (path_size + 1) || characters_written < 1) {
     return false;
   }
@@ -130,7 +131,7 @@
 
 // Note: This function is only minimally implemented to allow tests to run.
 bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) {
-  if (!out_path || !path_size) {
+  if (!out_path || (path_size <= 0)) {
     return false;
   }
 
diff --git a/src/testing/gtest.gyp b/src/testing/gtest.gyp
index 2b125f1..888ce5f 100644
--- a/src/testing/gtest.gyp
+++ b/src/testing/gtest.gyp
@@ -50,6 +50,20 @@
       'dependencies': [
         'gtest_prod',
       ],
+      'cflags_cc': [
+        '-frtti',
+      ],
+      'cflags_cc!': [
+        '-fno-rtti',
+      ],
+      'all_dependent_settings': {
+        'cflags_cc': [
+          '-frtti',
+        ],
+        'cflags_cc!': [
+          '-fno-rtti',
+        ],
+      },
       'conditions': [
         ['OS == "mac" or OS == "ios"', {
           'sources': [
diff --git a/src/third_party/angle/src/libANGLE/Caps.cpp b/src/third_party/angle/src/libANGLE/Caps.cpp
index df7947c..0b97fb5 100644
--- a/src/third_party/angle/src/libANGLE/Caps.cpp
+++ b/src/third_party/angle/src/libANGLE/Caps.cpp
@@ -569,7 +569,11 @@
     textureHalfFloatLinear = DetermineHalfFloatTextureFilteringSupport(textureCaps);
     textureFloat = DetermineFloatTextureSupport(textureCaps);
     textureFloatLinear = DetermineFloatTextureFilteringSupport(textureCaps);
+#if defined(OS_STARBOARD)
+    textureRG = false;
+#else
     textureRG = DetermineRGTextureSupport(textureCaps, textureHalfFloat, textureFloat);
+#endif  // OS_STARBOARD
     textureCompressionDXT1 = DetermineDXT1TextureSupport(textureCaps);
     textureCompressionDXT3 = DetermineDXT3TextureSupport(textureCaps);
     textureCompressionDXT5 = DetermineDXT5TextureSupport(textureCaps);
diff --git a/src/third_party/blink/Source/bindings/scripts/utilities.py b/src/third_party/blink/Source/bindings/scripts/utilities.py
index 4d43f6f..02a1c6c 100644
--- a/src/third_party/blink/Source/bindings/scripts/utilities.py
+++ b/src/third_party/blink/Source/bindings/scripts/utilities.py
@@ -27,6 +27,7 @@
         'fetch',
         'h5vcc',
         'media_session',
+        'page_visibility',
         'speech',
         'testing',
         'web_animations',
diff --git a/src/third_party/libxml/src/trionan.c b/src/third_party/libxml/src/trionan.c
index 6fbabb5..d922fd2 100644
--- a/src/third_party/libxml/src/trionan.c
+++ b/src/third_party/libxml/src/trionan.c
@@ -44,7 +44,11 @@
 #include "trionan.h"
 
 #include <math.h>
+#if !defined(STARBOARD)
+// Avoid "cannot apply asm label to function after its first use" error on
+// memcpy when building with Android NDK.
 #include <string.h>
+#endif
 #include <limits.h>
 #include <float.h>
 #if defined(TRIO_PLATFORM_UNIX)
diff --git a/src/third_party/mozjs-45/cobalt_config/include/jscustomallocator.h b/src/third_party/mozjs-45/cobalt_config/include/jscustomallocator.h
index b1c9919..284fa90 100644
--- a/src/third_party/mozjs-45/cobalt_config/include/jscustomallocator.h
+++ b/src/third_party/mozjs-45/cobalt_config/include/jscustomallocator.h
@@ -17,6 +17,7 @@
 
 #include <algorithm>
 
+#include "memory_allocator_reporter.h"
 #include "starboard/memory.h"
 #include "starboard/string.h"
 
@@ -136,7 +137,11 @@
 
 static inline void* js_malloc(size_t bytes)
 {
-    return SbMemoryAllocate(bytes);
+    size_t reservation_bytes = AllocationMetadata::GetReservationBytes(bytes);
+    MemoryAllocatorReporter::Get()->UpdateAllocatedBytes(reservation_bytes);
+    void* metadata = SbMemoryAllocate(reservation_bytes);
+    AllocationMetadata::SetSizeToBaseAddress(metadata, reservation_bytes);
+    return AllocationMetadata::GetUserAddressFromBaseAddress(metadata);
 }
 
 static inline void* js_calloc(size_t nmemb, size_t size)
@@ -156,7 +161,17 @@
 
 static inline void* js_realloc(void* p, size_t bytes)
 {
-    return SbMemoryReallocate(p, bytes);
+  AllocationMetadata* metadata =
+      AllocationMetadata::GetMetadataFromUserAddress(p);
+  size_t current_size =
+      AllocationMetadata::GetSizeOfAllocationFromMetadata(metadata);
+  size_t adjusted_size = AllocationMetadata::GetReservationBytes(bytes);
+
+  MemoryAllocatorReporter::Get()->UpdateAllocatedBytes(
+      static_cast<ssize_t>(adjusted_size - current_size));
+  void* new_ptr = SbMemoryReallocate(metadata, adjusted_size);
+  AllocationMetadata::SetSizeToBaseAddress(new_ptr, adjusted_size);
+  return AllocationMetadata::GetUserAddressFromBaseAddress(new_ptr);
 }
 
 static inline void js_free(void* p)
@@ -164,7 +179,11 @@
   if (p == NULL) {
     return;
   }
-  SbMemoryDeallocate(p);
+  AllocationMetadata* metadata =
+      AllocationMetadata::GetMetadataFromUserAddress(p);
+  MemoryAllocatorReporter::Get()->UpdateAllocatedBytes(-static_cast<ssize_t>(
+      AllocationMetadata::GetSizeOfAllocationFromMetadata(metadata)));
+  SbMemoryDeallocate(metadata);
 }
 
 static inline char* js_strdup(const char* s)
diff --git a/src/third_party/mozjs-45/js/public/Conversions.h b/src/third_party/mozjs-45/js/public/Conversions.h
index c08da01..3ed04ed 100644
--- a/src/third_party/mozjs-45/js/public/Conversions.h
+++ b/src/third_party/mozjs-45/js/public/Conversions.h
@@ -385,7 +385,8 @@
 {
     // clang crashes compiling this when targeting arm-darwin:
     // https://llvm.org/bugs/show_bug.cgi?id=22974
-#if defined (__arm__) && defined (__GNUC__) && !defined(__APPLE__)
+    // Cobalt update: More than just arm-darwin, just don't bother with this on clang.
+#if defined (__arm__) && defined (__GNUC__) && !defined(__APPLE__) && !defined(__clang__)
     int32_t i;
     uint32_t    tmp0;
     uint32_t    tmp1;
diff --git a/src/third_party/mozjs-45/js/public/TraceKind.h b/src/third_party/mozjs-45/js/public/TraceKind.h
index c7e1a40..d527646 100644
--- a/src/third_party/mozjs-45/js/public/TraceKind.h
+++ b/src/third_party/mozjs-45/js/public/TraceKind.h
@@ -100,7 +100,9 @@
 // the other hand, gets very confused if we have a |template| token there.
 // The clang-cl front end defines _MSC_VER, but still requires the explicit
 // template declaration, so we must test for __clang__ here as well.
-#if defined(_MSC_VER) && !defined(__clang__)
+#if defined(STARBOARD)
+# define JS_DEPENDENT_TEMPLATE_HINT template
+#elif defined(_MSC_VER) && !defined(__clang__)
 # define JS_DEPENDENT_TEMPLATE_HINT
 #else
 # define JS_DEPENDENT_TEMPLATE_HINT template
diff --git a/src/third_party/mozjs-45/js/public/Utility.h b/src/third_party/mozjs-45/js/public/Utility.h
index be4c600..5aaf48e 100644
--- a/src/third_party/mozjs-45/js/public/Utility.h
+++ b/src/third_party/mozjs-45/js/public/Utility.h
@@ -24,6 +24,7 @@
 #endif
 
 #include "jstypes.h"
+#include "js-confdefs.h"
 
 /* The public JS engine namespace. */
 namespace JS {}
diff --git a/src/third_party/mozjs-45/js/public/Value.h b/src/third_party/mozjs-45/js/public/Value.h
index 20a441a..e208483 100644
--- a/src/third_party/mozjs-45/js/public/Value.h
+++ b/src/third_party/mozjs-45/js/public/Value.h
@@ -968,6 +968,7 @@
     return JSVAL_IS_GCTHING_IMPL(l) && !JSVAL_IS_NULL_IMPL(l);
 }
 
+// TODO : Fix Windows compilation problem with JSVAL_TO_IMPL.
 static inline jsval_layout JSVAL_TO_IMPL(JS::Value v);
 static inline JS_VALUE_CONSTEXPR JS::Value IMPL_TO_JSVAL(jsval_layout l);
 
diff --git a/src/third_party/mozjs-45/js/src/gc/GCRuntime.h b/src/third_party/mozjs-45/js/src/gc/GCRuntime.h
index d7cf559..36d2d9d 100644
--- a/src/third_party/mozjs-45/js/src/gc/GCRuntime.h
+++ b/src/third_party/mozjs-45/js/src/gc/GCRuntime.h
@@ -8,6 +8,7 @@
 #define gc_GCRuntime_h
 
 #include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
 
 #include "jsfriendapi.h"
 #include "jsgc.h"
diff --git a/src/third_party/mozjs-45/js/src/gc/Memory.cpp b/src/third_party/mozjs-45/js/src/gc/Memory.cpp
index 72bad54..988445a2 100644
--- a/src/third_party/mozjs-45/js/src/gc/Memory.cpp
+++ b/src/third_party/mozjs-45/js/src/gc/Memory.cpp
@@ -12,7 +12,9 @@
 #include "js/HeapAPI.h"
 #include "vm/Runtime.h"
 
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+#include "starboard/log.h"
+#elif defined(XP_WIN)
 
 #include "jswin.h"
 #include <psapi.h>
@@ -815,7 +817,9 @@
 ProtectPages(void* p, size_t size)
 {
     MOZ_ASSERT(size % pageSize == 0);
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+    SB_NOTIMPLEMENTED();
+#elif defined(XP_WIN)
     DWORD oldProtect;
     if (!VirtualProtect(p, size, PAGE_NOACCESS, &oldProtect))
         MOZ_CRASH("VirtualProtect(PAGE_NOACCESS) failed");
@@ -830,7 +834,9 @@
 UnprotectPages(void* p, size_t size)
 {
     MOZ_ASSERT(size % pageSize == 0);
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+    SB_NOTIMPLEMENTED();
+#elif defined(XP_WIN)
     DWORD oldProtect;
     if (!VirtualProtect(p, size, PAGE_READWRITE, &oldProtect))
         MOZ_CRASH("VirtualProtect(PAGE_READWRITE) failed");
diff --git a/src/third_party/mozjs-45/js/src/irregexp/RegExpEngine.cpp b/src/third_party/mozjs-45/js/src/irregexp/RegExpEngine.cpp
index ba30fc7..b84accb 100644
--- a/src/third_party/mozjs-45/js/src/irregexp/RegExpEngine.cpp
+++ b/src/third_party/mozjs-45/js/src/irregexp/RegExpEngine.cpp
@@ -1643,7 +1643,7 @@
 static bool
 IsNativeRegExpEnabled(JSContext* cx)
 {
-#ifdef JS_CODEGEN_NONE
+#if defined(JS_CODEGEN_NONE) || defined(COBALT_DISABLE_JIT)
     return false;
 #else
     return cx->runtime()->options().nativeRegExp();
diff --git a/src/third_party/mozjs-45/js/src/jit/BaselineJIT.h b/src/third_party/mozjs-45/js/src/jit/BaselineJIT.h
index 1b35e6e..ff177c8 100644
--- a/src/third_party/mozjs-45/js/src/jit/BaselineJIT.h
+++ b/src/third_party/mozjs-45/js/src/jit/BaselineJIT.h
@@ -497,7 +497,7 @@
 inline bool
 IsBaselineEnabled(JSContext* cx)
 {
-#ifdef JS_CODEGEN_NONE
+#if defined(JS_CODEGEN_NONE) || defined(COBALT_DISABLE_JIT)
     return false;
 #else
     return cx->runtime()->options().baseline();
diff --git a/src/third_party/mozjs-45/js/src/jit/ExecutableAllocatorPosix.cpp b/src/third_party/mozjs-45/js/src/jit/ExecutableAllocatorPosix.cpp
index d6d7911..0e99f11 100644
--- a/src/third_party/mozjs-45/js/src/jit/ExecutableAllocatorPosix.cpp
+++ b/src/third_party/mozjs-45/js/src/jit/ExecutableAllocatorPosix.cpp
@@ -37,12 +37,14 @@
 
 using namespace js::jit;
 
+// TODO: Starboardize.
 size_t
 ExecutableAllocator::determinePageSize()
 {
     return getpagesize();
 }
 
+// TODO: Starboardize. PROT_READ, etc. flags are not defined in Starboard.
 void*
 js::jit::AllocateExecutableMemory(void* addr, size_t bytes, unsigned permissions, const char* tag,
                                   size_t pageSize)
@@ -52,6 +54,7 @@
     return p == MAP_FAILED ? nullptr : p;
 }
 
+// TODO: Starboardize.
 void
 js::jit::DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize)
 {
@@ -75,9 +78,11 @@
     DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
 }
 
+// TODO: Starboardize. PROT_READ, etc. flags are not defined in Starboard.
 static const unsigned FLAGS_RW = PROT_READ | PROT_WRITE;
 static const unsigned FLAGS_RX = PROT_READ | PROT_EXEC;
 
+// TODO: Starboardize.
 void
 ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
 {
@@ -98,6 +103,7 @@
     mprotect(pageStart, size, (setting == Writable) ? FLAGS_RW : FLAGS_RX);
 }
 
+// TODO: Starboardize.
 /* static */ unsigned
 ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
 {
diff --git a/src/third_party/mozjs-45/js/src/jit/Ion.h b/src/third_party/mozjs-45/js/src/jit/Ion.h
index 8a0b65e..a7f21db 100644
--- a/src/third_party/mozjs-45/js/src/jit/Ion.h
+++ b/src/third_party/mozjs-45/js/src/jit/Ion.h
@@ -155,7 +155,7 @@
 IsIonEnabled(JSContext* cx)
 {
     // The ARM64 Ion engine is not yet implemented.
-#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
+#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64) || defined(COBALT_DISABLE_JIT)
     return false;
 #else
     return cx->runtime()->options().ion() &&
diff --git a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/Debugger-vixl.h b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/Debugger-vixl.h
index 86c5a91..cf5e116 100644
--- a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/Debugger-vixl.h
+++ b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/Debugger-vixl.h
@@ -32,8 +32,11 @@
 #include <limits.h>
 
 #include "jit/arm64/vixl/Constants-vixl.h"
+#include "jit/arm64/vixl/Decoder-vixl.h"
+#include "jit/arm64/vixl/Disasm-vixl.h"
 #include "jit/arm64/vixl/Globals-vixl.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
+#include "jit/arm64/vixl/Simulator-vixl.h"
 #include "jit/arm64/vixl/Utils-vixl.h"
 
 namespace vixl {
diff --git a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
index f2b7156..952b7c2 100644
--- a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
+++ b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
@@ -425,6 +425,8 @@
     Instr dp_op = static_cast<Instr>(op | LogicalShiftedFixed);
     return DataProcShiftedRegister(rd, rn, operand, LeaveFlags, dp_op);
   }
+
+  return BufferOffset();
 }
 
 
diff --git a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp
index fcae1ba..a4fc9dc 100644
--- a/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp
+++ b/src/third_party/mozjs-45/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp
@@ -115,6 +115,8 @@
       default:
         VIXL_UNREACHABLE();
     }
+
+    return false;
 }
 
 
diff --git a/src/third_party/mozjs-45/js/src/jsapi.cpp b/src/third_party/mozjs-45/js/src/jsapi.cpp
index adbee56..b9f2ad1 100644
--- a/src/third_party/mozjs-45/js/src/jsapi.cpp
+++ b/src/third_party/mozjs-45/js/src/jsapi.cpp
@@ -3778,6 +3778,7 @@
 
 typedef Vector<char, 8, TempAllocPolicy> FileContents;
 
+// TODO: Starboardize this similar to mozjs 24.
 static bool
 ReadCompleteFile(JSContext* cx, FILE* fp, FileContents& buffer)
 {
diff --git a/src/third_party/mozjs-45/js/src/jsapi.h b/src/third_party/mozjs-45/js/src/jsapi.h
index 2940624..f506eed 100644
--- a/src/third_party/mozjs-45/js/src/jsapi.h
+++ b/src/third_party/mozjs-45/js/src/jsapi.h
@@ -1085,11 +1085,22 @@
 class JS_PUBLIC_API(RuntimeOptions) {
   public:
     RuntimeOptions()
-      : baseline_(true),
+      :
+#if defined(COBALT_DISABLE_JIT)
+        baseline_(false),
+        ion_(false),
+        asmJS_(false),
+#else
+        baseline_(true),
         ion_(true),
         asmJS_(true),
+#endif
         throwOnAsmJSValidationFailure_(false),
+#if defined(COBALT_DISABLE_JIT)
+        nativeRegExp_(false),
+#else
         nativeRegExp_(true),
+#endif
         unboxedArrays_(false),
         asyncStack_(true),
         werror_(false),
diff --git a/src/third_party/mozjs-45/js/src/jsgc.cpp b/src/third_party/mozjs-45/js/src/jsgc.cpp
index 23bd1a9..acc2db5 100644
--- a/src/third_party/mozjs-45/js/src/jsgc.cpp
+++ b/src/third_party/mozjs-45/js/src/jsgc.cpp
@@ -188,7 +188,8 @@
 
 #include <ctype.h>
 #include <string.h>
-#ifndef XP_WIN
+#if defined(STARBOARD)
+#elif !defined(XP_WIN)
 # include <sys/mman.h>
 # include <unistd.h>
 #endif
diff --git a/src/third_party/mozjs-45/js/src/jsutil.h b/src/third_party/mozjs-45/js/src/jsutil.h
index 9a9dea4..401ca64 100644
--- a/src/third_party/mozjs-45/js/src/jsutil.h
+++ b/src/third_party/mozjs-45/js/src/jsutil.h
@@ -21,6 +21,8 @@
 #include "js/Utility.h"
 #include "js/Value.h"
 
+#include "js-confdefs.h"
+
 #define JS_ALWAYS_TRUE(expr)      MOZ_ALWAYS_TRUE(expr)
 #define JS_ALWAYS_FALSE(expr)     MOZ_ALWAYS_FALSE(expr)
 
diff --git a/src/third_party/mozjs-45/js/src/memory_allocator_reporter.cpp b/src/third_party/mozjs-45/js/src/memory_allocator_reporter.cpp
new file mode 100644
index 0000000..a7d8efa
--- /dev/null
+++ b/src/third_party/mozjs-45/js/src/memory_allocator_reporter.cpp
@@ -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 "memory_allocator_reporter.h"
+
+#include "starboard/once.h"
+
+namespace {
+// Control to initialize s_instance.
+SbOnceControl s_instance_control = SB_ONCE_INITIALIZER;
+MemoryAllocatorReporter* s_instance = NULL;
+
+void Initialize() {
+  s_instance = new MemoryAllocatorReporter();
+}
+
+void* OffsetPointer(void* base, int64_t offset) {
+  uintptr_t base_as_int = reinterpret_cast<uintptr_t>(base);
+#if defined(STARBOARD)
+    return reinterpret_cast<void*>(base_as_int + static_cast<uintptr_t>(offset));
+#else
+    return reinterpret_cast<void*>(base_as_int + offset);
+#endif
+}
+}  // namespace
+
+AllocationMetadata* AllocationMetadata::GetMetadataFromUserAddress(void* ptr) {
+  if (ptr == NULL) {
+    return NULL;
+  }
+
+  // The metadata lives just in front of the data.
+  void* meta_addr =
+      OffsetPointer(ptr, -static_cast<int64_t>(sizeof(AllocationMetadata)));
+  return reinterpret_cast<AllocationMetadata*>(meta_addr);
+}
+
+void* AllocationMetadata::GetUserAddressFromBaseAddress(void* base_ptr) {
+  void* adjusted_base =
+      OffsetPointer(base_ptr, static_cast<int64_t>(sizeof(AllocationMetadata)));
+  return adjusted_base;
+}
+
+void AllocationMetadata::SetSizeToBaseAddress(void* base_ptr, int64_t size) {
+  if (base_ptr) {
+    AllocationMetadata* metadata =
+        reinterpret_cast<AllocationMetadata*>(base_ptr);
+    metadata->set_size_requested(size);
+  }
+}
+
+void MemoryAllocatorReporter::UpdateAllocatedBytes(int64_t bytes) {
+  starboard::ScopedLock lock(mutex_);
+  current_bytes_allocated_ += bytes;
+}
+
+int64_t MemoryAllocatorReporter::GetCurrentBytesAllocated() {
+  starboard::ScopedLock lock(mutex_);
+  return current_bytes_allocated_;
+}
+
+void MemoryAllocatorReporter::UpdateMappedBytes(int64_t bytes) {
+  starboard::ScopedLock lock(mutex_);
+  current_bytes_mapped_ += bytes;
+}
+
+int64_t MemoryAllocatorReporter::GetCurrentBytesMapped() {
+  starboard::ScopedLock lock(mutex_);
+  return current_bytes_mapped_;
+}
+
+// static
+MemoryAllocatorReporter* MemoryAllocatorReporter::Get() {
+  SbOnce(&s_instance_control, &Initialize);
+  return s_instance;
+}
diff --git a/src/third_party/mozjs-45/js/src/memory_allocator_reporter.h b/src/third_party/mozjs-45/js/src/memory_allocator_reporter.h
new file mode 100644
index 0000000..82227e3
--- /dev/null
+++ b/src/third_party/mozjs-45/js/src/memory_allocator_reporter.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 MemoryAllocatorReporter_h
+#define MemoryAllocatorReporter_h
+
+#include "starboard/mutex.h"
+#include "starboard/types.h"
+
+class AllocationMetadata {
+ public:
+  static AllocationMetadata* GetMetadataFromUserAddress(void* ptr);
+  static void* GetUserAddressFromBaseAddress(void* base_ptr);
+  static int64_t GetReservationBytes(int64_t bytes_requested) {
+    return sizeof(AllocationMetadata) + bytes_requested;
+  }
+  static int64_t GetSizeOfAllocationFromMetadata(AllocationMetadata* metadata) {
+    return metadata ? metadata->size_requested() : 0;
+  }
+  static void SetSizeToBaseAddress(void* base_ptr, int64_t size);
+
+  int64_t size_requested() { return size_requested_; }
+  void set_size_requested(int64_t size) { size_requested_ = size; }
+
+ private:
+  // Bytes requested by the underlying allocator.
+  int64_t size_requested_;
+};
+
+// Reporter that is used to report memory allocation.
+class MemoryAllocatorReporter {
+ public:
+  static MemoryAllocatorReporter* Get();
+
+  MemoryAllocatorReporter()
+      : current_bytes_allocated_(0), current_bytes_mapped_(0) {}
+
+  void UpdateAllocatedBytes(int64_t bytes);
+  int64_t GetCurrentBytesAllocated();
+
+  void UpdateMappedBytes(int64_t bytes);
+  int64_t GetCurrentBytesMapped();
+
+ private:
+  starboard::Mutex mutex_;
+  int64_t current_bytes_allocated_;
+  int64_t current_bytes_mapped_;
+};
+
+#endif /* MemoryAllocatorReporter_h */
diff --git a/src/third_party/mozjs-45/js/src/shell/OSObject.cpp b/src/third_party/mozjs-45/js/src/shell/OSObject.cpp
index 6b04f3d..eba8a16 100644
--- a/src/third_party/mozjs-45/js/src/shell/OSObject.cpp
+++ b/src/third_party/mozjs-45/js/src/shell/OSObject.cpp
@@ -37,7 +37,8 @@
 
 #include "jsobjinlines.h"
 
-#ifdef XP_WIN
+#if defined(STARBOARD)
+#elif defined(XP_WIN)
 # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
 # define getcwd _getcwd
 #else
@@ -521,7 +522,10 @@
 {
     char buffer[200];
 
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+    SbSystemGetErrorString(errno, buffer, sizeof(buffer));
+    const char* errstr = buffer;
+#elif defined(XP_WIN)
     strerror_s(buffer, sizeof(buffer), errno);
     const char* errstr = buffer;
 #else
diff --git a/src/third_party/mozjs-45/js/src/vm/ArrayBufferObject.cpp b/src/third_party/mozjs-45/js/src/vm/ArrayBufferObject.cpp
index 5cb626c..dbb67a2 100644
--- a/src/third_party/mozjs-45/js/src/vm/ArrayBufferObject.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/ArrayBufferObject.cpp
@@ -12,7 +12,8 @@
 #include "mozilla/TaggedAnonymousMemory.h"
 
 #include <string.h>
-#ifndef XP_WIN
+#if defined(STARBOARD)
+#elif !defined(XP_WIN)
 # include <sys/mman.h>
 #endif
 
diff --git a/src/third_party/mozjs-45/js/src/vm/CodeCoverage.cpp b/src/third_party/mozjs-45/js/src/vm/CodeCoverage.cpp
index 4b39d67..e63bb44 100644
--- a/src/third_party/mozjs-45/js/src/vm/CodeCoverage.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/CodeCoverage.cpp
@@ -10,7 +10,9 @@
 #include "mozilla/IntegerPrintfMacros.h"
 
 #include <stdio.h>
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+#include "starboard/thread.h"
+#elif defined(XP_WIN)
 # include <windows.h>
 #else
 # include <unistd.h>
@@ -509,7 +511,9 @@
 
 LCovRuntime::LCovRuntime()
   : out_(),
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+  pid_(SbThreadGetId()),
+#elif defined(XP_WIN)
     pid_(GetCurrentProcessId()),
 #else
     pid_(getpid()),
@@ -578,7 +582,9 @@
     if (!out_.isInitialized())
         return;
 
-#if defined(XP_WIN)
+#if defined(STARBOARD)
+    size_t p = SbThreadGetId();
+#elif defined(XP_WIN)
     size_t p = GetCurrentProcessId();
 #else
     size_t p = getpid();
diff --git a/src/third_party/mozjs-45/js/src/vm/Compression.h b/src/third_party/mozjs-45/js/src/vm/Compression.h
index c2db628..2ebbff9 100644
--- a/src/third_party/mozjs-45/js/src/vm/Compression.h
+++ b/src/third_party/mozjs-45/js/src/vm/Compression.h
@@ -7,7 +7,11 @@
 #ifndef vm_Compression_h
 #define vm_Compression_h
 
+#if defined(STARBOARD)
+#include "third_party/zlib/zlib.h"
+#else
 #include <zlib.h>
+#endif
 
 #include "jstypes.h"
 
diff --git a/src/third_party/mozjs-45/js/src/vm/Printer.cpp b/src/third_party/mozjs-45/js/src/vm/Printer.cpp
index 8f7fc86..a4b126c 100644
--- a/src/third_party/mozjs-45/js/src/vm/Printer.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/Printer.cpp
@@ -13,6 +13,7 @@
 #include "jscntxt.h"
 #include "jsprf.h"
 #include "jsutil.h"
+#include "js-confdefs.h"
 
 #include "ds/LifoAlloc.h"
 
diff --git a/src/third_party/mozjs-45/js/src/vm/Printer.h b/src/third_party/mozjs-45/js/src/vm/Printer.h
index 089f079..b01e342 100644
--- a/src/third_party/mozjs-45/js/src/vm/Printer.h
+++ b/src/third_party/mozjs-45/js/src/vm/Printer.h
@@ -11,6 +11,8 @@
 #include <stddef.h>
 #include <stdio.h>
 
+#include "js-confdefs.h"
+
 class JSString;
 
 namespace js {
diff --git a/src/third_party/mozjs-45/js/src/vm/Runtime.cpp b/src/third_party/mozjs-45/js/src/vm/Runtime.cpp
index 7df7dee..890da16 100644
--- a/src/third_party/mozjs-45/js/src/vm/Runtime.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/Runtime.cpp
@@ -24,6 +24,7 @@
 #include <locale.h>
 #include <string.h>
 
+// TODO: Starboardize.
 #ifdef JS_CAN_CHECK_THREADSAFE_ACCESSES
 # include <sys/mman.h>
 #endif
@@ -259,6 +260,7 @@
   return !!js_sb_getenv("JS_DISABLE_SLOW_SCRIPT_SIGNALS") || !!js_sb_getenv("JS_NO_SIGNALS");
 }
 
+// TODO: Starboardize.
 bool
 JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
 {
diff --git a/src/third_party/mozjs-45/js/src/vm/SharedArrayObject.cpp b/src/third_party/mozjs-45/js/src/vm/SharedArrayObject.cpp
index 7f8c797..fe82fb8 100644
--- a/src/third_party/mozjs-45/js/src/vm/SharedArrayObject.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/SharedArrayObject.cpp
@@ -15,7 +15,10 @@
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
-#ifndef XP_WIN
+#if defined(STARBOARD)
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#elif !defined(XP_WIN)
 # include <sys/mman.h>
 #endif
 #ifdef MOZ_VALGRIND
@@ -33,7 +36,12 @@
 static inline void*
 MapMemory(size_t length, bool commit)
 {
-#ifdef XP_WIN
+#if defined(STARBOARD)
+    if (!commit) {
+        SB_NOTREACHED();
+    }
+    return SbMemoryMap(length, kSbMemoryMapProtectReadWrite, NULL);
+#elif defined(XP_WIN)
     int prot = (commit ? MEM_COMMIT : MEM_RESERVE);
     int flags = (commit ? PAGE_READWRITE : PAGE_NOACCESS);
     return VirtualAlloc(nullptr, length, prot, flags);
@@ -49,7 +57,9 @@
 static inline void
 UnmapMemory(void* addr, size_t len)
 {
-#ifdef XP_WIN
+#if defined(STARBOARD)
+    SbMemoryUnmap(addr, len);
+#elif defined(XP_WIN)
     VirtualFree(addr, 0, MEM_RELEASE);
 #else
     munmap(addr, len);
@@ -59,7 +69,10 @@
 static inline bool
 MarkValidRegion(void* addr, size_t len)
 {
-#ifdef XP_WIN
+#if defined(STARBOARD)
+    SB_NOTIMPLEMENTED();
+    return false;
+#elif defined(XP_WIN)
     if (!VirtualAlloc(addr, len, MEM_COMMIT, PAGE_READWRITE))
         return false;
     return true;
diff --git a/src/third_party/mozjs-45/js/src/vm/String.cpp b/src/third_party/mozjs-45/js/src/vm/String.cpp
index 3771837..c2adbf6 100644
--- a/src/third_party/mozjs-45/js/src/vm/String.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/String.cpp
@@ -18,6 +18,7 @@
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
+#include "js-confdefs.h"
 
 // Unified leak fix:
 #include "builtin/ModuleObject.h"
diff --git a/src/third_party/mozjs-45/js/src/vm/TypedArrayObject.cpp b/src/third_party/mozjs-45/js/src/vm/TypedArrayObject.cpp
index 16081fe..13f2ac9 100644
--- a/src/third_party/mozjs-45/js/src/vm/TypedArrayObject.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/TypedArrayObject.cpp
@@ -11,7 +11,8 @@
 #include "mozilla/PodOperations.h"
 
 #include <string.h>
-#ifndef XP_WIN
+#if defined(STARBOARD)
+#elif !defined(XP_WIN)
 # include <sys/mman.h>
 #endif
 
diff --git a/src/third_party/mozjs-45/js/src/vm/UnboxedObject.cpp b/src/third_party/mozjs-45/js/src/vm/UnboxedObject.cpp
index f661dde..14e0413 100644
--- a/src/third_party/mozjs-45/js/src/vm/UnboxedObject.cpp
+++ b/src/third_party/mozjs-45/js/src/vm/UnboxedObject.cpp
@@ -695,7 +695,7 @@
             return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
     }
 
-#ifndef JS_CODEGEN_NONE
+#if !defined(JS_CODEGEN_NONE) && !defined(COBALT_DISABLE_JIT)
     if (cx->isJSContext() &&
         !layout.constructorCode() &&
         cx->asJSContext()->runtime()->jitSupportsFloatingPoint)
diff --git a/src/third_party/mozjs-45/mfbt/DebugOnly.h b/src/third_party/mozjs-45/mfbt/DebugOnly.h
index 451d9bc..179b773 100644
--- a/src/third_party/mozjs-45/mfbt/DebugOnly.h
+++ b/src/third_party/mozjs-45/mfbt/DebugOnly.h
@@ -14,6 +14,8 @@
 
 #include "mozilla/Attributes.h"
 
+#include "js-confdefs.h"
+
 namespace mozilla {
 
 /**
diff --git a/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.cpp b/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.cpp
index a2ba9ee..f696028 100644
--- a/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.cpp
+++ b/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.cpp
@@ -28,6 +28,7 @@
 
 namespace mozilla {
 
+// TODO: Starboardize.
 // Returns 0 for success and -1 (with errno) for error.
 static int
 TagAnonymousMemoryAligned(const void* aPtr, size_t aLength, const char* aTag)
@@ -39,6 +40,7 @@
                reinterpret_cast<unsigned long>(aTag));
 }
 
+// TODO: Starboardize.
 // On some architectures, it's possible for the page size to be larger
 // than the PAGE_SIZE we were compiled with.  This computes the
 // equivalent of PAGE_MASK.
@@ -89,6 +91,7 @@
   }
 }
 
+// TODO: Starboardize.
 void*
 MozTaggedAnonymousMmap(void* aAddr, size_t aLength, int aProt, int aFlags,
                        int aFd, off_t aOffset, const char* aTag)
diff --git a/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.h b/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.h
index d26b06d..e62335e 100644
--- a/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.h
+++ b/src/third_party/mozjs-45/mfbt/TaggedAnonymousMemory.h
@@ -34,6 +34,7 @@
 
 #ifndef XP_WIN
 
+// TODO: Starboardize
 #include <sys/types.h>
 #include <sys/mman.h>
 
@@ -66,6 +67,7 @@
 {
 }
 
+// TODO: Starboardize.
 static inline void*
 MozTaggedAnonymousMmap(void* aAddr, size_t aLength, int aProt, int aFlags,
                        int aFd, off_t aOffset, const char* aTag)
diff --git a/src/third_party/mozjs-45/mfbt/Types.h b/src/third_party/mozjs-45/mfbt/Types.h
index a5d9363..c5bfdc6 100644
--- a/src/third_party/mozjs-45/mfbt/Types.h
+++ b/src/third_party/mozjs-45/mfbt/Types.h
@@ -37,7 +37,9 @@
  * These macros are designed for use by library interfaces -- not for normal
  * methods or data used cross-file.
  */
-#if defined(WIN32)
+#if defined(STARBOARD)
+#define MOZ_EXPORT /* nothing */
+#elif defined(WIN32)
 #  define MOZ_EXPORT   __declspec(dllexport)
 #else /* Unix */
 #  ifdef HAVE_VISIBILITY_ATTRIBUTE
@@ -57,7 +59,9 @@
  * the export or import version of the macro, depending upon the compilation
  * mode.
  */
-#ifdef _WIN32
+#if defined(STARBOARD)
+#define MOZ_IMPORT_API /* nothing */
+#elif defined(_WIN32)
 #  if defined(__MWERKS__)
 #    define MOZ_IMPORT_API /* nothing */
 #  else
diff --git a/src/third_party/mozjs-45/mozglue/misc/TimeStamp.cpp b/src/third_party/mozjs-45/mozglue/misc/TimeStamp.cpp
index 3ff4af4..b640936 100644
--- a/src/third_party/mozjs-45/mozglue/misc/TimeStamp.cpp
+++ b/src/third_party/mozjs-45/mozglue/misc/TimeStamp.cpp
@@ -12,6 +12,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "js-confdefs.h"
+
 namespace mozilla {
 
 /**
@@ -46,7 +48,7 @@
 
 static TimeStampInitialization sInitOnce;
 
-MFBT_API TimeStamp
+TimeStamp
 TimeStamp::ProcessCreation(bool& aIsInconsistent)
 {
   aIsInconsistent = false;
diff --git a/src/third_party/mozjs-45/mozjs-45.gyp b/src/third_party/mozjs-45/mozjs-45.gyp
index 3b09dfc..3cd5233 100644
--- a/src/third_party/mozjs-45/mozjs-45.gyp
+++ b/src/third_party/mozjs-45/mozjs-45.gyp
@@ -40,85 +40,75 @@
       'cobalt_config/include',
       '<(DEPTH)/third_party/icu/source/common',
     ],
+    'common_msvs_disabled_warnings': [
+      # Level 2, Possible loss of data due to type conversion.
+      4244,
+      # Level 3, Possible loss of data due to type conversion from size_t.
+      4267,
+    ],
 
     'conditions': [
       ['target_arch == "x86"', {
         'common_defines': [
           'JS_CPU_X86=1',
+          'JS_CODEGEN_X86=1',
           'JS_NUNBOX32=1',
         ],
       }],
       ['target_arch == "x64"', {
         'common_defines': [
           'JS_CPU_X64=1',
+          'JS_CODEGEN_X64=1',
           'JS_PUNBOX64=1',
         ],
       }],
       ['target_arch == "arm"', {
         'common_defines': [
           'JS_CPU_ARM=1',
+          'JS_CODEGEN_ARM=1',
           'JS_NUNBOX32=1',
         ],
       }],
       ['target_arch == "arm64"', {
         'common_defines': [
           'JS_CPU_ARM64=1',
+          'JS_CODEGEN_ARM64=1',
           'JS_PUNBOX64=1',
+          # arm64 jit appears to not be ready, won't even compile without
+          # compiling in the simulator.  It is highly recommended that
+          # |cobalt_enable_jit| be set to |0| when building for architecture
+          # |arm64|.
+          'JS_SIMULATOR=1',
+          'JS_SIMULATOR_ARM64=1',
         ],
       }],
       ['target_arch == "mips"', {
         'common_defines': [
           'JS_CPU_MIPS=1',
+          'JS_CODEGEN_MIPS32=1',
           'JS_NUNBOX32=1',
         ],
       }],
       ['target_arch == "mips64"', {
         'common_defines': [
           'JS_CPU_MIPS=1',
-          'JS_PUNBOX64=1',
-        ],
-      }],
-      # TODO: Remove once ps4 configuration todos are addressed.
-      ['target_arch == "ps4" or actual_target_arch == "ps4" or sb_target_platform == "ps4"', {
-        'common_defines': [
-          'JS_CPU_X64=1',
+          'JS_CODEGEN_MIPS64=1',
           'JS_PUNBOX64=1',
         ],
       }],
 
-      ['cobalt_enable_jit == 0', {
+      ['cobalt_enable_jit != 1', {
         'common_defines': [
-          'JS_CODEGEN_NONE=1',
+          'COBALT_DISABLE_JIT=1',
         ],
       }],
-      ['target_arch == "x86" and cobalt_enable_jit == 1', {
+
+      # TODO: Remove once ps4 configuration todos are addressed.
+      ['target_arch == "ps4" or actual_target_arch == "ps4" or sb_target_platform == "ps4"', {
         'common_defines': [
-          'JS_CODEGEN_X86=1',
-        ],
-      }],
-      ['target_arch == "x64" and cobalt_enable_jit == 1', {
-        'common_defines': [
+          'JS_CPU_X64=1',
           'JS_CODEGEN_X64=1',
-        ],
-      }],
-      ['target_arch == "arm" and cobalt_enable_jit == 1', {
-        'common_defines': [
-          'JS_CODEGEN_ARM=1',
-        ],
-      }],
-      ['target_arch == "arm64" and cobalt_enable_jit == 1', {
-        'common_defines': [
-          'JS_CODEGEN_ARM64=1',
-        ],
-      }],
-      ['target_arch == "mips" and cobalt_enable_jit == 1', {
-        'common_defines': [
-          'JS_CODEGEN_MIPS32=1',
-        ],
-      }],
-      ['target_arch == "mips64" and cobalt_enable_jit == 1', {
-        'common_defines': [
-          'JS_CODEGEN_MIPS64=1',
+          'JS_PUNBOX64=1',
         ],
       }],
 
@@ -138,6 +128,12 @@
 
   'target_defaults': {
     'defines': [ '<@(common_defines)', ],
+    'msvs_disabled_warnings': [ '<@(common_msvs_disabled_warnings)', ],
+
+    # Unfortunately, there is code that generate warnings in the headers.
+    'direct_dependent_settings': {
+      'msvs_disabled_warnings': [ '<@(common_msvs_disabled_warnings)', ],
+    },
   },
 
   'targets': [
@@ -165,7 +161,7 @@
         '<@(mozjs-45_sources)',
       ],
       'conditions': [
-        ['target_arch == "x86" and cobalt_enable_jit == 1', {
+        ['target_arch == "x86"', {
           'sources': [
             'js/src/jit/x86-shared/Architecture-x86-shared.cpp',
             'js/src/jit/x86-shared/Assembler-x86-shared.cpp',
@@ -188,7 +184,8 @@
             'js/src/jit/x86/Trampoline-x86.cpp',
           ],
         }],
-        ['target_arch == "x64" and cobalt_enable_jit == 1', {
+        # TODO: Remove "* == ps4" once ps4 configuration todos are addressed.
+        ['target_arch == "x64" or target_arch == "ps4" or actual_target_arch == "ps4" or sb_target_platform == "ps4"', {
           'sources': [
             'js/src/jit/x64/Assembler-x64.cpp',
             'js/src/jit/x64/Bailouts-x64.cpp',
@@ -211,7 +208,7 @@
             'js/src/jit/x86-shared/MoveEmitter-x86-shared.cpp',
           ],
         }],
-        ['target_arch == "arm" and cobalt_enable_jit == 1', {
+        ['target_arch == "arm"', {
           'sources': [
             'js/src/jit/arm/Architecture-arm.cpp',
             'js/src/jit/arm/Architecture-arm.h',
@@ -243,7 +240,7 @@
             'js/src/jit/arm/Trampoline-arm.cpp',
           ],
         }],
-        ['target_arch == "arm64" and cobalt_enable_jit == 1', {
+        ['target_arch == "arm64"', {
           'sources': [
             'js/src/jit/arm64/Architecture-arm64.cpp',
             'js/src/jit/arm64/Architecture-arm64.h',
@@ -270,9 +267,23 @@
             'js/src/jit/arm64/SharedICHelpers-arm64.h',
             'js/src/jit/arm64/SharedICRegisters-arm64.h',
             'js/src/jit/arm64/Trampoline-arm64.cpp',
+            'js/src/jit/arm64/vixl/Assembler-vixl.cpp',
+            'js/src/jit/arm64/vixl/Cpu-vixl.cpp',
+            'js/src/jit/arm64/vixl/Debugger-vixl.cpp',
+            'js/src/jit/arm64/vixl/Decoder-vixl.cpp',
+            'js/src/jit/arm64/vixl/Disasm-vixl.cpp',
+            'js/src/jit/arm64/vixl/Instructions-vixl.cpp',
+            'js/src/jit/arm64/vixl/Instrument-vixl.cpp',
+            'js/src/jit/arm64/vixl/Logic-vixl.cpp',
+            'js/src/jit/arm64/vixl/MacroAssembler-vixl.cpp',
+            'js/src/jit/arm64/vixl/MozAssembler-vixl.cpp',
+            'js/src/jit/arm64/vixl/MozInstructions-vixl.cpp',
+            'js/src/jit/arm64/vixl/MozSimulator-vixl.cpp',
+            'js/src/jit/arm64/vixl/Simulator-vixl.cpp',
+            'js/src/jit/arm64/vixl/Utils-vixl.cpp',
           ],
         }],
-        ['target_arch == "mips" and cobalt_enable_jit == 1', {
+        ['target_arch == "mips"', {
           'sources': [
             'js/src/jit/mips-shared/Architecture-mips-shared.cpp',
             'js/src/jit/mips-shared/Assembler-mips-shared.cpp',
@@ -296,7 +307,7 @@
             'js/src/jit/mips32/Trampoline-mips32.cpp',
           ],
         }],
-        ['target_arch == "mips64" and cobalt_enable_jit == 1', {
+        ['target_arch == "mips64"', {
           'sources': [
             'js/src/jit/mips-shared/Architecture-mips-shared.cpp',
             'js/src/jit/mips-shared/Assembler-mips-shared.cpp',
@@ -320,11 +331,6 @@
             'js/src/jit/mips64/Trampoline-mips64.cpp',
           ],
         }],
-        ['cobalt_enable_jit == 0', {
-          'sources': [
-            'js/src/jit/none/Trampoline-none.cpp',
-          ],
-        }],
       ],
     },
 
diff --git a/src/third_party/mozjs-45/mozjs-45.gypi b/src/third_party/mozjs-45/mozjs-45.gypi
index f3a9b1e..cd06c66 100644
--- a/src/third_party/mozjs-45/mozjs-45.gypi
+++ b/src/third_party/mozjs-45/mozjs-45.gypi
@@ -158,6 +158,7 @@
       'js/src/jsutil.cpp',
       'js/src/jswatchpoint.cpp',
       'js/src/jsweakmap.cpp',
+      'js/src/memory_allocator_reporter.cpp',
       'js/src/perf/jsperf.cpp',
       'js/src/perf/pm_stub.cpp',
       'js/src/proxy/BaseProxyHandler.cpp',
diff --git a/src/third_party/mozjs/js/src/jsapi.cpp b/src/third_party/mozjs/js/src/jsapi.cpp
index f45fb04..033642a 100644
--- a/src/third_party/mozjs/js/src/jsapi.cpp
+++ b/src/third_party/mozjs/js/src/jsapi.cpp
@@ -692,6 +692,11 @@
     suppressGC(0)
 {}
 
+namespace {
+  // Some platforms require atleast a 16-byte alignment for jmp_buf.
+  const std::size_t kDesiredJmpBufAlignment = 16;
+}
+
 JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
   : mainThread(this),
     interrupt(0),
@@ -850,6 +855,12 @@
     decimalSeparator(0),
     numGrouping(0),
 #endif
+    // Note: alignas(16) is too big for some compilers, and thus a more
+    // verbose approach needed to be taken.
+    conservativeGC_memory(reinterpret_cast<js::ConservativeGCData*>(
+                SbMemoryAllocateAligned(kDesiredJmpBufAlignment,
+                                        sizeof(js::ConservativeGCData)))),
+    conservativeGC(*conservativeGC_memory),
     mathCache_(NULL),
     dtoaState(NULL),
     activeCompilations(0),
@@ -964,6 +975,7 @@
 
 JSRuntime::~JSRuntime()
 {
+  SbMemoryDeallocateAligned(conservativeGC_memory);
 #ifdef JS_THREADSAFE
     clearOwnerThread();
 
diff --git a/src/third_party/mozjs/js/src/jscntxt.h b/src/third_party/mozjs/js/src/jscntxt.h
index 9339c07..2713d65 100644
--- a/src/third_party/mozjs/js/src/jscntxt.h
+++ b/src/third_party/mozjs/js/src/jscntxt.h
@@ -1331,7 +1331,8 @@
 
     js::DateTimeInfo    dateTimeInfo;
 
-    js::ConservativeGCData conservativeGC;
+    js::ConservativeGCData* conservativeGC_memory;
+    js::ConservativeGCData& conservativeGC;
 
     /* Pool of maps used during parse/emit. */
     js::frontend::ParseMapPool parseMapPool;
diff --git a/src/third_party/mozjs/js/src/vm/NumericConversions.h b/src/third_party/mozjs/js/src/vm/NumericConversions.h
index 94cddae..a3edb4e 100644
--- a/src/third_party/mozjs/js/src/vm/NumericConversions.h
+++ b/src/third_party/mozjs/js/src/vm/NumericConversions.h
@@ -132,11 +132,10 @@
 inline int32_t
 ToInt32(double d)
 {
-#if defined(__ANDROID__) && defined(__clang__) && \
-    __clang_major__ == 3 && __clang_minor__ == 8
-#define ANDROID_CLANG38 1
+#if defined(__ANDROID__) && defined(__clang__)
+#define ANDROID_CLANG 1
 #endif
-#if defined (__arm__) && defined (__GNUC__) && !defined(ANDROID_CLANG38)
+#if defined (__arm__) && defined (__GNUC__) && !defined(ANDROID_CLANG)
     int32_t i;
     uint32_t    tmp0;
     uint32_t    tmp1;
@@ -260,7 +259,7 @@
 #else
     return detail::ToIntWidth<int32_t>(d);
 #endif
-#undef ANDROID_CLANG38
+#undef ANDROID_CLANG
 }
 
 /* ES5 9.6 (specialized for doubles). */
diff --git a/src/third_party/mozjs/mozjs.gyp b/src/third_party/mozjs/mozjs.gyp
index 2959aad..0de26f2 100644
--- a/src/third_party/mozjs/mozjs.gyp
+++ b/src/third_party/mozjs/mozjs.gyp
@@ -39,6 +39,25 @@
       # them.
       'JS_THREADSAFE',
     ],
+    'msvs_disabled_warnings': [
+      # Level 2, Typename first seen as 'type1', but then seen as 'type2'.
+      4099,
+      # Level 2, Possible loss of data due to type conversion.
+      4244,
+      # Level 3, Possible loss of data due to type conversion from size_t.
+      4267,
+    ],
+    # Unfortunately, there is code that generate warnings in the headers.
+    'direct_dependent_settings': {
+      'msvs_disabled_warnings': [
+        # Level 2, Typename first seen as 'type1', but then seen as 'type2'.
+        4099,
+        # Level 2, Possible loss of data due to type conversion.
+        4244,
+        # Level 3, Possible loss of data due to type conversion from size_t.
+        4267,
+      ],
+    },
     'include_dirs': [
       'cobalt_config/include',
       'js/src',
diff --git a/src/tools/gyp/pylib/gyp/generator/ninja.py b/src/tools/gyp/pylib/gyp/generator/ninja.py
index 6294478..cd5a218 100755
--- a/src/tools/gyp/pylib/gyp/generator/ninja.py
+++ b/src/tools/gyp/pylib/gyp/generator/ninja.py
@@ -12,9 +12,19 @@
 import signal
 import subprocess
 import sys
+
+_COBALT_SRC = os.path.abspath(os.path.join(*([__file__] + 6 * [os.pardir])))
+sys.path.append(os.path.join(_COBALT_SRC, 'cobalt', 'build', 'config'))
+from base import LoadPlatformConfig
+
 import gyp
 import gyp.common
-import gyp.msvs_emulation
+
+# TODO: These should be replaced with calls to the abstract tool chain, when it
+# is implemented on all supported platforms.
+from gyp.msvs_emulation import EncodeRspFileList
+from gyp.msvs_emulation import GenerateEnvironmentFiles
+from gyp.msvs_emulation import MsvsSettings
 import gyp.MSVSUtil as MSVSUtil
 import gyp.xcode_emulation
 
@@ -25,36 +35,36 @@
   import cygpath
 
 generator_default_variables = {
-  'EXECUTABLE_PREFIX': '',
-  'EXECUTABLE_SUFFIX': '',
-  'STATIC_LIB_PREFIX': 'lib',
-  'STATIC_LIB_SUFFIX': '.a',
-  'SHARED_LIB_PREFIX': 'lib',
+    'EXECUTABLE_PREFIX': '',
+    'EXECUTABLE_SUFFIX': '',
+    'STATIC_LIB_PREFIX': 'lib',
+    'STATIC_LIB_SUFFIX': '.a',
+    'SHARED_LIB_PREFIX': 'lib',
 
-  # Gyp expects the following variables to be expandable by the build
-  # system to the appropriate locations.  Ninja prefers paths to be
-  # known at gyp time.  To resolve this, introduce special
-  # variables starting with $! and $| (which begin with a $ so gyp knows it
-  # should be treated specially, but is otherwise an invalid
-  # ninja/shell variable) that are passed to gyp here but expanded
-  # before writing out into the target .ninja files; see
-  # ExpandSpecial.
-  # $! is used for variables that represent a path and that can only appear at
-  # the start of a string, while $| is used for variables that can appear
-  # anywhere in a string.
-  'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR',
-  'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen',
-  'PRODUCT_DIR': '$!PRODUCT_DIR',
-  'CONFIGURATION_NAME': '$|CONFIGURATION_NAME',
+    # Gyp expects the following variables to be expandable by the build
+    # system to the appropriate locations.  Ninja prefers paths to be
+    # known at gyp time.  To resolve this, introduce special
+    # variables starting with $! and $| (which begin with a $ so gyp knows it
+    # should be treated specially, but is otherwise an invalid
+    # ninja/shell variable) that are passed to gyp here but expanded
+    # before writing out into the target .ninja files; see
+    # ExpandSpecial.
+    # $! is used for variables that represent a path and that can only appear at
+    # the start of a string, while $| is used for variables that can appear
+    # anywhere in a string.
+    'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR',
+    'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen',
+    'PRODUCT_DIR': '$!PRODUCT_DIR',
+    'CONFIGURATION_NAME': '$|CONFIGURATION_NAME',
 
-  # Special variables that may be used by gyp 'rule' targets.
-  # We generate definitions for these variables on the fly when processing a
-  # rule.
-  'RULE_INPUT_ROOT': '${root}',
-  'RULE_INPUT_DIRNAME': '${dirname}',
-  'RULE_INPUT_PATH': '${source}',
-  'RULE_INPUT_EXT': '${ext}',
-  'RULE_INPUT_NAME': '${name}',
+    # Special variables that may be used by gyp 'rule' targets.
+    # We generate definitions for these variables on the fly when processing a
+    # rule.
+    'RULE_INPUT_ROOT': '${root}',
+    'RULE_INPUT_DIRNAME': '${dirname}',
+    'RULE_INPUT_PATH': '${source}',
+    'RULE_INPUT_EXT': '${ext}',
+    'RULE_INPUT_NAME': '${name}',
 }
 
 # Placates pylint.
@@ -64,14 +74,13 @@
 
 # TODO: figure out how to not build extra host objects in the non-cross-compile
 # case when this is enabled, and enable unconditionally.
-generator_supports_multiple_toolsets = (
-  os.environ.get('GYP_CROSSCOMPILE') or
-  os.environ.get('AR_host') or
-  os.environ.get('CC_host') or
-  os.environ.get('CXX_host') or
-  os.environ.get('AR_target') or
-  os.environ.get('CC_target') or
-  os.environ.get('CXX_target'))
+generator_supports_multiple_toolsets = (os.environ.get('GYP_CROSSCOMPILE') or
+                                        os.environ.get('AR_host') or
+                                        os.environ.get('CC_host') or
+                                        os.environ.get('CXX_host') or
+                                        os.environ.get('AR_target') or
+                                        os.environ.get('CC_target') or
+                                        os.environ.get('CXX_target'))
 
 is_linux = platform.system() == 'Linux'
 is_windows = platform.system() == 'Windows'
@@ -80,6 +89,18 @@
 sony_flavors = ['ps3', 'ps4']
 windows_host_flavors = microsoft_flavors + sony_flavors
 
+_platform_configs = {}
+
+
+def GetToolchainOrNone(flavor):
+  if not flavor in _platform_configs.keys():
+    _platform_configs[flavor] = LoadPlatformConfig(flavor)
+  toolchain = _platform_configs[flavor].GetToolchain()
+  if toolchain:
+    return toolchain
+  return None
+
+
 def StripPrefix(arg, prefix):
   if arg.startswith(prefix):
     return arg[len(prefix):]
@@ -93,22 +114,17 @@
   # whitelist common OK ones and quote anything else.
   if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
     return arg  # No quoting necessary.
-  if flavor in microsoft_flavors:
-    return gyp.msvs_emulation.QuoteForRspFile(arg)
-  elif flavor in sony_flavors :
+  if GetToolchainOrNone(flavor):
+    return GetToolchainOrNone(flavor).QuoteForRspFile(arg)
+  elif flavor in sony_flavors:
     # Escape double quotes.
     return '"' + arg.replace('\"', '\\\"') + '"'
-  return "'" + arg.replace("'", "'" + '"\'"' + "'")  + "'"
+  return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'"
 
 
 def Define(d, flavor):
   """Takes a preprocessor define and returns a -D parameter that's ninja- and
   shell-escaped."""
-  if flavor in microsoft_flavors:
-    # cl.exe replaces literal # characters with = in preprocesor definitions for
-    # some reason. Octal-encode to work around that.
-    d = d.replace('#', '\\%03o' % ord('#'))
-    return '/D' + gyp.msvs_emulation.QuoteForRspFile(ninja_syntax.escape(d))
 
   return QuoteShellArgument(ninja_syntax.escape('-D' + d), flavor)
 
@@ -151,6 +167,7 @@
   variables only store concrete paths to single files, while methods
   compute derived values like "the last output of the target".
   """
+
   def __init__(self, type):
     # Gyp type ("static_library", etc.) of this target.
     self.type = type
@@ -233,12 +250,20 @@
 #   an output file; the result can be namespaced such that it is unique
 #   to the input file name as well as the output target name.
 
+
 class NinjaWriter:
-  def __init__(self, qualified_target, target_outputs, base_dir, build_dir,
-               output_file, flavor, case_sensitive_filesystem,
+
+  def __init__(self,
+               qualified_target,
+               target_outputs,
+               base_dir,
+               build_dir,
+               output_file,
+               flavor,
+               case_sensitive_filesystem,
                abs_build_dir=None):
-    """
-    base_dir: path from source root to directory containing this gyp file,
+    """base_dir: path from source root to directory containing this gyp file,
+
               by gyp semantics, all input paths are relative to this
     build_dir: path from source root to build output
     abs_build_dir: absolute path to the build directory
@@ -298,8 +323,9 @@
 
   def ExpandRuleVariables(self, path, root, dirname, source, ext, name):
     if self.flavor == 'win':
-      path = self.msvs_settings.ConvertVSMacros(
-          path, config=self.config_name)
+      path = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().ConvertVSMacros(
+              path, config=self.config_name)
     path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root)
     path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'],
                         dirname)
@@ -326,8 +352,8 @@
     if env:
       if self.flavor == 'mac':
         path = gyp.xcode_emulation.ExpandEnvVars(path, env)
-      elif self.flavor in microsoft_flavors:
-        path = gyp.msvs_emulation.ExpandMacros(path, env)
+      elif GetToolchainOrNone(self.flavor):
+        path = GetToolchainOrNone(self.flavor).ExpandEnvVars(path, env)
     if path.startswith('$!'):
       expanded = self.ExpandSpecial(path)
       if self.flavor == 'win':
@@ -336,11 +362,12 @@
         expanded = self.path_module.normpath(expanded)
       return self.GypPathCaseCorrection(expanded)
     if '$|' in path:
-      path =  self.ExpandSpecial(path)
+      path = self.ExpandSpecial(path)
     assert '$' not in path, path
 
     # TODO: this needs a proper fix.
-    is_absolute = path.startswith('C:') or path.startswith('c:') or path.startswith('/')
+    is_absolute = path.startswith('C:') or path.startswith(
+        'c:') or path.startswith('/')
     if not is_absolute:
       path = self.path_module.normpath(os.path.join(self.build_to_base, path))
 
@@ -376,8 +403,8 @@
     path_dir, path_basename = os.path.split(path)
     if qualified:
       path_basename = self.name + '.' + path_basename
-    path = self.path_module.normpath(os.path.join(obj, self.base_dir, path_dir,
-                                         path_basename))
+    path = self.path_module.normpath(
+        os.path.join(obj, self.base_dir, path_dir, path_basename))
 
     return self.GypPathCaseCorrection(path)
 
@@ -412,15 +439,21 @@
         spec.get('standalone_static_library', 0))
 
     self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec)
-    self.xcode_settings = self.msvs_settings = None
+    self.xcode_settings = None
     if self.flavor == 'mac':
       self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec)
-    if (self.flavor in windows_host_flavors
-        and is_windows):
-      self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec,
-                                                           generator_flags)
-      arch = self.msvs_settings.GetArch(config_name)
+    if (self.flavor in windows_host_flavors and is_windows):
+      if self.flavor in sony_flavors:
+        self.msvs_settings = gyp.msvs_emulation.MsvsSettings(
+            spec, generator_flags)
+        arch = self.msvs_settings.GetArch(config_name)
+      else:
+        GetToolchainOrNone(self.flavor).InitCompilerSettings(
+            spec, **{'generator_flags': generator_flags})
+        arch = GetToolchainOrNone(
+            self.flavor).GetCompilerSettings().GetArch(config_name)
       self.ninja.variable('arch', self.win_env[arch])
+      None
 
     # Compute predepends for all rules.
     # actions_depends is the dependencies this target depends on before running
@@ -465,21 +498,36 @@
     sources = spec.get('sources', []) + extra_sources
     if sources:
       pch = None
-      if self.flavor in microsoft_flavors:
-        gyp.msvs_emulation.VerifyMissingSources(
-            sources, self.abs_build_dir, generator_flags, self.GypPathToNinja)
-        pch = gyp.msvs_emulation.PrecompiledHeader(
-            self.msvs_settings, config_name, self.GypPathToNinja,
-            self.GypPathToUniqueOutput, self.obj_ext)
+      if GetToolchainOrNone(self.flavor):
+        GetToolchainOrNone(self.flavor).VerifyMissingSources(
+            sources, **{
+                'build_dir': self.abs_build_dir,
+                'generator_flags': generator_flags,
+                'gyp_path_to_ninja': self.GypPathToNinja
+            })
+        pch = GetToolchainOrNone(self.flavor).GetPrecompiledHeader(
+            **{
+                'settings':
+                    GetToolchainOrNone(self.flavor).GetCompilerSettings(),
+                'config':
+                    config_name,
+                'gyp_path_to_ninja':
+                    self.GypPathToNinja,
+                'gyp_path_to_unique_output':
+                    self.GypPathToUniqueOutput,
+                'obj_ext':
+                    self.obj_ext
+            })
       else:
         pch = gyp.xcode_emulation.MacPrefixHeader(
             self.xcode_settings, self.GypPathToNinja,
             lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang))
-      link_deps = self.WriteSources(
-          config_name, config, sources, compile_depends_stamp, pch, spec)
+      link_deps = self.WriteSources(config_name, config, sources,
+                                    compile_depends_stamp, pch, spec)
       # Some actions/rules output 'sources' that are already object files.
-      link_deps += [self.GypPathToNinja(f)
-          for f in sources if f.endswith(self.obj_ext)]
+      link_deps += [
+          self.GypPathToNinja(f) for f in sources if f.endswith(self.obj_ext)
+      ]
 
     if self.flavor in microsoft_flavors and self.target.type == 'static_library':
       self.target.component_objs = link_deps
@@ -505,31 +553,34 @@
   def _WinIdlRule(self, source, prebuild, outputs):
     """Handle the implicit VS .idl rule for one source file. Fills |outputs|
     with files that are generated."""
-    outdir, output, vars, flags = self.msvs_settings.GetIdlBuildData(
-        source, self.config_name)
+    outdir, output, vars, flags = GetToolchainOrNone(
+        self.flavor).GetCompilerSettings().GetIdlBuildData(
+            source, self.config_name)
     outdir = self.GypPathToNinja(outdir)
+
     def fix_path(path, rel=None):
       path = os.path.join(outdir, path)
       dirname, basename = os.path.split(source)
       root, ext = os.path.splitext(basename)
-      path = self.ExpandRuleVariables(
-          path, root, dirname, source, ext, basename)
+      path = self.ExpandRuleVariables(path, root, dirname, source, ext,
+                                      basename)
       if rel:
         path = os.path.relpath(path, rel)
       return path
+
     vars = [(name, fix_path(value, outdir)) for name, value in vars]
     output = [fix_path(p) for p in output]
     vars.append(('outdir', outdir))
     vars.append(('idlflags', flags))
     input = self.GypPathToNinja(source)
-    self.ninja.build(output, 'idl', input,
-        variables=vars, order_only=prebuild)
+    self.ninja.build(output, 'idl', input, variables=vars, order_only=prebuild)
     outputs.extend(output)
 
   def WriteWinIdlFiles(self, spec, prebuild):
     """Writes rules to match MSVS's implicit idl handling."""
-    assert self.flavor in microsoft_flavors
-    if self.msvs_settings.HasExplicitIdlRules(spec):
+    assert GetToolchainOrNone(self.flavor)
+    if GetToolchainOrNone(
+        self.flavor).GetCompilerSettings().HasExplicitIdlRules(spec):
       return []
     outputs = []
     for source in filter(lambda x: x.endswith('.idl'), spec['sources']):
@@ -586,7 +637,7 @@
     # Actions cd into the base directory.
     env = self.GetSortedXcodeEnv()
     if self.flavor == 'win':
-      env = self.msvs_settings.GetVSMacroEnv(
+      env = GetToolchainOrNone(self.flavor).GetCompilerSettings().GetVSMacroEnv(
           '$!PRODUCT_DIR', config=self.config_name)
     all_outputs = []
     for action in actions:
@@ -594,12 +645,11 @@
       name = '%s_%s' % (action['action_name'],
                         hashlib.md5(self.qualified_target).hexdigest())
       description = self.GenerateDescription('ACTION',
-                                             action.get('message', None),
-                                             name)
+                                             action.get('message', None), name)
       is_cygwin = self.IsCygwinRule(action)
       args = action['action']
-      rule_name, _ = self.WriteNewNinjaRule(name, args, description,
-                                            is_cygwin, env=env)
+      rule_name, _ = self.WriteNewNinjaRule(
+          name, args, description, is_cygwin, env=env)
 
       inputs = [self.GypPathToNinja(i, env) for i in action['inputs']]
       if int(action.get('process_outputs_as_sources', False)):
@@ -609,8 +659,7 @@
       outputs = [self.GypPathToNinja(o, env) for o in action['outputs']]
 
       # Then write out an edge using the rule.
-      self.ninja.build(outputs, rule_name, inputs,
-                       order_only=prebuild)
+      self.ninja.build(outputs, rule_name, inputs, order_only=prebuild)
       all_outputs += outputs
 
       self.ninja.newline()
@@ -661,12 +710,14 @@
         root, ext = os.path.splitext(basename)
 
         # Gather the list of inputs and outputs, expanding $vars if possible.
-        outputs = [self.ExpandRuleVariables(o, root, dirname,
-                                            source, ext, basename)
-                   for o in rule['outputs']]
-        inputs = [self.ExpandRuleVariables(i, root, dirname,
-                                           source, ext, basename)
-                  for i in rule.get('inputs', [])]
+        outputs = [
+            self.ExpandRuleVariables(o, root, dirname, source, ext, basename)
+            for o in rule['outputs']
+        ]
+        inputs = [
+            self.ExpandRuleVariables(i, root, dirname, source, ext, basename)
+            for i in rule.get('inputs', [])
+        ]
 
         if int(rule.get('process_outputs_as_sources', False)):
           extra_sources += outputs
@@ -695,11 +746,14 @@
         inputs = [self.GypPathToNinja(i, env) for i in inputs]
         outputs = [self.GypPathToNinja(o, env) for o in outputs]
         extra_bindings.append(('unique_name',
-            hashlib.md5(outputs[0]).hexdigest()))
-        self.ninja.build(outputs, rule_name, self.GypPathToNinja(source),
-                         implicit=inputs,
-                         order_only=prebuild,
-                         variables=extra_bindings)
+                               hashlib.md5(outputs[0]).hexdigest()))
+        self.ninja.build(
+            outputs,
+            rule_name,
+            self.GypPathToNinja(source),
+            implicit=inputs,
+            order_only=prebuild,
+            variables=extra_bindings)
 
         all_outputs.extend(outputs)
 
@@ -747,16 +801,15 @@
             for f in files:
               src = self.GypPathToNinja(os.path.join(rel_root, f), env)
               common_prefix = os.path.commonprefix([joined_path, root])
-              subdir = root[len(common_prefix)+1:]
+              subdir = root[len(common_prefix) + 1:]
 
               dst = os.path.join(destination, basename, subdir, f)
-              outputs += self.WriteCopy(src, dst,
-                prebuild, env, mac_bundle_depends)
+              outputs += self.WriteCopy(src, dst, prebuild, env,
+                                        mac_bundle_depends)
         else:
           src = self.GypPathToNinja(path, env)
           dst = os.path.join(destination, basename)
-          outputs += self.WriteCopy(src, dst,
-            prebuild, env, mac_bundle_depends)
+          outputs += self.WriteCopy(src, dst, prebuild, env, mac_bundle_depends)
 
     return outputs
 
@@ -765,8 +818,11 @@
     for output, res in gyp.xcode_emulation.GetMacBundleResources(
         self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
         self.xcode_settings, map(self.GypPathToNinja, resources)):
-      self.ninja.build(output, 'mac_tool', res,
-                       variables=[('mactool_cmd', 'copy-bundle-resource')])
+      self.ninja.build(
+          output,
+          'mac_tool',
+          res,
+          variables=[('mactool_cmd', 'copy-bundle-resource')])
       bundle_depends.append(output)
 
   def WriteMacInfoPlist(self, bundle_depends):
@@ -781,15 +837,20 @@
       intermediate_plist = self.GypPathToUniqueOutput(
           os.path.basename(info_plist))
       defines = ' '.join([Define(d, self.flavor) for d in defines])
-      info_plist = self.ninja.build(intermediate_plist, 'infoplist', info_plist,
-                                    variables=[('defines',defines)])
+      info_plist = self.ninja.build(
+          intermediate_plist,
+          'infoplist',
+          info_plist,
+          variables=[('defines', defines)])
 
     env = self.GetSortedXcodeEnv(additional_settings=extra_env)
     env = self.ComputeExportEnvString(env)
 
-    self.ninja.build(out, 'mac_tool', info_plist,
-                     variables=[('mactool_cmd', 'copy-info-plist'),
-                                ('env', env)])
+    self.ninja.build(
+        out,
+        'mac_tool',
+        info_plist,
+        variables=[('mactool_cmd', 'copy-info-plist'), ('env', env)])
     bundle_depends.append(out)
 
   def WriteSources(self, config_name, config, sources, predepends,
@@ -810,16 +871,20 @@
                     self.xcode_settings.GetCflagsObjC(config_name)
       cflags_objcc = ['$cflags_cc'] + \
                      self.xcode_settings.GetCflagsObjCC(config_name)
-    elif self.flavor in microsoft_flavors:
-      cflags = self.msvs_settings.GetCflags(config_name)
-      cflags_c = self.msvs_settings.GetCflagsC(config_name)
-      cflags_cc = self.msvs_settings.GetCflagsCC(config_name)
-      extra_defines = self.msvs_settings.GetComputedDefines(config_name)
+    elif GetToolchainOrNone(self.flavor):
+      cflags = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetCflags(config_name)
+      cflags_c = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetCflagsC(config_name)
+      cflags_cc = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetCflagsCC(config_name)
+      extra_defines = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetDefines(config_name)
       obj = 'obj'
       if self.toolset != 'target':
         obj += '.' + self.toolset
-      pdbpath = os.path.normpath(os.path.join(obj, self.base_dir,
-                                              self.name + '.pdb'))
+      pdbpath = os.path.normpath(
+          os.path.join(obj, self.base_dir, self.name + '.pdb'))
       self.WriteVariableList('pdbname', [pdbpath])
       self.WriteVariableList('pchprefix', [self.name])
     else:
@@ -832,26 +897,36 @@
     cflags_cc_host = config.get('cflags_cc_host', cflags_cc)
 
     defines = config.get('defines', []) + extra_defines
-    self.WriteVariableList('defines', [Define(d, self.flavor) for d in defines])
-    if self.flavor in microsoft_flavors:
-      self.WriteVariableList('rcflags',
-          [QuoteShellArgument(self.ExpandSpecial(f), self.flavor)
-           for f in self.msvs_settings.GetRcflags(config_name,
-                                                  self.GypPathToNinja)])
+    if GetToolchainOrNone(self.flavor):
+      self.WriteVariableList('defines', [
+          GetToolchainOrNone(self.flavor).Define(d) for d in defines
+      ])
+    else:
+      self.WriteVariableList('defines',
+                             [Define(d, self.flavor) for d in defines])
+    if GetToolchainOrNone(self.flavor):
+      self.WriteVariableList('rcflags', [
+          QuoteShellArgument(self.ExpandSpecial(f), self.flavor)
+          for f in GetToolchainOrNone(self.flavor).GetCompilerSettings()
+          .GetRcFlags(config_name, self.GypPathToNinja)
+      ])
 
     include_dirs = config.get('include_dirs', [])
     include_dirs += config.get('include_dirs_' + self.toolset, [])
 
-    if self.flavor in microsoft_flavors:
-      include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs,
-                                                          config_name)
-      self.WriteVariableList('includes',
-        ['/I' + gyp.msvs_emulation.QuoteForRspFile(self.GypPathToNinja(i))
-         for i in include_dirs])
+    if GetToolchainOrNone(self.flavor):
+      include_dirs = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().ProcessIncludeDirs(
+              include_dirs, config_name)
+      self.WriteVariableList('includes', [
+          '/I' + GetToolchainOrNone(self.flavor).QuoteForRspFile(
+              self.GypPathToNinja(i)) for i in include_dirs
+      ])
     else:
-      self.WriteVariableList('includes',
-          [QuoteShellArgument('-I' + self.GypPathToNinja(i), self.flavor)
-           for i in include_dirs])
+      self.WriteVariableList('includes', [
+          QuoteShellArgument('-I' + self.GypPathToNinja(i), self.flavor)
+          for i in include_dirs
+      ])
 
     pch_commands = precompiled_header.GetPchBuildCommands()
     if self.flavor == 'mac':
@@ -869,14 +944,16 @@
     self.WriteVariableList('cflags_cc', map(self.ExpandSpecial, cflags_cc))
 
     self.WriteVariableList('cflags_host', map(self.ExpandSpecial, cflags_host))
-    self.WriteVariableList('cflags_c_host', map(self.ExpandSpecial, cflags_c_host))
-    self.WriteVariableList('cflags_cc_host', map(self.ExpandSpecial, cflags_cc_host))
+    self.WriteVariableList('cflags_c_host',
+                           map(self.ExpandSpecial, cflags_c_host))
+    self.WriteVariableList('cflags_cc_host',
+                           map(self.ExpandSpecial, cflags_cc_host))
 
     if self.flavor == 'mac':
       self.WriteVariableList('cflags_objc', map(self.ExpandSpecial,
                                                 cflags_objc))
-      self.WriteVariableList('cflags_objcc', map(self.ExpandSpecial,
-                                                 cflags_objcc))
+      self.WriteVariableList('cflags_objcc',
+                             map(self.ExpandSpecial, cflags_objcc))
     self.ninja.newline()
     outputs = []
     for source in sources:
@@ -889,9 +966,10 @@
         command = 'cc'
       elif ext == 's' and self.flavor != 'win':  # Doesn't generate .o.d files.
         command = 'cc_s'
-      elif (self.flavor == 'win' and ext == 'asm' and
-            self.msvs_settings.GetArch(config_name) == 'x86' and
-            not self.msvs_settings.HasExplicitAsmRules(spec)):
+      elif (self.flavor == 'win' and ext == 'asm' and GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetArch(config_name) == 'x86' and
+            not GetToolchainOrNone(
+                self.flavor).GetCompilerSettings().HasExplicitAsmRules(spec)):
         # Asm files only get auto assembled for x86 (not x64).
         command = 'asm'
         # Add the _asm suffix as msvs is capable of handling .cc and
@@ -902,6 +980,7 @@
       elif self.flavor == 'mac' and ext == 'mm':
         command = 'objcxx'
       elif self.flavor in microsoft_flavors and ext == 'rc':
+        # TODO: Starboardize this.
         command = 'rc'
         obj_ext = '.res'
       else:
@@ -914,13 +993,17 @@
       output = self.GypPathToUniqueOutput(filename + obj_ext)
       implicit = precompiled_header.GetObjDependencies([input], [output])
       variables = []
-      if self.flavor in microsoft_flavors:
+      if GetToolchainOrNone(self.flavor):
         variables, output, implicit = precompiled_header.GetFlagsModifications(
             input, output, implicit, command, cflags_c, cflags_cc,
             self.ExpandSpecial)
-      self.ninja.build(output, command, input,
-                       implicit=[gch for _, _, gch in implicit],
-                       order_only=predepends, variables=variables)
+      self.ninja.build(
+          output,
+          command,
+          input,
+          implicit=[gch for _, _, gch in implicit],
+          order_only=predepends,
+          variables=variables)
       outputs.append(output)
 
     self.WritePchTargets(pch_commands)
@@ -935,13 +1018,18 @@
 
     for gch, lang_flag, lang, input in pch_commands:
       var_name = {
-        'c': 'cflags_pch_c',
-        'cc': 'cflags_pch_cc',
-        'm': 'cflags_pch_objc',
-        'mm': 'cflags_pch_objcc',
+          'c': 'cflags_pch_c',
+          'cc': 'cflags_pch_cc',
+          'm': 'cflags_pch_objc',
+          'mm': 'cflags_pch_objcc',
       }[lang]
 
-      map = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', }
+      map = {
+          'c': 'cc',
+          'cc': 'cxx',
+          'm': 'objc',
+          'mm': 'objcxx',
+      }
       cmd = map.get(lang)
       self.ninja.build(gch, cmd, input, variables=[(var_name, lang_flag)])
 
@@ -949,9 +1037,9 @@
     """Write out a link step. Fills out target.binary. """
 
     command = {
-      'executable':      'link',
-      'loadable_module': 'solink_module',
-      'shared_library':  'solink',
+        'executable': 'link',
+        'loadable_module': 'solink_module',
+        'shared_library': 'solink',
     }[spec['type']]
 
     implicit_deps = set()
@@ -969,9 +1057,11 @@
         if not target:
           continue
         linkable = target.Linkable()
+        # TODO: Starboardize.
         if linkable:
           if (self.flavor in microsoft_flavors and target.component_objs and
-              self.msvs_settings.IsUseLibraryDependencyInputs(config_name)):
+              GetToolchainOrNone(self.flavor).GetCompilerSettings()
+              .IsUseLibraryDependencyInputs(config_name)):
             extra_link_deps.extend(target.component_objs)
           elif (self.flavor in (microsoft_flavors + ['ps3']) and
                 target.import_lib):
@@ -988,7 +1078,9 @@
 
       # dedup the extra link deps while preserving order
       seen = set()
-      extra_link_deps = [ x for x in extra_link_deps if x not in seen and not seen.add(x) ]
+      extra_link_deps = [
+          x for x in extra_link_deps if x not in seen and not seen.add(x)
+      ]
 
       link_deps.extend(extra_link_deps)
 
@@ -997,35 +1089,41 @@
       output = self.ComputeMacBundleBinaryOutput()
     else:
       output = self.ComputeOutput(spec)
-      extra_bindings.append(('postbuilds',
-                             self.GetPostbuildCommand(spec, output, output)))
+      extra_bindings.append(('postbuilds', self.GetPostbuildCommand(
+          spec, output, output)))
 
     if self.flavor == 'mac':
-      ldflags = self.xcode_settings.GetLdflags(config_name,
+      ldflags = self.xcode_settings.GetLdflags(
+          config_name,
           self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
           self.GypPathToNinja)
-    elif self.flavor in microsoft_flavors:
-      libflags = self.msvs_settings.GetLibFlags(config_name,
-                                                self.GypPathToNinja)
+    elif GetToolchainOrNone(self.flavor):
+      libflags = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetLibFlags(
+              config_name, self.GypPathToNinja)
       self.WriteVariableList(
           'libflags', gyp.common.uniquer(map(self.ExpandSpecial, libflags)))
       is_executable = spec['type'] == 'executable'
       manifest_name = self.GypPathToUniqueOutput(
           self.ComputeOutputFileName(spec))
-      ldflags, manifest_files = self.msvs_settings.GetLdflags(config_name,
-          self.GypPathToNinja, self.ExpandSpecial, manifest_name, is_executable)
+      ldflags, manifest_files = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetLdFlags(config_name, **{
+              'gyp_path_to_ninja': self.GypPathToNinja,
+              'expand_special': self.ExpandSpecial,
+              'manifest_name': manifest_name,
+              'is_executable': is_executable
+          })
       self.WriteVariableList('manifests', manifest_files)
     else:
       ldflags = config.get('ldflags', [])
       ldflags_host = config.get('ldflags_host', ldflags)
 
     self.WriteVariableList('ldflags',
-                           gyp.common.uniquer(map(self.ExpandSpecial,
-                                                  ldflags)))
+                           gyp.common.uniquer(map(self.ExpandSpecial, ldflags)))
     if ('ldflags_host' in locals()):
-      self.WriteVariableList('ldflags_host',
-                             gyp.common.uniquer(map(self.ExpandSpecial,
-                                                    ldflags_host)))
+      self.WriteVariableList(
+          'ldflags_host',
+          gyp.common.uniquer(map(self.ExpandSpecial, ldflags_host)))
 
     if self.toolset == 'host':
       libs = spec.get('libraries_host', [])
@@ -1038,8 +1136,9 @@
 
     if self.flavor == 'mac':
       libraries = self.xcode_settings.AdjustLibraries(libraries)
-    elif self.flavor in microsoft_flavors:
-      libraries = self.msvs_settings.AdjustLibraries(libraries)
+    elif GetToolchainOrNone(self.flavor):
+      libraries = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().ProcessLibraries(libraries)
     self.WriteVariableList('libs', libraries)
 
     self.target.binary = output
@@ -1047,7 +1146,8 @@
     if command in ('solink', 'solink_module'):
       extra_bindings.append(('soname', os.path.split(output)[1]))
       extra_bindings.append(('lib',
-                            gyp.common.EncodePOSIXShellArgument(output)))
+                             gyp.common.EncodePOSIXShellArgument(output)))
+      # TODO: Starboardize.
       if self.flavor in microsoft_flavors:
         extra_bindings.append(('dll', output))
         if '/NOENTRY' not in ldflags:
@@ -1077,17 +1177,19 @@
         # with an "export pickup" step that runs over the object files
         # and produces a new .c file. That .c file should be compiled and linked
         # into the PRX.
-        gen_files_dir = os.path.join(self.ExpandSpecial(
-            generator_default_variables['SHARED_INTERMEDIATE_DIR']), 'prx')
+        gen_files_dir = os.path.join(
+            self.ExpandSpecial(
+                generator_default_variables['SHARED_INTERMEDIATE_DIR']), 'prx')
 
         export_pickup_output = os.path.join(
             gen_files_dir, os.path.basename(prx_output_base) + '.prx_export.c')
         prx_export_obj_file = export_pickup_output[:-2] + '.o'
-        self.ninja.build(export_pickup_output,
-                         'prx_export_pickup',
-                         link_deps,
-                         implicit=list(implicit_deps),
-                         order_only=list(order_only_deps))
+        self.ninja.build(
+            export_pickup_output,
+            'prx_export_pickup',
+            link_deps,
+            implicit=list(implicit_deps),
+            order_only=list(order_only_deps))
 
         self.ninja.build(prx_export_obj_file, 'cc', export_pickup_output)
         link_deps.append(prx_export_obj_file)
@@ -1101,10 +1203,13 @@
     if self.toolset != 'target':
       command += '_' + self.toolset
 
-    self.ninja.build(output, command, link_deps,
-                     implicit=list(implicit_deps),
-                     order_only=list(order_only_deps),
-                     variables=extra_bindings)
+    self.ninja.build(
+        output,
+        command,
+        link_deps,
+        implicit=list(implicit_deps),
+        order_only=list(order_only_deps),
+        variables=extra_bindings)
 
   def WriteTarget(self, spec, config_name, config, link_deps, compile_deps):
     if spec['type'] == 'none':
@@ -1114,26 +1219,33 @@
     elif spec['type'] == 'static_library':
       self.target.binary = self.ComputeOutput(spec)
       variables = []
-      if self.flavor in microsoft_flavors:
-        libflags = self.msvs_settings.GetLibFlags(config_name,
-                                                  self.GypPathToNinja)
+      if GetToolchainOrNone(self.flavor):
+        libflags = GetToolchainOrNone(
+            self.flavor).GetCompilerSettings().GetLibFlags(
+                config_name, self.GypPathToNinja)
+        # TODO: Starboardize libflags vs libtool_flags.
         variables.append(('libflags', ' '.join(libflags)))
-      postbuild = self.GetPostbuildCommand(
-          spec, self.target.binary, self.target.binary)
+      postbuild = self.GetPostbuildCommand(spec, self.target.binary,
+                                           self.target.binary)
       if postbuild:
         variables.append(('postbuilds', postbuild))
       if self.xcode_settings:
         variables.append(('libtool_flags',
                           self.xcode_settings.GetLibtoolflags(config_name)))
-      if (self.flavor not in (['mac'] + microsoft_flavors) and not
-          self.is_standalone_static_library):
+      # TODO: Starboardize.
+      if (self.flavor not in (['mac'] + microsoft_flavors) and
+          not self.is_standalone_static_library):
         command = 'alink_thin'
       else:
         command = 'alink'
       if self.toolset != 'target':
         command += '_' + self.toolset
-      self.ninja.build(self.target.binary, command, link_deps,
-                       order_only=compile_deps, variables=variables)
+      self.ninja.build(
+          self.target.binary,
+          command,
+          link_deps,
+          order_only=compile_deps,
+          variables=variables)
     else:
       self.WriteLink(spec, config_name, config, link_deps)
     return self.target.binary
@@ -1142,18 +1254,20 @@
     assert self.is_mac_bundle
     package_framework = spec['type'] in ('shared_library', 'loadable_module')
     output = self.ComputeMacBundleOutput()
-    postbuild = self.GetPostbuildCommand(spec, output, self.target.binary,
-                                         is_command_start=not package_framework)
+    postbuild = self.GetPostbuildCommand(
+        spec,
+        output,
+        self.target.binary,
+        is_command_start=not package_framework)
     variables = []
     if postbuild:
       variables.append(('postbuilds', postbuild))
     if package_framework:
       variables.append(('version', self.xcode_settings.GetFrameworkVersion()))
-      self.ninja.build(output, 'package_framework', mac_bundle_depends,
-                       variables=variables)
+      self.ninja.build(
+          output, 'package_framework', mac_bundle_depends, variables=variables)
     else:
-      self.ninja.build(output, 'stamp', mac_bundle_depends,
-                       variables=variables)
+      self.ninja.build(output, 'stamp', mac_bundle_depends, variables=variables)
     self.target.bundle = output
     return output
 
@@ -1163,8 +1277,8 @@
     abs_build_dir = self.abs_build_dir
     return gyp.xcode_emulation.GetSortedXcodeEnv(
         self.xcode_settings, abs_build_dir,
-        os.path.join(abs_build_dir, self.build_to_base), self.config_name,
-        additional_settings)
+        os.path.join(abs_build_dir,
+                     self.build_to_base), self.config_name, additional_settings)
 
   def GetSortedXcodePostbuildEnv(self):
     """Returns the variables Xcode would set for postbuild steps."""
@@ -1177,7 +1291,10 @@
       postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = strip_save_file
     return self.GetSortedXcodeEnv(additional_settings=postbuild_settings)
 
-  def GetPostbuildCommand(self, spec, output, output_binary,
+  def GetPostbuildCommand(self,
+                          spec,
+                          output,
+                          output_binary,
                           is_command_start=False):
     """Returns a shell command that runs all the postbuilds, and removes
     |output| if any of them fails. If |is_command_start| is False, then the
@@ -1198,17 +1315,19 @@
       return ''
     # Postbuilds expect to be run in the gyp file's directory, so insert an
     # implicit postbuild to cd to there.
-    postbuilds.insert(0, gyp.common.EncodePOSIXShellList(
-        ['cd', self.build_to_base]))
+    postbuilds.insert(0,
+                      gyp.common.EncodePOSIXShellList(
+                          ['cd', self.build_to_base]))
     env = self.ComputeExportEnvString(self.GetSortedXcodePostbuildEnv())
     # G will be non-null if any postbuild fails. Run all postbuilds in a
     # subshell.
     commands = env + ' (F=0; ' + \
         ' '.join([ninja_syntax.escape(command) + ' || F=$$?;'
                                  for command in postbuilds])
-    command_string = (commands + ' exit $$F); G=$$?; '
-                      # Remove the final output if any postbuild failed.
-                      '((exit $$G) || rm -rf %s) ' % output + '&& exit $$G)')
+    command_string = (
+        commands + ' exit $$F); G=$$?; '
+        # Remove the final output if any postbuild failed.
+        '((exit $$G) || rm -rf %s) ' % output + '&& exit $$G)')
     if is_command_start:
       return '(' + command_string + ' && '
     else:
@@ -1220,7 +1339,8 @@
     that exports |env| to the shell."""
     export_str = []
     for k, v in env:
-      export_str.append('export %s=%s;' %
+      export_str.append(
+          'export %s=%s;' %
           (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(v))))
     return ' '.join(export_str)
 
@@ -1242,16 +1362,20 @@
       type = spec['type']
 
     default_variables = copy.copy(generator_default_variables)
-    CalculateVariables(default_variables, {'flavor': self.flavor})
+    if GetToolchainOrNone(self.flavor):
+      GetToolchainOrNone(
+          self.flavor).SetAdditionalGypVariables(default_variables)
+    else:
+      CalculateVariables(default_variables, {'flavor': self.flavor})
 
     # Compute filename prefix: the product prefix, or a default for
     # the product type.
     DEFAULT_PREFIX = {
-      'loadable_module': default_variables['SHARED_LIB_PREFIX'],
-      'shared_library': default_variables['SHARED_LIB_PREFIX'],
-      'static_library': default_variables['STATIC_LIB_PREFIX'],
-      'executable': default_variables['EXECUTABLE_PREFIX'],
-      }
+        'loadable_module': default_variables['SHARED_LIB_PREFIX'],
+        'shared_library': default_variables['SHARED_LIB_PREFIX'],
+        'static_library': default_variables['STATIC_LIB_PREFIX'],
+        'executable': default_variables['EXECUTABLE_PREFIX'],
+    }
     prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
 
     # Compute filename extension: the product extension, or a default
@@ -1261,7 +1385,7 @@
         'shared_library': default_variables['SHARED_LIB_SUFFIX'],
         'static_library': default_variables['STATIC_LIB_SUFFIX'],
         'executable': default_variables['EXECUTABLE_SUFFIX'],
-      }
+    }
     extension = spec.get('product_extension')
     if extension:
       extension = '.' + extension
@@ -1284,7 +1408,7 @@
         target = StripPrefix(target, 'lib')
 
     if type in ('static_library', 'loadable_module', 'shared_library',
-                        'executable'):
+                'executable'):
       return '%s%s%s' % (prefix, target, extension)
     elif type == 'none':
       return '%s.stamp' % target
@@ -1299,13 +1423,14 @@
       type = spec['type']
 
     if self.flavor == 'win':
-      override = self.msvs_settings.GetOutputName(self.config_name,
-                                                  self.ExpandSpecial)
+      override = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().GetOutputName(
+              self.config_name, self.ExpandSpecial)
       if override:
         return override
 
-    if self.flavor == 'mac' and type in (
-        'static_library', 'executable', 'shared_library', 'loadable_module'):
+    if self.flavor == 'mac' and type in ('static_library', 'executable',
+                                         'shared_library', 'loadable_module'):
       filename = self.xcode_settings.GetExecutablePath()
     else:
       filename = self.ComputeOutputFileName(spec, type)
@@ -1345,11 +1470,13 @@
     expanded."""
 
     if self.flavor == 'win':
-      args = [self.msvs_settings.ConvertVSMacros(
-                  arg, self.base_to_build, config=self.config_name)
-              for arg in args]
-      description = self.msvs_settings.ConvertVSMacros(
-          description, config=self.config_name)
+      args = [
+          GetToolchainOrNone(self.flavor).GetCompilerSettings().ConvertVSMacros(
+              arg, self.base_to_build, config=self.config_name) for arg in args
+      ]
+      description = GetToolchainOrNone(
+          self.flavor).GetCompilerSettings().ConvertVSMacros(
+              description, config=self.config_name)
     elif self.flavor == 'mac':
       # |env| is an empty list on non-mac.
       args = [gyp.xcode_emulation.ExpandEnvVars(arg, env) for arg in args]
@@ -1367,7 +1494,7 @@
     # Remove variable references, but not if they refer to the magic rule
     # variables.  This is not quite right, as it also protects these for
     # actions, not just for rules where they are valid. Good enough.
-    protect = [ '${root}', '${dirname}', '${source}', '${ext}', '${name}' ]
+    protect = ['${root}', '${dirname}', '${source}', '${ext}', '${name}']
     protect = '(?!' + '|'.join(map(re.escape, protect)) + ')'
     description = re.sub(protect + r'\$', '_', description)
 
@@ -1377,16 +1504,18 @@
     rspfile = None
     rspfile_content = None
     args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args]
-    if (self.flavor in windows_host_flavors
-        and is_windows):
+    if (self.flavor in windows_host_flavors and is_windows):
       rspfile = rule_name + '.$unique_name.rsp'
       # The cygwin case handles this inside the bash sub-shell.
       run_in = '' if is_cygwin else ' ' + self.build_to_base
       if is_cygwin:
         rspfile_content = self.msvs_settings.BuildCygwinBashCommandLine(
             args, self.build_to_base)
-      else:
+      elif self.flavor in sony_flavors:
         rspfile_content = gyp.msvs_emulation.EncodeRspFileList(args)
+      else:
+        rspfile_content = GetToolchainOrNone(
+            self.flavor).EncodeRspFileList(args)
 
       command = ('%s gyp-win-tool action-wrapper $arch ' % sys.executable +
                  rspfile + run_in)
@@ -1398,8 +1527,13 @@
     # GYP rules/actions express being no-ops by not touching their outputs.
     # Avoid executing downstream dependencies in this case by specifying
     # restat=1 to ninja.
-    self.ninja.rule(rule_name, command, description, restat=True,
-                    rspfile=rspfile, rspfile_content=rspfile_content)
+    self.ninja.rule(
+        rule_name,
+        command,
+        description,
+        restat=True,
+        rspfile=rspfile,
+        rspfile_content=rspfile_content)
     self.ninja.newline()
 
     return rule_name, args
@@ -1421,51 +1555,21 @@
     # Copy additional generator configuration data from Xcode, which is shared
     # by the Mac Ninja generator.
     import gyp.generator.xcode as xcode_generator
-    generator_additional_non_configuration_keys = getattr(xcode_generator,
-        'generator_additional_non_configuration_keys', [])
-    generator_additional_path_sections = getattr(xcode_generator,
-        'generator_additional_path_sections', [])
+    generator_additional_non_configuration_keys = getattr(
+        xcode_generator, 'generator_additional_non_configuration_keys', [])
+    generator_additional_path_sections = getattr(
+        xcode_generator, 'generator_additional_path_sections', [])
     global generator_extra_sources_for_rules
-    generator_extra_sources_for_rules = getattr(xcode_generator,
-        'generator_extra_sources_for_rules', [])
-  elif flavor in microsoft_flavors:
-    default_variables.setdefault('OS', 'win')
-    default_variables['EXECUTABLE_SUFFIX'] = '.exe'
-    default_variables['STATIC_LIB_PREFIX'] = ''
-    default_variables['STATIC_LIB_SUFFIX'] = '.lib'
-    default_variables['SHARED_LIB_PREFIX'] = ''
-    default_variables['SHARED_LIB_SUFFIX'] = '.dll'
-    generator_flags = params.get('generator_flags', {})
-
-    # Copy additional generator configuration data from VS, which is shared
-    # by the Windows Ninja generator.
-    import gyp.generator.msvs as msvs_generator
-    generator_additional_non_configuration_keys = getattr(msvs_generator,
-        'generator_additional_non_configuration_keys', [])
-    generator_additional_path_sections = getattr(msvs_generator,
-        'generator_additional_path_sections', [])
-
-    # Set a variable so conditions can be based on msvs_version.
-    msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
-    default_variables['MSVS_VERSION'] = msvs_version.ShortName()
-
-    # To determine processor word size on Windows, in addition to checking
-    # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
-    # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
-    # contains the actual word size of the system when running thru WOW64).
-    if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
-        '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
-      default_variables['MSVS_OS_BITS'] = 64
-    else:
-      default_variables['MSVS_OS_BITS'] = 32
+    generator_extra_sources_for_rules = getattr(
+        xcode_generator, 'generator_extra_sources_for_rules', [])
   elif flavor in ['ps3']:
     if is_windows:
       # This is required for BuildCygwinBashCommandLine() to work.
       import gyp.generator.msvs as msvs_generator
-      generator_additional_non_configuration_keys = getattr(msvs_generator,
-          'generator_additional_non_configuration_keys', [])
-      generator_additional_path_sections = getattr(msvs_generator,
-          'generator_additional_path_sections', [])
+      generator_additional_non_configuration_keys = getattr(
+          msvs_generator, 'generator_additional_non_configuration_keys', [])
+      generator_additional_path_sections = getattr(
+          msvs_generator, 'generator_additional_path_sections', [])
 
     default_variables['SHARED_LIB_PREFIX'] = ''
     default_variables['SHARED_LIB_SUFFIX'] = '.sprx'
@@ -1475,10 +1579,10 @@
     if is_windows:
       # This is required for BuildCygwinBashCommandLine() to work.
       import gyp.generator.msvs as msvs_generator
-      generator_additional_non_configuration_keys = getattr(msvs_generator,
-          'generator_additional_non_configuration_keys', [])
-      generator_additional_path_sections = getattr(msvs_generator,
-          'generator_additional_path_sections', [])
+      generator_additional_non_configuration_keys = getattr(
+          msvs_generator, 'generator_additional_non_configuration_keys', [])
+      generator_additional_path_sections = getattr(
+          msvs_generator, 'generator_additional_path_sections', [])
 
     default_variables['EXECUTABLE_SUFFIX'] = '.elf'
     default_variables['SHARED_LIB_PREFIX'] = 'lib'
@@ -1487,10 +1591,10 @@
     # Copy additional generator configuration data from VS, which is shared
     # by the Windows Ninja generator.
     import gyp.generator.msvs as msvs_generator
-    generator_additional_non_configuration_keys = getattr(msvs_generator,
-        'generator_additional_non_configuration_keys', [])
-    generator_additional_path_sections = getattr(msvs_generator,
-        'generator_additional_path_sections', [])
+    generator_additional_non_configuration_keys = getattr(
+        msvs_generator, 'generator_additional_non_configuration_keys', [])
+    generator_additional_path_sections = getattr(
+        msvs_generator, 'generator_additional_path_sections', [])
   else:
     operating_system = flavor
     if flavor == 'android':
@@ -1499,8 +1603,8 @@
     default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
     default_variables.setdefault('SHARED_LIB_DIR',
                                  os.path.join('$!PRODUCT_DIR', 'lib'))
-    default_variables.setdefault('LIB_DIR',
-                                 os.path.join('$!PRODUCT_DIR', 'obj'))
+    default_variables.setdefault('LIB_DIR', os.path.join(
+        '$!PRODUCT_DIR', 'obj'))
 
 
 def OpenOutput(path, mode='w'):
@@ -1523,15 +1627,15 @@
 
     class MEMORYSTATUSEX(ctypes.Structure):
       _fields_ = [
-        ("dwLength", ctypes.c_ulong),
-        ("dwMemoryLoad", ctypes.c_ulong),
-        ("ullTotalPhys", ctypes.c_ulonglong),
-        ("ullAvailPhys", ctypes.c_ulonglong),
-        ("ullTotalPageFile", ctypes.c_ulonglong),
-        ("ullAvailPageFile", ctypes.c_ulonglong),
-        ("ullTotalVirtual", ctypes.c_ulonglong),
-        ("ullAvailVirtual", ctypes.c_ulonglong),
-        ("sullAvailExtendedVirtual", ctypes.c_ulonglong),
+          ('dwLength', ctypes.c_ulong),
+          ('dwMemoryLoad', ctypes.c_ulong),
+          ('ullTotalPhys', ctypes.c_ulonglong),
+          ('ullAvailPhys', ctypes.c_ulonglong),
+          ('ullTotalPageFile', ctypes.c_ulonglong),
+          ('ullAvailPageFile', ctypes.c_ulonglong),
+          ('ullTotalVirtual', ctypes.c_ulonglong),
+          ('ullAvailVirtual', ctypes.c_ulonglong),
+          ('sullAvailExtendedVirtual', ctypes.c_ulonglong),
       ]
 
     stat = MEMORYSTATUSEX()
@@ -1540,26 +1644,26 @@
 
     # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
     # on a 64 GB machine.
-    mem_limit = max(1, stat.ullTotalPhys / (5 * (2 ** 30)))  # total / 5GB
+    mem_limit = max(1, stat.ullTotalPhys / (5 * (2**30)))  # total / 5GB
     hard_cap = max(1, int(os.getenv('GYP_LINK_CONCURRENCY_MAX', 2**32)))
     return min(mem_limit, hard_cap)
   elif sys.platform.startswith('linux'):
-    if os.path.exists("/proc/meminfo"):
-      with open("/proc/meminfo") as meminfo:
+    if os.path.exists('/proc/meminfo'):
+      with open('/proc/meminfo') as meminfo:
         memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
         for line in meminfo:
           match = memtotal_re.match(line)
           if not match:
             continue
           # Allow 6Gb per link on Linux because Gold is quite memory hungry
-          return max(1, int(match.group(1)) / (6 * (2 ** 20)))
+          return max(1, int(match.group(1)) / (6 * (2**20)))
     return 1
   elif sys.platform == 'darwin':
     try:
       avail_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
       # A static library debug build of Chromium's unit_tests takes ~2.7GB, so
       # 4GB per ld process allows for some more bloat.
-      return max(1, avail_bytes / (4 * (2 ** 30)))  # total / 4GB
+      return max(1, avail_bytes / (4 * (2**30)))  # total / 4GB
     except:
       return 1
   else:
@@ -1583,15 +1687,13 @@
 
   # build_dir: relative path from source root to our output files.
   # e.g. "out/Debug"
-  build_dir = os.path.normpath(os.path.join(generator_dir,
-                                            output_dir,
-                                            config_name))
+  build_dir = os.path.normpath(
+      os.path.join(generator_dir, output_dir, config_name))
 
   toplevel_build = os.path.join(options.toplevel_dir, build_dir)
 
   master_ninja = ninja_syntax.Writer(
-      OpenOutput(os.path.join(toplevel_build, 'build.ninja')),
-      width=120)
+      OpenOutput(os.path.join(toplevel_build, 'build.ninja')), width=120)
   case_sensitive_filesystem = True
 
   # Put build-time support tools in out/{config_name}.
@@ -1608,14 +1710,15 @@
     cc = 'cl.exe'
     cxx = 'cl.exe'
     ld = 'link.exe'
-    gyp.msvs_emulation.GenerateEnvironmentFiles(
-        toplevel_build, generator_flags, OpenOutput)
+    gyp.msvs_emulation.GenerateEnvironmentFiles(toplevel_build, generator_flags,
+                                                OpenOutput)
     ld_host = '$ld'
-  elif flavor in microsoft_flavors:
+  elif GetToolchainOrNone(flavor):
+    # TODO: starboardize.
     cc = 'cl.exe'
     cxx = 'cl.exe'
     ld = 'link.exe'
-    gyp.msvs_emulation.GenerateXB1EnvironmentFiles(
+    GetToolchainOrNone(flavor).GenerateEnvironmentFiles(
         toplevel_build, generator_flags, OpenOutput)
     ld_host = '$ld'
   else:
@@ -1682,7 +1785,8 @@
     master_ninja.variable('ld', os.environ.get('LD'))
     master_ninja.variable('ar', os.environ.get('AR', 'ar'))
     if flavor in ['ps3']:
-      master_ninja.variable('prx_export_pickup', os.environ['PRX_EXPORT_PICKUP'])
+      master_ninja.variable('prx_export_pickup',
+                            os.environ['PRX_EXPORT_PICKUP'])
     ar_flags = os.environ.get('ARFLAGS', 'rcs')
     master_ninja.variable('arFlags', ar_flags)
     # On the PS3, when we use ps3snarl.exe with a response file, we cannot
@@ -1734,51 +1838,51 @@
   master_ninja.newline()
 
   if flavor not in microsoft_flavors:
-    if flavor in sony_flavors :
+    if flavor in sony_flavors:
       # uca := Unnamed Console A
       dep_format = 'snc' if (flavor in ['ps3']) else 'uca'
       master_ninja.rule(
-        'cc',
-        description='CC $out',
-        command=('$cc @$out.rsp'),
-        rspfile='$out.rsp',
-        rspfile_content=('-c $in -o $out '
-                         '-MMD $defines $includes $cflags $cflags_c '
-                        '$cflags_pch_c'),
-        depfile='$out_no_ext.d',
-        deps='gcc',
-        depformat=dep_format)
+          'cc',
+          description='CC $out',
+          command=('$cc @$out.rsp'),
+          rspfile='$out.rsp',
+          rspfile_content=('-c $in -o $out '
+                           '-MMD $defines $includes $cflags $cflags_c '
+                           '$cflags_pch_c'),
+          depfile='$out_no_ext.d',
+          deps='gcc',
+          depformat=dep_format)
       master_ninja.rule(
-        'cxx',
-        description='CXX $out',
-        command=('$cxx @$out.rsp'),
-        rspfile='$out.rsp',
-        rspfile_content=('-c $in -o $out '
-                         '-MMD $defines $includes $cflags $cflags_cc '
-                        '$cflags_pch_cc'),
-        depfile='$out_no_ext.d',
-        deps='gcc',
-        depformat=dep_format)
+          'cxx',
+          description='CXX $out',
+          command=('$cxx @$out.rsp'),
+          rspfile='$out.rsp',
+          rspfile_content=('-c $in -o $out '
+                           '-MMD $defines $includes $cflags $cflags_cc '
+                           '$cflags_pch_cc'),
+          depfile='$out_no_ext.d',
+          deps='gcc',
+          depformat=dep_format)
     else:
       master_ninja.rule(
-        'cc',
-        description='CC $out',
-        command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c '
-                '$cflags_pch_c -c $in -o $out'),
-        deps='gcc',
-        depfile='$out.d')
+          'cc',
+          description='CC $out',
+          command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c '
+                   '$cflags_pch_c -c $in -o $out'),
+          deps='gcc',
+          depfile='$out.d')
       master_ninja.rule(
-        'cc_s',
-        description='CC $out',
-        command=('$cc $defines $includes $cflags $cflags_c '
-                '$cflags_pch_c -c $in -o $out'))
+          'cc_s',
+          description='CC $out',
+          command=('$cc $defines $includes $cflags $cflags_c '
+                   '$cflags_pch_c -c $in -o $out'))
       master_ninja.rule(
-        'cxx',
-        description='CXX $out',
-        command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc '
-                '$cflags_pch_cc -c $in -o $out'),
-        deps='gcc',
-        depfile='$out.d')
+          'cxx',
+          description='CXX $out',
+          command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc '
+                   '$cflags_pch_cc -c $in -o $out'),
+          deps='gcc',
+          depfile='$out.d')
 
   else:
     cc_command = ('$cc /nologo /showIncludes /FC '
@@ -1786,33 +1890,32 @@
     cxx_command = ('$cxx /nologo /showIncludes /FC '
                    '@$out.rsp /c $in /Fo$out /Fd$pdbname ')
     master_ninja.rule(
-      'cc',
-      description='CC $out',
-      command=cc_command,
-      deps='msvc',
-      rspfile='$out.rsp',
-      rspfile_content='$defines $includes $cflags $cflags_c')
+        'cc',
+        description='CC $out',
+        command=cc_command,
+        deps='msvc',
+        rspfile='$out.rsp',
+        rspfile_content='$defines $includes $cflags $cflags_c')
     master_ninja.rule(
-      'cxx',
-      description='CXX $out',
-      command=cxx_command,
-      deps='msvc',
-      rspfile='$out.rsp',
-      rspfile_content='$defines $includes $cflags $cflags_cc')
+        'cxx',
+        description='CXX $out',
+        command=cxx_command,
+        deps='msvc',
+        rspfile='$out.rsp',
+        rspfile_content='$defines $includes $cflags $cflags_cc')
 
     master_ninja.rule(
-      'rc',
-      description='RC $in',
-      # Note: $in must be last otherwise rc.exe complains.
-      command=('%s gyp-win-tool rc-wrapper '
-               '$arch $rc $defines $includes $rcflags /fo$out $in' %
-               python_exec))
+        'rc',
+        description='RC $in',
+        # Note: $in must be last otherwise rc.exe complains.
+        command=(
+            '%s gyp-win-tool rc-wrapper '
+            '$arch $rc $defines $includes $rcflags /fo$out $in' % python_exec))
     master_ninja.rule(
-      'asm',
-      description='ASM $in',
-      command=('%s gyp-win-tool asm-wrapper '
-               '$arch $asm $defines $includes /c /Fo $out $in' %
-               python_exec))
+        'asm',
+        description='ASM $in',
+        command=('%s gyp-win-tool asm-wrapper '
+                 '$arch $asm $defines $includes /c /Fo $out $in' % python_exec))
 
   if flavor not in (['mac'] + microsoft_flavors):
     alink_command = 'rm -f $out && $ar $arFlags $out @$out.rsp'
@@ -1827,17 +1930,17 @@
       ld_cmd = '%s gyp-win-tool link-wrapper $arch $ld' % python_exec
 
     master_ninja.rule(
-      'alink',
-      description='AR $out',
-      command=alink_command,
-      rspfile='$out.rsp',
-      rspfile_content='$in_newline')
+        'alink',
+        description='AR $out',
+        command=alink_command,
+        rspfile='$out.rsp',
+        rspfile_content='$in_newline')
     master_ninja.rule(
-      'alink_thin',
-      description='AR $out',
-      command=alink_thin_command,
-      rspfile='$out.rsp',
-      rspfile_content='$in_newline')
+        'alink_thin',
+        description='AR $out',
+        command=alink_thin_command,
+        rspfile='$out.rsp',
+        rspfile_content='$in_newline')
 
     if flavor in ['ps3']:
       # TODO: Can we suppress the warnings from verlog.txt rather than
@@ -1848,18 +1951,17 @@
 
       prx_flags = '--oformat=fsprx --prx-with-runtime --zgenprx -zgenstub'
       master_ninja.rule(
-        'solink',
-        description='LINK(PRX) $lib',
-        restat=True,
-        command=ld_cmd + ' @$prx.rsp',
-        rspfile='$prx.rsp',
-        rspfile_content='$ldflags %s -o $prx $in $libs' % prx_flags,
-        pool='link_pool'
-      )
+          'solink',
+          description='LINK(PRX) $lib',
+          restat=True,
+          command=ld_cmd + ' @$prx.rsp',
+          rspfile='$prx.rsp',
+          rspfile_content='$ldflags %s -o $prx $in $libs' % prx_flags,
+          pool='link_pool')
       master_ninja.rule(
-        'prx_export_pickup',
-        description='PRX-EXPORT-PICKUP $out',
-        command='$prx_export_pickup --output-src=$out $in')
+          'prx_export_pickup',
+          description='PRX-EXPORT-PICKUP $out',
+          command='$prx_export_pickup --output-src=$out $in')
 
     else:  # Assume it is a Linux platform
       # This allows targets that only need to depend on $lib's API to declare an
@@ -1872,27 +1974,30 @@
           '%(solink)s && %(extract_toc)s > ${lib}.TOC; else '
           '%(solink)s && %(extract_toc)s > ${lib}.tmp && '
           'if ! cmp -s ${lib}.tmp ${lib}.TOC; then mv ${lib}.tmp ${lib}.TOC ; '
-          'fi; fi'
-          % { 'solink':
-                (ld_cmd +
-                 ' -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s'),
-              'extract_toc':
-                ('{ readelf -d ${lib} | grep SONAME ; '
-                 'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')})
+          'fi; fi' % {
+              'solink':
+                  (ld_cmd +
+                   ' -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s'),
+              'extract_toc': ('{ readelf -d ${lib} | grep SONAME ; '
+                              'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')
+          })
 
       master_ninja.rule(
-        'solink',
-        description='SOLINK $lib',
-        restat=True,
-        command=(mtime_preserving_solink_base % {
-            'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive '
-            '$libs'}))
+          'solink',
+          description='SOLINK $lib',
+          restat=True,
+          command=(mtime_preserving_solink_base % {
+              'suffix':
+                  '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive '
+                  '$libs'
+          }))
       master_ninja.rule(
-        'solink_module',
-        description='SOLINK(module) $lib',
-        restat=True,
-        command=(mtime_preserving_solink_base % {
-            'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group $libs'}))
+          'solink_module',
+          description='SOLINK(module) $lib',
+          restat=True,
+          command=(mtime_preserving_solink_base % {
+              'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group $libs'
+          }))
 
     if flavor in sony_flavors:
       # PS3 and PS4 linkers don't know about rpath.
@@ -1901,20 +2006,19 @@
       rpath = r'-Wl,-rpath=\$$ORIGIN/lib'
 
     master_ninja.rule(
-      'link',
-      description='LINK $out',
-      command=(ld_cmd +' @$out.rsp'),
-      rspfile='$out.rsp',
-      rspfile_content=('$ldflags -o $out %s -Wl,--start-group $in $solibs '
-                       '-Wl,--end-group $libs' % rpath),
-      pool='link_pool')
+        'link',
+        description='LINK $out',
+        command=(ld_cmd + ' @$out.rsp'),
+        rspfile='$out.rsp',
+        rspfile_content=('$ldflags -o $out %s -Wl,--start-group $in $solibs '
+                         '-Wl,--end-group $libs' % rpath),
+        pool='link_pool')
   elif flavor in microsoft_flavors:
     master_ninja.rule(
         'alink',
         description='LIB $out',
         command=('%s gyp-win-tool link-wrapper $arch '
-                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' %
-                 python_exec),
+                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' % python_exec),
         rspfile='$out.rsp',
         rspfile_content='$in_newline $libflags')
     dlldesc = 'LINK(DLL) $dll'
@@ -1923,31 +2027,37 @@
               '/PDB:$dll.pdb @$dll.rsp' % python_exec)
     if not flavor in microsoft_flavors:
       # XB1 doesn't need a manifest.
-      dllcmd += (' && %s gyp-win-tool manifest-wrapper $arch '
-                 '$mt -nologo -manifest $manifests -out:$dll.manifest' %
-                 python_exec)
-    master_ninja.rule('solink', description=dlldesc, command=dllcmd,
-                      rspfile='$dll.rsp',
-                      rspfile_content='$libs $in_newline $ldflags',
-                      restat=True)
-    master_ninja.rule('solink_module', description=dlldesc, command=dllcmd,
-                      rspfile='$dll.rsp',
-                      rspfile_content='$libs $in_newline $ldflags',
-                      restat=True)
+      dllcmd += (
+          ' && %s gyp-win-tool manifest-wrapper $arch '
+          '$mt -nologo -manifest $manifests -out:$dll.manifest' % python_exec)
+    master_ninja.rule(
+        'solink',
+        description=dlldesc,
+        command=dllcmd,
+        rspfile='$dll.rsp',
+        rspfile_content='$libs $in_newline $ldflags',
+        restat=True)
+    master_ninja.rule(
+        'solink_module',
+        description=dlldesc,
+        command=dllcmd,
+        rspfile='$dll.rsp',
+        rspfile_content='$libs $in_newline $ldflags',
+        restat=True)
     # Note that ldflags goes at the end so that it has the option of
     # overriding default settings earlier in the command line.
     if flavor == 'win':
-      link_command=('%s gyp-win-tool link-wrapper $arch '
-                   '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp && '
-                   '%s gyp-win-tool manifest-wrapper $arch '
-                   '$mt -nologo -manifest $manifests -out:$out.manifest' %
-                   (python_exec, python_exec))
+      link_command = ('%s gyp-win-tool link-wrapper $arch '
+                      '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp && '
+                      '%s gyp-win-tool manifest-wrapper $arch '
+                      '$mt -nologo -manifest $manifests -out:$out.manifest' %
+                      (python_exec, python_exec))
     else:
       assert flavor in microsoft_flavors
       # XB1 doesn't need a manifest.
-      link_command=('%s gyp-win-tool link-wrapper $arch '
-                   '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' %
-                   (python_exec))
+      link_command = ('%s gyp-win-tool link-wrapper $arch '
+                      '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' %
+                      (python_exec))
 
     master_ninja.rule(
         'link',
@@ -1958,107 +2068,109 @@
         pool='link_pool')
   else:
     master_ninja.rule(
-      'objc',
-      description='OBJC $out',
-      command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc '
-               '$cflags_pch_objc -c $in -o $out'),
-      depfile='$out.d')
+        'objc',
+        description='OBJC $out',
+        command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc '
+                 '$cflags_pch_objc -c $in -o $out'),
+        depfile='$out.d')
     master_ninja.rule(
-      'objcxx',
-      description='OBJCXX $out',
-      command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc '
-               '$cflags_pch_objcc -c $in -o $out'),
-      depfile='$out.d')
+        'objcxx',
+        description='OBJCXX $out',
+        command=(
+            '$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc '
+            '$cflags_pch_objcc -c $in -o $out'),
+        depfile='$out.d')
     master_ninja.rule(
-      'alink',
-      description='LIBTOOL-STATIC $out, POSTBUILDS',
-      command='rm -f $out && '
-              './gyp-mac-tool filter-libtool libtool $libtool_flags '
-              '-static -o $out $in'
-              '$postbuilds')
+        'alink',
+        description='LIBTOOL-STATIC $out, POSTBUILDS',
+        command='rm -f $out && '
+        './gyp-mac-tool filter-libtool libtool $libtool_flags '
+        '-static -o $out $in'
+        '$postbuilds')
 
     # Record the public interface of $lib in $lib.TOC. See the corresponding
     # comment in the posix section above for details.
     mtime_preserving_solink_base = (
         'if [ ! -e $lib -o ! -e ${lib}.TOC ] || '
-             # Always force dependent targets to relink if this library
-             # reexports something. Handling this correctly would require
-             # recursive TOC dumping but this is rare in practice, so punt.
-             'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then '
-          '%(solink)s && %(extract_toc)s > ${lib}.TOC; '
+        # Always force dependent targets to relink if this library
+        # reexports something. Handling this correctly would require
+        # recursive TOC dumping but this is rare in practice, so punt.
+        'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then '
+        '%(solink)s && %(extract_toc)s > ${lib}.TOC; '
         'else '
-          '%(solink)s && %(extract_toc)s > ${lib}.tmp && '
-          'if ! cmp -s ${lib}.tmp ${lib}.TOC; then '
-            'mv ${lib}.tmp ${lib}.TOC ; '
-          'fi; '
-        'fi'
-        % { 'solink': '$ld -shared $ldflags -o $lib %(suffix)s',
+        '%(solink)s && %(extract_toc)s > ${lib}.tmp && '
+        'if ! cmp -s ${lib}.tmp ${lib}.TOC; then '
+        'mv ${lib}.tmp ${lib}.TOC ; '
+        'fi; '
+        'fi' % {
+            'solink': '$ld -shared $ldflags -o $lib %(suffix)s',
             'extract_toc':
-              '{ otool -l $lib | grep LC_ID_DYLIB -A 5; '
-              'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'})
+                '{ otool -l $lib | grep LC_ID_DYLIB -A 5; '
+                'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'
+        })
 
     # TODO(thakis): The solink_module rule is likely wrong. Xcode seems to pass
     # -bundle -single_module here (for osmesa.so).
     master_ninja.rule(
-      'solink',
-      description='SOLINK $lib, POSTBUILDS',
-      restat=True,
-      command=(mtime_preserving_solink_base % {
-          'suffix': '$in $solibs $libs$postbuilds'}))
+        'solink',
+        description='SOLINK $lib, POSTBUILDS',
+        restat=True,
+        command=(mtime_preserving_solink_base % {
+            'suffix': '$in $solibs $libs$postbuilds'
+        }))
     master_ninja.rule(
-      'solink_module',
-      description='SOLINK(module) $lib, POSTBUILDS',
-      restat=True,
-      command=(mtime_preserving_solink_base % {
-          'suffix': '$in $solibs $libs$postbuilds'}))
+        'solink_module',
+        description='SOLINK(module) $lib, POSTBUILDS',
+        restat=True,
+        command=(mtime_preserving_solink_base % {
+            'suffix': '$in $solibs $libs$postbuilds'
+        }))
 
     master_ninja.rule(
-      'link',
-      description='LINK $out, POSTBUILDS',
-      command=('$ld $ldflags -o $out '
-               '$in $solibs $libs$postbuilds'),
-      pool='link_pool')
+        'link',
+        description='LINK $out, POSTBUILDS',
+        command=('$ld $ldflags -o $out '
+                 '$in $solibs $libs$postbuilds'),
+        pool='link_pool')
     master_ninja.rule(
-      'infoplist',
-      description='INFOPLIST $out',
-      command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && '
-               'plutil -convert xml1 $out $out'))
+        'infoplist',
+        description='INFOPLIST $out',
+        command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && '
+                 'plutil -convert xml1 $out $out'))
     master_ninja.rule(
-      'mac_tool',
-      description='MACTOOL $mactool_cmd $in',
-      command='$env ./gyp-mac-tool $mactool_cmd $in $out')
+        'mac_tool',
+        description='MACTOOL $mactool_cmd $in',
+        command='$env ./gyp-mac-tool $mactool_cmd $in $out')
     master_ninja.rule(
-      'package_framework',
-      description='PACKAGE FRAMEWORK $out, POSTBUILDS',
-      command='./gyp-mac-tool package-framework $out $version$postbuilds '
-              '&& touch $out')
+        'package_framework',
+        description='PACKAGE FRAMEWORK $out, POSTBUILDS',
+        command='./gyp-mac-tool package-framework $out $version$postbuilds '
+        '&& touch $out')
   if flavor in microsoft_flavors:
     master_ninja.rule(
-      'stamp',
-      description='STAMP $out',
-      command='%s gyp-win-tool stamp $out' % python_exec)
+        'stamp',
+        description='STAMP $out',
+        command='%s gyp-win-tool stamp $out' % python_exec)
     master_ninja.rule(
-      'copy',
-      description='COPY $in $out',
-      command='%s gyp-win-tool recursive-mirror $in $out' % python_exec)
+        'copy',
+        description='COPY $in $out',
+        command='%s gyp-win-tool recursive-mirror $in $out' % python_exec)
   elif sys.platform in ['cygwin', 'win32']:
     master_ninja.rule(
-      'stamp',
-      description='STAMP $out',
-      command='$python gyp-win-tool stamp $out')
+        'stamp',
+        description='STAMP $out',
+        command='$python gyp-win-tool stamp $out')
     master_ninja.rule(
-      'copy',
-      description='COPY $in $out',
-      command='$python gyp-win-tool recursive-mirror $in $out')
+        'copy',
+        description='COPY $in $out',
+        command='$python gyp-win-tool recursive-mirror $in $out')
   else:
     master_ninja.rule(
-      'stamp',
-      description='STAMP $out',
-      command='${postbuilds}touch $out')
+        'stamp', description='STAMP $out', command='${postbuilds}touch $out')
     master_ninja.rule(
-      'copy',
-      description='COPY $in $out',
-      command='rm -rf $out && cp -af $in $out')
+        'copy',
+        description='COPY $in $out',
+        command='rm -rf $out && cp -af $in $out')
   master_ninja.newline()
 
   # Output host building rules
@@ -2068,26 +2180,25 @@
     cxx_command = ('$cxx /nologo /showIncludes /FC '
                    '@$out.rsp /c $in /Fo$out /Fd$pdbname ')
     master_ninja.rule(
-      'cc_host',
-      description='CC_HOST $out',
-      command=cc_command,
-      deps='msvc',
-      rspfile='$out.rsp',
-      rspfile_content='$defines $includes $cflags_host $cflags_c_host')
+        'cc_host',
+        description='CC_HOST $out',
+        command=cc_command,
+        deps='msvc',
+        rspfile='$out.rsp',
+        rspfile_content='$defines $includes $cflags_host $cflags_c_host')
     master_ninja.rule(
-      'cxx_host',
-      description='CXX_HOST $out',
-      command=cxx_command,
-      deps='msvc',
-      rspfile='$out.rsp',
-      rspfile_content='$defines $includes $cflags_host $cflags_cc_host')
+        'cxx_host',
+        description='CXX_HOST $out',
+        command=cxx_command,
+        deps='msvc',
+        rspfile='$out.rsp',
+        rspfile_content='$defines $includes $cflags_host $cflags_cc_host')
 
     master_ninja.rule(
         'alink_host',
         description='LIB_HOST $out',
         command=('%s gyp-win-tool link-wrapper $arch '
-                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' %
-                 python_exec),
+                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' % python_exec),
         rspfile='$out.rsp',
         rspfile_content='$in_newline $libflags_host')
 
@@ -2095,14 +2206,13 @@
         'alink_thin_host',
         description='LIB_HOST $out',
         command=('%s gyp-win-tool link-wrapper $arch '
-                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' %
-                 python_exec),
+                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' % python_exec),
         rspfile='$out.rsp',
         rspfile_content='$in_newline $libflags_host')
 
-    link_command=('%s gyp-win-tool link-wrapper $arch '
-                 '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' %
-                 (python_exec))
+    link_command = ('%s gyp-win-tool link-wrapper $arch '
+                    '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' %
+                    (python_exec))
 
     master_ninja.rule(
         'link_host',
@@ -2115,38 +2225,38 @@
     cc_command = 'bash -c "$cc_host @$out.rsp"'
     cxx_command = 'bash -c "$cxx_host @$out.rsp"'
     master_ninja.rule(
-      'cc_host',
-      description='CC_HOST $out',
-      command=cc_command,
-      rspfile='$out.rsp',
-      rspfile_content=('-MMD -MF $out.d $defines $includes $cflags_host '
-                       '$cflags_c_host $cflags_pch_c -c $in -o $out'),
-      depfile='$out.d')
+        'cc_host',
+        description='CC_HOST $out',
+        command=cc_command,
+        rspfile='$out.rsp',
+        rspfile_content=('-MMD -MF $out.d $defines $includes $cflags_host '
+                         '$cflags_c_host $cflags_pch_c -c $in -o $out'),
+        depfile='$out.d')
     master_ninja.rule(
-      'cxx_host',
-      description='CXX_HOST $out',
-      command=cxx_command,
-      rspfile='$out.rsp',
-      rspfile_content=('-MMD -MF $out.d $defines $includes $cflags_host '
-                       '$cflags_cc_host $cflags_pch_cc -c $in -o $out'),
-      depfile='$out.d')
+        'cxx_host',
+        description='CXX_HOST $out',
+        command=cxx_command,
+        rspfile='$out.rsp',
+        rspfile_content=('-MMD -MF $out.d $defines $includes $cflags_host '
+                         '$cflags_cc_host $cflags_pch_cc -c $in -o $out'),
+        depfile='$out.d')
 
     alink_command = 'rm -f $out && $ar_host $arFlags_host $out @$out.rsp'
     alink_thin_command = ('rm -f $out && $ar_host $arThinFlags_host $out '
                           '@$out.rsp')
 
     master_ninja.rule(
-      'alink_host',
-      description='AR_HOST $out',
-      command='bash -c "' + alink_command + '"',
-      rspfile='$out.rsp',
-      rspfile_content='$in_newline')
+        'alink_host',
+        description='AR_HOST $out',
+        command='bash -c "' + alink_command + '"',
+        rspfile='$out.rsp',
+        rspfile_content='$in_newline')
     master_ninja.rule(
-      'alink_thin_host',
-      description='AR_HOST $out',
-      command='bash -c "' + alink_thin_command + '"',
-      rspfile='$out.rsp',
-      rspfile_content='$in_newline')
+        'alink_thin_host',
+        description='AR_HOST $out',
+        command='bash -c "' + alink_thin_command + '"',
+        rspfile='$out.rsp',
+        rspfile_content='$in_newline')
     beginlinkinlibs = ''
     endlinkinlibs = ''
     if is_linux:
@@ -2154,16 +2264,15 @@
       endlinkinlibs = '-Wl,--end-group'
     rpath = '-Wl,-rpath=\$$ORIGIN/lib'
     master_ninja.rule(
-      'link_host',
-      description='LINK_HOST $out',
-      command=('bash -c "$ld_host $ldflags_host -o $out %s '
-               '%s $in $solibs %s $libs"' % (rpath,
-                                             beginlinkinlibs, endlinkinlibs)))
+        'link_host',
+        description='LINK_HOST $out',
+        command=('bash -c "$ld_host $ldflags_host -o $out %s '
+                 '%s $in $solibs %s $libs"' % (rpath, beginlinkinlibs,
+                                               endlinkinlibs)))
 
   all_targets = set()
   for build_file in params['build_files']:
-    for target in gyp.common.AllTargets(target_list,
-                                        target_dicts,
+    for target in gyp.common.AllTargets(target_list, target_dicts,
                                         os.path.normpath(build_file)):
       all_targets.add(target)
   all_outputs = set()
@@ -2181,7 +2290,7 @@
 
     this_make_global_settings = data[build_file].get('make_global_settings', [])
     assert make_global_settings == this_make_global_settings, (
-        "make_global_settings needs to be the same for all targets.")
+        'make_global_settings needs to be the same for all targets.')
 
     spec = target_dicts[qualified_target]
 
@@ -2190,7 +2299,8 @@
         default_project = name
       else:
         raise Exception('More than one default_project specified.'
-          'First in {0} and now in {1}'.format(default_project, name))
+                        'First in {0} and now in {1}'.format(
+                            default_project, name))
 
     if flavor == 'mac':
       gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec)
@@ -2204,14 +2314,18 @@
     output_file = os.path.join(obj, base_path, name + '.ninja')
 
     abs_build_dir = os.path.abspath(toplevel_build)
-    writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir,
-                         OpenOutput(os.path.join(toplevel_build, output_file)),
-                         flavor, case_sensitive_filesystem,
-                         abs_build_dir=abs_build_dir)
+    writer = NinjaWriter(
+        qualified_target,
+        target_outputs,
+        base_path,
+        build_dir,
+        OpenOutput(os.path.join(toplevel_build, output_file)),
+        flavor,
+        case_sensitive_filesystem,
+        abs_build_dir=abs_build_dir)
     master_ninja.subninja(output_file)
 
-    target = writer.WriteSpec(
-        spec, config_name, generator_flags)
+    target = writer.WriteSpec(spec, config_name, generator_flags)
     if target:
       if name != target.FinalOutput():
         out_name = name
@@ -2229,8 +2343,9 @@
     master_ninja.newline()
     master_ninja.comment('Short names for targets.')
     for short_name in target_short_names:
-      master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in
-                                               target_short_names[short_name]])
+      master_ninja.build(short_name, 'phony', [
+          x.FinalOutput() for x in target_short_names[short_name]
+      ])
 
   if all_outputs:
     master_ninja.newline()
@@ -2261,6 +2376,7 @@
 
 def GenerateOutput(target_list, target_dicts, data, params):
   user_config = params.get('generator_flags', {}).get('config', None)
+  # TODO: Replace MSVSUtil with calls to abstract toolchain.
   if gyp.common.GetFlavor(params) in microsoft_flavors:
     target_list, target_dicts = MSVSUtil.ShardTargets(target_list, target_dicts)
   if user_config:
@@ -2273,8 +2389,8 @@
         pool = multiprocessing.Pool(len(config_names))
         arglists = []
         for config_name in config_names:
-          arglists.append(
-              (target_list, target_dicts, data, params, config_name))
+          arglists.append((target_list, target_dicts, data, params,
+                           config_name))
           pool.map(CallGenerateOutputForConfig, arglists)
       except KeyboardInterrupt, e:
         pool.terminate()