Import Cobalt 12.90790
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 9d943ae..aef6987 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 This document records all notable changes made to Cobalt since the last release.
 
+## Version 12
+ - **Add support for touchpad not associated with a pointer**
+
+   This adds support for input of type kSbInputDeviceTypeTouchPad for
+   touchpads not associated with a pointer.
+
 ## Version 11
  - **Splash Screen Customization**
 
@@ -105,3 +111,24 @@
    Support for preloading an application with no graphics resources. See the
    new [Application Lifecycle Integration](doc/lifecycle.md) guide for more
    details.
+
+ - **Improvements and Bug Fixes**
+   - Fixed position elements given correct draw order without requiring z-index.
+   - Overflow-hidden correctly applied when z-index is used.
+   - Padding box forms containing block for fixed position elements within
+     transforms.
+   - Determine whether a font is bold in more cases, so synthetic bolding isn't
+     incorrectly applied.
+   - Fix bug where decoding images with large headers would fail.
+   - Include missing CSS transforms in `GetClientRects()` calculations.
+   - CSS Transitions and Animations not resetting properly when adjusting
+     `display: none` on an element.
+   - `offset_left` and similar properties are no longer cause many expensive
+     re-layouts to query.
+   - Fix bug where pseudo-elements were "inheriting" the inline style of their
+     parent elements as if it were their own inline style.
+
+## Version 9
+ - **Improvements and Bug Fixes**
+   - Non-fixed position elements given correct draw order without requiring
+     z-index.
\ No newline at end of file
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index 5c826de..9473e36 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -61,6 +61,7 @@
     MacroOpWithNameOnly(focus)                                       \
     MacroOpWithNameOnly(focusin)                                     \
     MacroOpWithNameOnly(focusout)                                    \
+    MacroOpWithNameOnly(gotpointercapture)                           \
     MacroOpWithNameOnly(hashchange)                                  \
     MacroOpWithNameOnly(keydown)                                     \
     MacroOpWithNameOnly(keypress)                                    \
@@ -71,6 +72,7 @@
     MacroOpWithNameOnly(loadedmetadata)                              \
     MacroOpWithNameOnly(loadend)                                     \
     MacroOpWithNameOnly(loadstart)                                   \
+    MacroOpWithNameOnly(lostpointercapture)                          \
     MacroOpWithNameOnly(mark)                                        \
     MacroOpWithNameOnly(message)                                     \
     MacroOpWithNameOnly(mousedown)                                   \
diff --git a/src/cobalt/bindings/code_generator_cobalt.py b/src/cobalt/bindings/code_generator_cobalt.py
index c9dd3ac..fb43c76 100644
--- a/src/cobalt/bindings/code_generator_cobalt.py
+++ b/src/cobalt/bindings/code_generator_cobalt.py
@@ -246,7 +246,9 @@
     cc_text = self.render_template(cpp_template_filename, template_context)
     header_path = self.path_builder.BindingsHeaderFullPath(interface_name)
     cc_path = self.path_builder.BindingsImplementationPath(interface_name)
-    return ((header_path, header_text), (cc_path, cc_text),)
+    return (
+        (header_path, header_text),
+        (cc_path, cc_text),)
 
   def generate_dictionary_code(self, definitions, dictionary_name, dictionary):
     header_template_filename = 'dictionary.h.template'
@@ -265,8 +267,9 @@
     conversion_impl_path = (
         self.path_builder.DictionaryConversionImplementationPath(
             dictionary_name))
-    return ((header_path, header_text), (conversion_impl_path,
-                                         conversion_text),)
+    return (
+        (header_path, header_text),
+        (conversion_impl_path, conversion_text),)
 
   def generate_enum_code(self, definitions, enumeration_name, enumeration):
     header_template_filename = 'enumeration.h.template'
@@ -290,8 +293,9 @@
     conversion_impl_path = (
         self.path_builder.EnumConversionImplementationFullPath(enumeration_name)
     )
-    return ((header_path, header_text), (conversion_impl_path,
-                                         conversion_text),)
+    return (
+        (header_path, header_text),
+        (conversion_impl_path, conversion_text),)
 
   def generate_conversion_code(self):
     enumerations = list(self.info_provider.enumerations.keys())
@@ -433,7 +437,9 @@
                                                     dictionary.members))
     if dictionary.parent:
       referenced_interface_names.add(dictionary.parent)
-      context['parent'] = dictionary.parent
+      parent_namespace = '::'.join(
+          self.path_builder.NamespaceComponents(dictionary.parent))
+      context['parent'] = '%s::%s' % (parent_namespace, dictionary.parent)
 
     referenced_class_contexts = self.referenced_class_contexts(
         referenced_interface_names, for_conversion)
diff --git a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/derived_dictionary.h b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/derived_dictionary.h
new file mode 100644
index 0000000..cd27ab6
--- /dev/null
+++ b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/derived_dictionary.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+// clang-format off
+
+// This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
+// Auto-generated from template: bindings/templates/dictionary.h.template
+
+#ifndef DerivedDictionary_h
+#define DerivedDictionary_h
+
+#include <string>
+
+#include "base/optional.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/script/sequence.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/bindings/testing/test_dictionary.h"
+
+using cobalt::bindings::testing::TestDictionary;
+
+namespace cobalt {
+namespace bindings {
+namespace testing {
+
+class DerivedDictionary : public cobalt::bindings::testing::TestDictionary {
+ public:
+  DerivedDictionary() {
+    additional_member_ = false;
+  }
+
+  DerivedDictionary(const DerivedDictionary& other)
+    : cobalt::bindings::testing::TestDictionary(other) {
+    additional_member_ = other.additional_member_;
+  }
+
+  DerivedDictionary& operator=(const DerivedDictionary& other) {
+    cobalt::bindings::testing::TestDictionary::operator=(other);
+    additional_member_ = other.additional_member_;
+    return *this;
+  }
+
+  bool has_additional_member() const {
+    return true;
+  }
+  bool additional_member() const {
+    return additional_member_;
+  }
+  void set_additional_member(bool value) {
+    additional_member_ = value;
+  }
+
+ private:
+  bool additional_member_;
+};
+
+// This ostream override is necessary for MOCK_METHODs commonly used
+// in idl test code
+inline std::ostream& operator<<(
+    std::ostream& stream, const cobalt::bindings::testing::DerivedDictionary& in) {
+  UNREFERENCED_PARAMETER(in);
+  stream << "[DerivedDictionary]";
+  return stream;
+}
+
+}  // namespace cobalt
+}  // namespace bindings
+}  // namespace testing
+
+#endif  // DerivedDictionary_h
diff --git a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc
new file mode 100644
index 0000000..6c23574
--- /dev/null
+++ b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+// clang-format off
+
+// This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
+// Auto-generated from template: bindings/mozjs/templates/dictionary-conversion.cc.template
+
+#include "mozjs_gen_type_conversion.h"
+
+#include "cobalt/bindings/testing/derived_dictionary.h"
+
+#include "cobalt/script/exception_state.h"
+#include "third_party/mozjs/js/src/jsapi.h"
+#include "cobalt/bindings/testing/test_dictionary.h"
+
+using cobalt::bindings::testing::DerivedDictionary;
+using cobalt::bindings::testing::TestDictionary;
+
+namespace cobalt {
+namespace script {
+namespace mozjs {
+
+void ToJSValue(
+    JSContext* context,
+    const DerivedDictionary& in_dictionary,
+    JS::MutableHandleValue out_value) {
+  // Create a new object that will hold the dictionary values.
+  JS::RootedObject dictionary_object(
+      context, JS_NewObject(context, NULL, NULL, NULL));
+  const int kPropertyAttributes = JSPROP_ENUMERATE;
+  if (in_dictionary.has_additional_member()) {
+    JS::RootedValue member_value(context);
+    ToJSValue(context, in_dictionary.additional_member(), &member_value);
+    if (!JS_DefineProperty(context, dictionary_object,
+                           "additionalMember",
+                           member_value, NULL, NULL, kPropertyAttributes)) {
+      // Some internal error occurred.
+      NOTREACHED();
+      return;
+    }
+  }
+  out_value.set(OBJECT_TO_JSVAL(dictionary_object));
+}
+
+void FromJSValue(JSContext* context, JS::HandleValue value,
+                 int conversion_flags, ExceptionState* exception_state,
+                 DerivedDictionary* out_dictionary) {
+  DCHECK_EQ(0, conversion_flags) << "Unexpected conversion flags.";
+  FromJSValue(context, value, conversion_flags, exception_state,
+      static_cast<cobalt::bindings::testing::TestDictionary*>(out_dictionary));
+  // https://heycam.github.io/webidl/#es-dictionary
+
+  if (value.isUndefined() || value.isNull()) {
+    // The default constructor will assign appropriate values to dictionary
+    // members with default values and leave the others unset.
+    *out_dictionary = DerivedDictionary();
+    return;
+  }
+  if (!value.isObject()) {
+    // 1. If Type(V) is not Undefined, Null or Object, then throw a TypeError.
+    exception_state->SetSimpleException(kNotObjectType);
+    return;
+  }
+  JS::RootedObject dictionary_object(context, JSVAL_TO_OBJECT(value));
+  JS::RootedValue additional_member(context);
+  if (!JS_GetProperty(context, dictionary_object,
+                      "additionalMember",
+                      additional_member.address())) {
+    exception_state->SetSimpleException(kSimpleError);
+    return;
+  }
+  if (!additional_member.isUndefined()) {
+    bool converted_value;
+    FromJSValue(context,
+                additional_member,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_additional_member(converted_value);
+  }
+}
+
+}  // namespace mozjs
+}  // namespace script
+}  // namespace cobalt
+
diff --git a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
index e40be08..1a32edc 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
@@ -24,6 +24,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/opaque_handle.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/bindings/testing/derived_dictionary.h"
 #include "cobalt/bindings/testing/dictionary_with_dictionary_member.h"
 #include "cobalt/bindings/testing/test_dictionary.h"
 
@@ -53,6 +54,7 @@
 namespace {
 using cobalt::bindings::testing::DictionaryInterface;
 using cobalt::bindings::testing::MozjsDictionaryInterface;
+using cobalt::bindings::testing::DerivedDictionary;
 using cobalt::bindings::testing::DictionaryWithDictionaryMember;
 using cobalt::bindings::testing::TestDictionary;
 using cobalt::script::CallbackInterfaceTraits;
@@ -277,6 +279,70 @@
   return !exception_state.is_exception_set();
 }
 
+JSBool fcn_derivedDictionaryOperation(
+    JSContext* context, uint32_t argc, JS::Value *vp) {
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  // Compute the 'this' value.
+  JS::RootedValue this_value(context, JS_ComputeThis(context, vp));
+  // 'this' should be an object.
+  JS::RootedObject object(context);
+  if (JS_TypeOfValue(context, this_value) != JSTYPE_OBJECT) {
+    return false;
+  }
+  if (!JS_ValueToObject(context, this_value, object.address())) {
+    NOTREACHED();
+    return false;
+  }
+  const JSClass* proto_class =
+      MozjsDictionaryInterface::PrototypeClass(context);
+  if (proto_class == JS_GetClass(object)) {
+    // Simply returns true if the object is this class's prototype object.
+    // There is no need to return any value due to the object is not a platform
+    // object. The execution reaches here when Object.getOwnPropertyDescriptor
+    // gets called on native object prototypes.
+    return true;
+  }
+
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DictionaryInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
+  MozjsExceptionState exception_state(context);
+  JS::RootedValue result_value(context);
+
+  WrapperPrivate* wrapper_private =
+      WrapperPrivate::GetFromObject(context, object);
+  DictionaryInterface* impl =
+      wrapper_private->wrappable<DictionaryInterface>().get();
+  const size_t kMinArguments = 1;
+  if (args.length() < kMinArguments) {
+    exception_state.SetSimpleException(script::kInvalidNumberOfArguments);
+    return false;
+  }
+  // Non-optional arguments
+  TypeTraits<DerivedDictionary >::ConversionType dictionary;
+
+  DCHECK_LT(0, args.length());
+  JS::RootedValue non_optional_value0(
+      context, args[0]);
+  FromJSValue(context,
+              non_optional_value0,
+              kNoConversionFlags,
+              &exception_state, &dictionary);
+  if (exception_state.is_exception_set()) {
+    return false;
+  }
+
+  impl->DerivedDictionaryOperation(dictionary);
+  result_value.set(JS::UndefinedHandleValue);
+  return !exception_state.is_exception_set();
+}
+
 JSBool fcn_dictionaryOperation(
     JSContext* context, uint32_t argc, JS::Value *vp) {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
@@ -418,6 +484,13 @@
 
 const JSFunctionSpec prototype_functions[] = {
   {
+      "derivedDictionaryOperation",
+      JSOP_WRAPPER(&fcn_derivedDictionaryOperation),
+      1,
+      JSPROP_ENUMERATE,
+      NULL,
+  },
+  {
       "dictionaryOperation",
       JSOP_WRAPPER(&fcn_dictionaryOperation),
       1,
diff --git a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
index 8c8f3c4..071d45a 100644
--- a/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
+++ b/src/cobalt/bindings/generated/mozjs/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
@@ -334,6 +334,18 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
+  if (!any_member_with_default.isUndefined()) {
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                any_member_with_default,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_any_member_with_default(&converted_value);
+  }
   JS::RootedValue any_member(context);
   if (!JS_GetProperty(context, dictionary_object,
                       "anyMember",
@@ -341,6 +353,18 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
+  if (!any_member.isUndefined()) {
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                any_member,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_any_member(&converted_value);
+  }
 }
 
 }  // namespace mozjs
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/derived_dictionary.h b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/derived_dictionary.h
new file mode 100644
index 0000000..cd27ab6
--- /dev/null
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/derived_dictionary.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+// clang-format off
+
+// This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
+// Auto-generated from template: bindings/templates/dictionary.h.template
+
+#ifndef DerivedDictionary_h
+#define DerivedDictionary_h
+
+#include <string>
+
+#include "base/optional.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/script/sequence.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/bindings/testing/test_dictionary.h"
+
+using cobalt::bindings::testing::TestDictionary;
+
+namespace cobalt {
+namespace bindings {
+namespace testing {
+
+class DerivedDictionary : public cobalt::bindings::testing::TestDictionary {
+ public:
+  DerivedDictionary() {
+    additional_member_ = false;
+  }
+
+  DerivedDictionary(const DerivedDictionary& other)
+    : cobalt::bindings::testing::TestDictionary(other) {
+    additional_member_ = other.additional_member_;
+  }
+
+  DerivedDictionary& operator=(const DerivedDictionary& other) {
+    cobalt::bindings::testing::TestDictionary::operator=(other);
+    additional_member_ = other.additional_member_;
+    return *this;
+  }
+
+  bool has_additional_member() const {
+    return true;
+  }
+  bool additional_member() const {
+    return additional_member_;
+  }
+  void set_additional_member(bool value) {
+    additional_member_ = value;
+  }
+
+ private:
+  bool additional_member_;
+};
+
+// This ostream override is necessary for MOCK_METHODs commonly used
+// in idl test code
+inline std::ostream& operator<<(
+    std::ostream& stream, const cobalt::bindings::testing::DerivedDictionary& in) {
+  UNREFERENCED_PARAMETER(in);
+  stream << "[DerivedDictionary]";
+  return stream;
+}
+
+}  // namespace cobalt
+}  // namespace bindings
+}  // namespace testing
+
+#endif  // DerivedDictionary_h
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc
new file mode 100644
index 0000000..de2c713
--- /dev/null
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_dictionary.cc
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+// clang-format off
+
+// This file has been auto-generated by bindings/code_generator_cobalt.py. DO NOT MODIFY!
+// Auto-generated from template: bindings/mozjs45/templates/dictionary-conversion.cc.template
+
+#include "mozjs_gen_type_conversion.h"
+
+#include "cobalt/bindings/testing/derived_dictionary.h"
+
+#include "cobalt/script/exception_state.h"
+#include "third_party/mozjs-45/js/src/jsapi.h"
+#include "cobalt/bindings/testing/test_dictionary.h"
+
+using cobalt::bindings::testing::DerivedDictionary;
+using cobalt::bindings::testing::TestDictionary;
+
+namespace cobalt {
+namespace script {
+namespace mozjs {
+
+void ToJSValue(
+    JSContext* context,
+    const DerivedDictionary& in_dictionary,
+    JS::MutableHandleValue out_value) {
+  // Create a new object that will hold the dictionary values.
+  JS::RootedObject dictionary_object(
+      context, JS_NewObject(context, nullptr));
+  const int kPropertyAttributes = JSPROP_ENUMERATE;
+  if (in_dictionary.has_additional_member()) {
+    JS::RootedValue member_value(context);
+    ToJSValue(context, in_dictionary.additional_member(), &member_value);
+    if (!JS_DefineProperty(context, dictionary_object,
+                           "additionalMember",
+                           member_value, kPropertyAttributes, nullptr, nullptr)) {
+      // Some internal error occurred.
+      NOTREACHED();
+      return;
+    }
+  }
+  out_value.setObject(*dictionary_object);
+}
+
+void FromJSValue(JSContext* context, JS::HandleValue value,
+                 int conversion_flags, ExceptionState* exception_state,
+                 DerivedDictionary* out_dictionary) {
+  DCHECK_EQ(0, conversion_flags) << "Unexpected conversion flags.";
+  FromJSValue(context, value, conversion_flags, exception_state,
+      static_cast<cobalt::bindings::testing::TestDictionary*>(out_dictionary));
+  // https://heycam.github.io/webidl/#es-dictionary
+
+  if (value.isUndefined() || value.isNull()) {
+    // The default constructor will assign appropriate values to dictionary
+    // members with default values and leave the others unset.
+    *out_dictionary = DerivedDictionary();
+    return;
+  }
+  if (!value.isObject()) {
+    // 1. If Type(V) is not Undefined, Null or Object, then throw a TypeError.
+    exception_state->SetSimpleException(kNotObjectType);
+    return;
+  }
+  JS::RootedObject dictionary_object(context, &value.toObject());
+  JS::RootedValue additional_member(context);
+  if (!JS_GetProperty(context, dictionary_object,
+                      "additionalMember",
+                      &additional_member)) {
+    exception_state->SetSimpleException(kSimpleError);
+    return;
+  }
+  if (!additional_member.isUndefined()) {
+    bool converted_value;
+    FromJSValue(context,
+                additional_member,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_additional_member(converted_value);
+  }
+}
+
+}  // namespace mozjs
+}  // namespace script
+}  // namespace cobalt
+
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
index f855b8d..c6eb535 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_getter_setter_interface.cc
@@ -822,7 +822,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 13;
+  const int kInterfaceUniqueId = 14;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
index e04680b..3e5b114 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_derived_interface.cc
@@ -382,7 +382,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 14;
+  const int kInterfaceUniqueId = 15;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
index 15d0eed..85aa292 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_dictionary_interface.cc
@@ -24,6 +24,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/opaque_handle.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/bindings/testing/derived_dictionary.h"
 #include "cobalt/bindings/testing/dictionary_with_dictionary_member.h"
 #include "cobalt/bindings/testing/test_dictionary.h"
 
@@ -53,6 +54,7 @@
 namespace {
 using cobalt::bindings::testing::DictionaryInterface;
 using cobalt::bindings::testing::MozjsDictionaryInterface;
+using cobalt::bindings::testing::DerivedDictionary;
 using cobalt::bindings::testing::DictionaryWithDictionaryMember;
 using cobalt::bindings::testing::TestDictionary;
 using cobalt::script::CallbackInterfaceTraits;
@@ -281,6 +283,70 @@
   return !exception_state.is_exception_set();
 }
 
+bool fcn_derivedDictionaryOperation(
+    JSContext* context, uint32_t argc, JS::Value *vp) {
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  // Compute the 'this' value.
+  JS::RootedValue this_value(context, JS_ComputeThis(context, vp));
+  // 'this' should be an object.
+  JS::RootedObject object(context);
+  if (JS_TypeOfValue(context, this_value) != JSTYPE_OBJECT) {
+    return false;
+  }
+  if (!JS_ValueToObject(context, this_value, &object)) {
+    NOTREACHED();
+    return false;
+  }
+  const JSClass* proto_class =
+      MozjsDictionaryInterface::PrototypeClass(context);
+  if (proto_class == JS_GetClass(object)) {
+    // Simply returns true if the object is this class's prototype object.
+    // There is no need to return any value due to the object is not a platform
+    // object. The execution reaches here when Object.getOwnPropertyDescriptor
+    // gets called on native object prototypes.
+    return true;
+  }
+
+  MozjsGlobalEnvironment* global_environment =
+      static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
+  WrapperFactory* wrapper_factory = global_environment->wrapper_factory();
+  if (!wrapper_factory->DoesObjectImplementInterface(
+        object, base::GetTypeId<DictionaryInterface>())) {
+    MozjsExceptionState exception(context);
+    exception.SetSimpleException(script::kDoesNotImplementInterface);
+    return false;
+  }
+  MozjsExceptionState exception_state(context);
+  JS::RootedValue result_value(context);
+
+  WrapperPrivate* wrapper_private =
+      WrapperPrivate::GetFromObject(context, object);
+  DictionaryInterface* impl =
+      wrapper_private->wrappable<DictionaryInterface>().get();
+  const size_t kMinArguments = 1;
+  if (args.length() < kMinArguments) {
+    exception_state.SetSimpleException(script::kInvalidNumberOfArguments);
+    return false;
+  }
+  // Non-optional arguments
+  TypeTraits<DerivedDictionary >::ConversionType dictionary;
+
+  DCHECK_LT(0, args.length());
+  JS::RootedValue non_optional_value0(
+      context, args[0]);
+  FromJSValue(context,
+              non_optional_value0,
+              kNoConversionFlags,
+              &exception_state, &dictionary);
+  if (exception_state.is_exception_set()) {
+    return false;
+  }
+
+  impl->DerivedDictionaryOperation(dictionary);
+  result_value.set(JS::UndefinedHandleValue);
+  return !exception_state.is_exception_set();
+}
+
 bool fcn_dictionaryOperation(
     JSContext* context, uint32_t argc, JS::Value *vp) {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
@@ -423,6 +489,9 @@
 
 const JSFunctionSpec prototype_functions[] = {
   JS_FNSPEC(
+      "derivedDictionaryOperation", fcn_derivedDictionaryOperation, NULL,
+      1, JSPROP_ENUMERATE, NULL),
+  JS_FNSPEC(
       "dictionaryOperation", fcn_dictionaryOperation, NULL,
       1, JSPROP_ENUMERATE, NULL),
   JS_FNSPEC(
@@ -509,7 +578,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 15;
+  const int kInterfaceUniqueId = 16;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
index f08fc6a..0a829bb 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_disabled_interface.cc
@@ -422,7 +422,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 17;
+  const int kInterfaceUniqueId = 18;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_enumeration_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_enumeration_interface.cc
index 299c539..fb1bbf3 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_enumeration_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_enumeration_interface.cc
@@ -448,7 +448,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 18;
+  const int kInterfaceUniqueId = 19;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exception_object_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exception_object_interface.cc
index 5030193..f826cee 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exception_object_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exception_object_interface.cc
@@ -377,7 +377,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 19;
+  const int kInterfaceUniqueId = 20;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exceptions_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exceptions_interface.cc
index 8b3aff7..c68f5b0 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exceptions_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_exceptions_interface.cc
@@ -430,7 +430,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 20;
+  const int kInterfaceUniqueId = 21;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_extended_idl_attributes_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_extended_idl_attributes_interface.cc
index 41a46d6..0bb6b92 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_extended_idl_attributes_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_extended_idl_attributes_interface.cc
@@ -489,7 +489,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 21;
+  const int kInterfaceUniqueId = 22;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_garbage_collection_test_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_garbage_collection_test_interface.cc
index e854293..516292d 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_garbage_collection_test_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_garbage_collection_test_interface.cc
@@ -491,7 +491,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 22;
+  const int kInterfaceUniqueId = 23;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_global_interface_parent.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_global_interface_parent.cc
index a9d6b66..aae9abc 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_global_interface_parent.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_global_interface_parent.cc
@@ -314,7 +314,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 23;
+  const int kInterfaceUniqueId = 24;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_indexed_getter_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_indexed_getter_interface.cc
index 7957be4..3dbdd6f 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_indexed_getter_interface.cc
@@ -648,7 +648,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 25;
+  const int kInterfaceUniqueId = 26;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any.cc
index f59f815..9251dee 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any.cc
@@ -397,7 +397,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 26;
+  const int kInterfaceUniqueId = 27;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any_dictionary.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any_dictionary.cc
index 34cf85c..72f9597 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any_dictionary.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_any_dictionary.cc
@@ -507,7 +507,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 27;
+  const int kInterfaceUniqueId = 28;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_unsupported_properties.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_unsupported_properties.cc
index fd76bd8..1e9f8a3 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_unsupported_properties.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_interface_with_unsupported_properties.cc
@@ -319,7 +319,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 28;
+  const int kInterfaceUniqueId = 29;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_constructor_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_constructor_interface.cc
index b9f0a33..a045a97 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_constructor_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_constructor_interface.cc
@@ -275,7 +275,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 29;
+  const int kInterfaceUniqueId = 30;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_getter_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_getter_interface.cc
index a5fe033..0ab91ae 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_getter_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_getter_interface.cc
@@ -596,7 +596,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 30;
+  const int kInterfaceUniqueId = 31;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_indexed_getter_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_indexed_getter_interface.cc
index 3abd0a1..d306f6c 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_indexed_getter_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_named_indexed_getter_interface.cc
@@ -970,7 +970,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 31;
+  const int kInterfaceUniqueId = 32;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nested_put_forwards_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nested_put_forwards_interface.cc
index 148e851..1873dba 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nested_put_forwards_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nested_put_forwards_interface.cc
@@ -397,7 +397,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 32;
+  const int kInterfaceUniqueId = 33;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_constructor_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_constructor_interface.cc
index 4990467..242d0db 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_constructor_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_constructor_interface.cc
@@ -265,7 +265,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 33;
+  const int kInterfaceUniqueId = 34;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_interface_object_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_interface_object_interface.cc
index d9cf90e..ef77f03 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_interface_object_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_no_interface_object_interface.cc
@@ -228,7 +228,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 34;
+  const int kInterfaceUniqueId = 35;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nullable_types_test_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nullable_types_test_interface.cc
index 3ad3e5d..9608bc5 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nullable_types_test_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_nullable_types_test_interface.cc
@@ -1181,7 +1181,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 35;
+  const int kInterfaceUniqueId = 36;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_numeric_types_test_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_numeric_types_test_interface.cc
index 9dfd580..c366032 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_numeric_types_test_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_numeric_types_test_interface.cc
@@ -3393,7 +3393,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 36;
+  const int kInterfaceUniqueId = 37;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_object_type_bindings_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_object_type_bindings_interface.cc
index a04bd09..9ab8de4 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_object_type_bindings_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_object_type_bindings_interface.cc
@@ -649,7 +649,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 37;
+  const int kInterfaceUniqueId = 38;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_operations_test_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_operations_test_interface.cc
index 5232239..f8e366d 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_operations_test_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_operations_test_interface.cc
@@ -1817,7 +1817,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 38;
+  const int kInterfaceUniqueId = 39;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_promise_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_promise_interface.cc
index e4ef6d9..593cefe 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_promise_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_promise_interface.cc
@@ -534,7 +534,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 39;
+  const int kInterfaceUniqueId = 40;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_put_forwards_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_put_forwards_interface.cc
index a537335..c7ae942 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_put_forwards_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_put_forwards_interface.cc
@@ -456,7 +456,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 40;
+  const int kInterfaceUniqueId = 41;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_sequence_user.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_sequence_user.cc
index c499278..7416868 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_sequence_user.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_sequence_user.cc
@@ -1133,7 +1133,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 41;
+  const int kInterfaceUniqueId = 42;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_static_properties_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_static_properties_interface.cc
index dc0f2a4..b8b9c56 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_static_properties_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_static_properties_interface.cc
@@ -581,7 +581,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 43;
+  const int kInterfaceUniqueId = 44;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_anonymous_operation_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_anonymous_operation_interface.cc
index e1171ba..13b3bcf 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_anonymous_operation_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_anonymous_operation_interface.cc
@@ -326,7 +326,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 44;
+  const int kInterfaceUniqueId = 45;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_attribute_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_attribute_interface.cc
index a12de2b..78d93ea 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_attribute_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_attribute_interface.cc
@@ -432,7 +432,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 45;
+  const int kInterfaceUniqueId = 46;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_operation_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_operation_interface.cc
index 0a1bc87..14a2c07 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_operation_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_stringifier_operation_interface.cc
@@ -381,7 +381,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 46;
+  const int kInterfaceUniqueId = 47;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_target_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_target_interface.cc
index 8ac1392..1ebf396 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_target_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_target_interface.cc
@@ -363,7 +363,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 47;
+  const int kInterfaceUniqueId = 48;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
index 78401b1..7e2bae0 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_test_dictionary.cc
@@ -334,6 +334,18 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
+  if (!any_member_with_default.isUndefined()) {
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                any_member_with_default,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_any_member_with_default(&converted_value);
+  }
   JS::RootedValue any_member(context);
   if (!JS_GetProperty(context, dictionary_object,
                       "anyMember",
@@ -341,6 +353,18 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
+  if (!any_member.isUndefined()) {
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                any_member,
+                kNoConversionFlags,
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_any_member(&converted_value);
+  }
 }
 
 }  // namespace mozjs
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_union_types_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_union_types_interface.cc
index 5269ebc..2a312fd 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_union_types_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_union_types_interface.cc
@@ -697,7 +697,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 49;
+  const int kInterfaceUniqueId = 50;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
index b9cb145..0118eab 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_window.cc
@@ -956,7 +956,7 @@
 }
 
 inline InterfaceData* GetInterfaceData(JSContext* context) {
-  const int kInterfaceUniqueId = 51;
+  const int kInterfaceUniqueId = 52;
   MozjsGlobalEnvironment* global_environment =
       static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
   // By convention, the |MozjsGlobalEnvironment| that we are associated with
diff --git a/src/cobalt/bindings/mozjs/templates/dictionary-conversion.cc.template b/src/cobalt/bindings/mozjs/templates/dictionary-conversion.cc.template
index 3fb2623..d61a0ef 100644
--- a/src/cobalt/bindings/mozjs/templates/dictionary-conversion.cc.template
+++ b/src/cobalt/bindings/mozjs/templates/dictionary-conversion.cc.template
@@ -93,6 +93,10 @@
                  int conversion_flags, ExceptionState* exception_state,
                  {{class_name}}* out_dictionary) {
   DCHECK_EQ(0, conversion_flags) << "Unexpected conversion flags.";
+  {% if parent %}
+  FromJSValue(context, value, conversion_flags, exception_state,
+      static_cast<{{parent}}*>(out_dictionary));
+  {% endif %}
   // https://heycam.github.io/webidl/#es-dictionary
 
   if (value.isUndefined() || value.isNull()) {
@@ -115,8 +119,8 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
-{% if not member.is_script_value %}
   if (!{{member.name}}.isUndefined()) {
+  {% if not member.is_script_value %}
     {{member.type}} converted_value;
     FromJSValue(context,
                 {{member.name}},
@@ -127,8 +131,19 @@
       return;
     }
     out_dictionary->set_{{member.name}}(converted_value);
+  {% else %}
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                {{member.name}},
+                {{member.conversion_flags}},
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_{{member.name}}(&converted_value);
+  {% endif %}
   }
-{% endif %}
 {% endfor %}
 }
 
diff --git a/src/cobalt/bindings/mozjs45/templates/dictionary-conversion.cc.template b/src/cobalt/bindings/mozjs45/templates/dictionary-conversion.cc.template
index eefd158..6c46b81 100644
--- a/src/cobalt/bindings/mozjs45/templates/dictionary-conversion.cc.template
+++ b/src/cobalt/bindings/mozjs45/templates/dictionary-conversion.cc.template
@@ -93,6 +93,10 @@
                  int conversion_flags, ExceptionState* exception_state,
                  {{class_name}}* out_dictionary) {
   DCHECK_EQ(0, conversion_flags) << "Unexpected conversion flags.";
+  {% if parent %}
+  FromJSValue(context, value, conversion_flags, exception_state,
+      static_cast<{{parent}}*>(out_dictionary));
+  {% endif %}
   // https://heycam.github.io/webidl/#es-dictionary
 
   if (value.isUndefined() || value.isNull()) {
@@ -115,8 +119,8 @@
     exception_state->SetSimpleException(kSimpleError);
     return;
   }
-{% if not member.is_script_value %}
   if (!{{member.name}}.isUndefined()) {
+  {% if not member.is_script_value %}
     {{member.type}} converted_value;
     FromJSValue(context,
                 {{member.name}},
@@ -127,8 +131,19 @@
       return;
     }
     out_dictionary->set_{{member.name}}(converted_value);
+  {% else %}
+    TypeTraits<::cobalt::script::ValueHandle >::ConversionType converted_value;
+    FromJSValue(context,
+                {{member.name}},
+                {{member.conversion_flags}},
+                exception_state,
+                &converted_value);
+    if (context->isExceptionPending()) {
+      return;
+    }
+    out_dictionary->set_{{member.name}}(&converted_value);
+  {% endif %}
   }
-{% endif %}
 {% endfor %}
 }
 
diff --git a/src/cobalt/bindings/testing/derived_dictionary.idl b/src/cobalt/bindings/testing/derived_dictionary.idl
new file mode 100644
index 0000000..0dfea67
--- /dev/null
+++ b/src/cobalt/bindings/testing/derived_dictionary.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.
+ */
+
+dictionary DerivedDictionary : TestDictionary {
+  boolean additionalMember = false;
+};
diff --git a/src/cobalt/bindings/testing/dictionary_interface.h b/src/cobalt/bindings/testing/dictionary_interface.h
index 4d8debf..b43cfc7 100644
--- a/src/cobalt/bindings/testing/dictionary_interface.h
+++ b/src/cobalt/bindings/testing/dictionary_interface.h
@@ -19,6 +19,7 @@
 
 #include <string>
 
+#include "cobalt/bindings/testing/derived_dictionary.h"
 #include "cobalt/bindings/testing/dictionary_with_dictionary_member.h"
 #include "cobalt/bindings/testing/test_dictionary.h"
 #include "cobalt/script/sequence.h"
@@ -33,6 +34,8 @@
  public:
   MOCK_METHOD1(DictionaryOperation,
                void(const TestDictionary& test_dictionary));
+  MOCK_METHOD1(DerivedDictionaryOperation,
+               void(const DerivedDictionary& derived_dictionary));
 
   MOCK_METHOD0(dictionary_sequence, TestDictionary());
   MOCK_METHOD1(set_dictionary_sequence,
diff --git a/src/cobalt/bindings/testing/dictionary_interface.idl b/src/cobalt/bindings/testing/dictionary_interface.idl
index 80f4f21..07982cd 100644
--- a/src/cobalt/bindings/testing/dictionary_interface.idl
+++ b/src/cobalt/bindings/testing/dictionary_interface.idl
@@ -16,6 +16,7 @@
 
 interface DictionaryInterface {
   void dictionaryOperation(TestDictionary dictionary);
+  void derivedDictionaryOperation(DerivedDictionary dictionary);
   attribute sequence<TestDictionary> dictionarySequence;
 
   // Dictionary with dictionary member.
diff --git a/src/cobalt/bindings/testing/dictionary_test.cc b/src/cobalt/bindings/testing/dictionary_test.cc
index 210cc46..0f97d89 100644
--- a/src/cobalt/bindings/testing/dictionary_test.cc
+++ b/src/cobalt/bindings/testing/dictionary_test.cc
@@ -18,6 +18,7 @@
 
 #include "base/stringprintf.h"
 #include "cobalt/bindings/testing/bindings_test_base.h"
+#include "cobalt/bindings/testing/derived_dictionary.h"
 #include "cobalt/bindings/testing/dictionary_interface.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -39,24 +40,103 @@
       .WillOnce(SaveArg<0>(&dictionary));
   EXPECT_TRUE(EvaluateScript("test.dictionaryOperation({});", NULL));
   EXPECT_FALSE(dictionary.has_non_default_member());
-  EXPECT_TRUE(dictionary.has_member_with_default());
+  ASSERT_TRUE(dictionary.has_member_with_default());
   EXPECT_EQ(5, dictionary.member_with_default());
 
   EXPECT_CALL(test_mock(), DictionaryOperation(_))
       .WillOnce(SaveArg<0>(&dictionary));
   EXPECT_TRUE(EvaluateScript("test.dictionaryOperation(undefined);", NULL));
   EXPECT_FALSE(dictionary.has_non_default_member());
-  EXPECT_TRUE(dictionary.has_member_with_default());
+  ASSERT_TRUE(dictionary.has_member_with_default());
   EXPECT_EQ(5, dictionary.member_with_default());
 
   EXPECT_CALL(test_mock(), DictionaryOperation(_))
       .WillOnce(SaveArg<0>(&dictionary));
   EXPECT_TRUE(EvaluateScript("test.dictionaryOperation(null);", NULL));
   EXPECT_FALSE(dictionary.has_non_default_member());
-  EXPECT_TRUE(dictionary.has_member_with_default());
+  ASSERT_TRUE(dictionary.has_member_with_default());
   EXPECT_EQ(5, dictionary.member_with_default());
 }
 
+TEST_F(DictionaryTest, BooleanMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_boolean_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {booleanMember : true} );", NULL));
+  EXPECT_TRUE(dictionary.has_boolean_member());
+  EXPECT_TRUE(dictionary.boolean_member());
+
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {booleanMember : false} );", NULL));
+  ASSERT_TRUE(dictionary.has_boolean_member());
+  EXPECT_FALSE(dictionary.boolean_member());
+}
+
+TEST_F(DictionaryTest, ClampMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_short_clamp_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+
+  EXPECT_TRUE(EvaluateScript(
+      StringPrintf("test.dictionaryOperation( {shortClampMember : %d } );",
+                   std::numeric_limits<int32_t>::max()),
+      NULL));
+  ASSERT_TRUE(dictionary.has_short_clamp_member());
+  EXPECT_EQ(std::numeric_limits<int16_t>::max(),
+            dictionary.short_clamp_member());
+}
+
+TEST_F(DictionaryTest, LongMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_long_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {longMember : 123456} );", NULL));
+  ASSERT_TRUE(dictionary.has_long_member());
+  EXPECT_EQ(123456, dictionary.long_member());
+}
+
+TEST_F(DictionaryTest, DoubleMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_double_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {doubleMember : 123.456} );", NULL));
+  ASSERT_TRUE(dictionary.has_double_member());
+  EXPECT_EQ(123.456, dictionary.double_member());
+}
+
+TEST_F(DictionaryTest, StringMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_string_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {stringMember : 'Cobalt'} );", NULL));
+  ASSERT_TRUE(dictionary.has_string_member());
+  EXPECT_EQ("Cobalt", dictionary.string_member());
+}
+
+TEST_F(DictionaryTest, ArbitraryInterfaceMember) {
+  TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_interface_member());
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(
+      EvaluateScript("test.dictionaryOperation( {interfaceMember : new "
+                     "ArbitraryInterface()} );",
+                     NULL));
+  ASSERT_TRUE(dictionary.has_interface_member());
+  EXPECT_TRUE(dictionary.interface_member() != NULL);
+}
+
 TEST_F(DictionaryTest, OverrideDefault) {
   TestDictionary dictionary;
   EXPECT_CALL(test_mock(), DictionaryOperation(_))
@@ -66,17 +146,55 @@
   EXPECT_EQ(20, dictionary.member_with_default());
 }
 
-TEST_F(DictionaryTest, ClampDictionaryMember) {
+TEST_F(DictionaryTest, AnyMember) {
   TestDictionary dictionary;
+  EXPECT_FALSE(dictionary.has_any_member());
   EXPECT_CALL(test_mock(), DictionaryOperation(_))
       .WillOnce(SaveArg<0>(&dictionary));
-
   EXPECT_TRUE(EvaluateScript(
-      StringPrintf("test.dictionaryOperation( {shortClampMember : %d } );",
-                   std::numeric_limits<int32_t>::max()),
+      "test.dictionaryOperation( {anyMember : new ArbitraryInterface()} );",
       NULL));
-  EXPECT_EQ(std::numeric_limits<int16_t>::max(),
-            dictionary.short_clamp_member());
+  ASSERT_TRUE(dictionary.has_any_member());
+  EXPECT_TRUE(dictionary.any_member() != NULL);
+
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.dictionaryOperation( {anyMember : {'foo':'bar'}} );", NULL));
+  ASSERT_TRUE(dictionary.has_any_member());
+  EXPECT_TRUE(dictionary.any_member() != NULL);
+}
+
+TEST_F(DictionaryTest, OverrideAnyMemberWithDefault) {
+  TestDictionary dictionary;
+  EXPECT_TRUE(dictionary.has_any_member_with_default());
+  EXPECT_TRUE(dictionary.any_member_with_default() == NULL);
+  EXPECT_CALL(test_mock(), DictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(
+      EvaluateScript("test.dictionaryOperation( {anyMemberWithDefault : new "
+                     "ArbitraryInterface()} );",
+                     NULL));
+  ASSERT_TRUE(dictionary.has_any_member_with_default());
+  EXPECT_TRUE(dictionary.any_member_with_default() != NULL);
+}
+
+TEST_F(DictionaryTest, DerivedDictionaryMember) {
+  DerivedDictionary dictionary;
+  EXPECT_CALL(test_mock(), DerivedDictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.derivedDictionaryOperation( {additionalMember : true} );", NULL));
+  EXPECT_TRUE(dictionary.additional_member());
+}
+
+TEST_F(DictionaryTest, BaseDictionaryMemberOfDerivedDictionary) {
+  DerivedDictionary dictionary;
+  EXPECT_CALL(test_mock(), DerivedDictionaryOperation(_))
+      .WillOnce(SaveArg<0>(&dictionary));
+  EXPECT_TRUE(EvaluateScript(
+      "test.derivedDictionaryOperation( {memberWithDefault : 25} );", NULL));
+  EXPECT_EQ(25, dictionary.member_with_default());
 }
 
 }  // namespace testing
diff --git a/src/cobalt/bindings/testing/testing.gyp b/src/cobalt/bindings/testing/testing.gyp
index 2ef0d01..2a3216e 100644
--- a/src/cobalt/bindings/testing/testing.gyp
+++ b/src/cobalt/bindings/testing/testing.gyp
@@ -76,6 +76,7 @@
     ],
 
     'generated_header_idl_files': [
+        'derived_dictionary.idl',
         'dictionary_with_dictionary_member.idl',
         'test_dictionary.idl',
         'test_enum.idl'
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 92f4157..cf2ba72 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -262,6 +262,7 @@
         '../dom/element_css_inline_style.idl',
         '../dom/element_cssom_view.idl',
         '../dom/element_dom_parsing_and_serialization.idl',
+        '../dom/element_pointer_events.idl',
         '../dom/global_crypto.idl',
         '../dom/global_event_handlers.idl',
         '../dom/html_element_cssom_view.idl',
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 63abd63..87fc9f2 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -341,8 +341,7 @@
   // Used for DOM node highlighting and overlay messages.
   scoped_ptr<debug::RenderOverlay> debug_overlay_;
 
-  // The core of the debugging system, described here:
-  // https://docs.google.com/document/d/1lZhrBTusQZJsacpt21J3kPgnkj7pyQObhFqYktvm40Y
+  // The core of the debugging system.
   // Created lazily when accessed via |GetDebugServer|.
   scoped_ptr<debug::DebugServerModule> debug_server_module_;
 #endif  // ENABLE_DEBUG_CONSOLE
@@ -1235,8 +1234,6 @@
   // 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::Start,
                             base::Unretained(impl_.get()), resource_provider));
@@ -1257,8 +1254,6 @@
   // 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::Unpause, base::Unretained(impl_.get())));
@@ -1287,8 +1282,6 @@
   // 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));
@@ -1299,7 +1292,7 @@
   const scoped_refptr<dom::Document>& document = window_->document();
   scoped_refptr<dom::Event> event;
   do {
-    event = document->GetNextQueuedPointerEvent();
+    event = document->pointer_state()->GetNextQueuedPointerEvent();
     if (event) {
       SB_DCHECK(
           window_ ==
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 06db324..3c54c85 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-89101
\ No newline at end of file
+90790
\ No newline at end of file
diff --git a/src/cobalt/cssom/css_font_face_rule_test.cc b/src/cobalt/cssom/css_font_face_rule_test.cc
index 8ee956d..3980882 100644
--- a/src/cobalt/cssom/css_font_face_rule_test.cc
+++ b/src/cobalt/cssom/css_font_face_rule_test.cc
@@ -47,17 +47,17 @@
 
 class CSSFontFaceRuleTest : public ::testing::Test {
  protected:
-  CSSFontFaceRuleTest()
-      : style_sheet_list_(new StyleSheetList(&mutation_observer_)),
-        css_style_sheet_(new CSSStyleSheet(&css_parser_)) {
-    css_style_sheet_->AttachToStyleSheetList(style_sheet_list_);
+  CSSFontFaceRuleTest() : css_style_sheet_(new CSSStyleSheet(&css_parser_)) {
+    StyleSheetVector style_sheets;
+    style_sheets.push_back(css_style_sheet_);
+    style_sheet_list_ = new StyleSheetList(style_sheets, &mutation_observer_);
   }
   ~CSSFontFaceRuleTest() OVERRIDE {}
 
-  const scoped_refptr<StyleSheetList> style_sheet_list_;
   const scoped_refptr<CSSStyleSheet> css_style_sheet_;
-  testing::MockCSSParser css_parser_;
+  scoped_refptr<StyleSheetList> style_sheet_list_;
   MockMutationObserver mutation_observer_;
+  testing::MockCSSParser css_parser_;
 };
 
 TEST_F(CSSFontFaceRuleTest, PropertyValueSetter) {
diff --git a/src/cobalt/cssom/css_grouping_rule_test.cc b/src/cobalt/cssom/css_grouping_rule_test.cc
index 69952ba..1fee788 100644
--- a/src/cobalt/cssom/css_grouping_rule_test.cc
+++ b/src/cobalt/cssom/css_grouping_rule_test.cc
@@ -48,7 +48,7 @@
 class CSSGroupingRuleTest : public ::testing::Test {
  protected:
   CSSGroupingRuleTest()
-      : style_sheet_list_(new StyleSheetList(NULL)),
+      : style_sheet_list_(new StyleSheetList()),
         css_style_sheet_(new CSSStyleSheet(&css_parser_)),
         css_grouping_rule_(new FakeCSSGroupingRule()) {
     css_style_sheet_->AttachToStyleSheetList(style_sheet_list_);
diff --git a/src/cobalt/cssom/css_style_sheet.cc b/src/cobalt/cssom/css_style_sheet.cc
index 76ac8ef..4318abb 100644
--- a/src/cobalt/cssom/css_style_sheet.cc
+++ b/src/cobalt/cssom/css_style_sheet.cc
@@ -103,6 +103,10 @@
   parent_style_sheet_list_ = style_sheet_list;
 }
 
+void CSSStyleSheet::DetachFromStyleSheetList() {
+  parent_style_sheet_list_ = NULL;
+}
+
 void CSSStyleSheet::set_css_rules(
     const scoped_refptr<CSSRuleList>& css_rule_list) {
   DCHECK(css_rule_list);
diff --git a/src/cobalt/cssom/css_style_sheet.h b/src/cobalt/cssom/css_style_sheet.h
index 4f843ff..aefa0de 100644
--- a/src/cobalt/cssom/css_style_sheet.h
+++ b/src/cobalt/cssom/css_style_sheet.h
@@ -58,18 +58,18 @@
   //
   // From StyleSheet.
   void AttachToStyleSheetList(StyleSheetList* style_sheet_list) OVERRIDE;
-  void SetLocationUrl(const GURL& url) OVERRIDE { location_url_ = url; }
-  const GURL& LocationUrl() const OVERRIDE { return location_url_; }
+  void DetachFromStyleSheetList() OVERRIDE;
   StyleSheetList* ParentStyleSheetList() const OVERRIDE {
     return parent_style_sheet_list_;
   }
+  void SetLocationUrl(const GURL& url) OVERRIDE { location_url_ = url; }
+  const GURL& LocationUrl() const OVERRIDE { return location_url_; }
   scoped_refptr<CSSStyleSheet> AsCSSStyleSheet() OVERRIDE { return this; }
 
   // From MutationObserver.
   void OnCSSMutation() OVERRIDE;
 
   CSSParser* css_parser() const { return css_parser_; }
-
   void set_css_rules(const scoped_refptr<CSSRuleList>& css_rule_list);
 
   Origin origin() const { return origin_; }
diff --git a/src/cobalt/cssom/css_style_sheet_test.cc b/src/cobalt/cssom/css_style_sheet_test.cc
index cb61d05..e817f48 100644
--- a/src/cobalt/cssom/css_style_sheet_test.cc
+++ b/src/cobalt/cssom/css_style_sheet_test.cc
@@ -45,16 +45,16 @@
 
 class CSSStyleSheetTest : public ::testing::Test {
  protected:
-  CSSStyleSheetTest()
-      : style_sheet_list_(new StyleSheetList(&mutation_observer_)),
-        css_style_sheet_(new CSSStyleSheet(&css_parser_)) {
-    css_style_sheet_->AttachToStyleSheetList(style_sheet_list_);
+  CSSStyleSheetTest() : css_style_sheet_(new CSSStyleSheet(&css_parser_)) {
+    StyleSheetVector style_sheets;
+    style_sheets.push_back(css_style_sheet_);
+    style_sheet_list_ = new StyleSheetList(style_sheets, &mutation_observer_);
   }
   ~CSSStyleSheetTest() OVERRIDE {}
 
-  MockMutationObserver mutation_observer_;
-  const scoped_refptr<StyleSheetList> style_sheet_list_;
   const scoped_refptr<CSSStyleSheet> css_style_sheet_;
+  scoped_refptr<StyleSheetList> style_sheet_list_;
+  MockMutationObserver mutation_observer_;
   testing::MockCSSParser css_parser_;
 };
 
diff --git a/src/cobalt/cssom/style_sheet.h b/src/cobalt/cssom/style_sheet.h
index b8030e5..2823a1c 100644
--- a/src/cobalt/cssom/style_sheet.h
+++ b/src/cobalt/cssom/style_sheet.h
@@ -38,9 +38,10 @@
   void set_index(int index) { index_ = index; }
 
   virtual void AttachToStyleSheetList(StyleSheetList* style_sheet_list) = 0;
+  virtual void DetachFromStyleSheetList() = 0;
+  virtual StyleSheetList* ParentStyleSheetList() const = 0;
   virtual void SetLocationUrl(const GURL& url) = 0;
   virtual const GURL& LocationUrl() const = 0;
-  virtual StyleSheetList* ParentStyleSheetList() const = 0;
   virtual scoped_refptr<CSSStyleSheet> AsCSSStyleSheet() = 0;
 
   DEFINE_WRAPPABLE_TYPE(StyleSheet);
diff --git a/src/cobalt/cssom/style_sheet_list.cc b/src/cobalt/cssom/style_sheet_list.cc
index 1be9b43..b3d36e3 100644
--- a/src/cobalt/cssom/style_sheet_list.cc
+++ b/src/cobalt/cssom/style_sheet_list.cc
@@ -21,8 +21,18 @@
 namespace cobalt {
 namespace cssom {
 
-StyleSheetList::StyleSheetList(MutationObserver* observer)
-    : mutation_observer_(observer) {}
+StyleSheetList::StyleSheetList() : mutation_observer_(NULL) {}
+
+StyleSheetList::StyleSheetList(const StyleSheetVector& style_sheets,
+                               MutationObserver* observer)
+    : style_sheets_(style_sheets), mutation_observer_(observer) {
+  for (StyleSheetVector::const_iterator iter = style_sheets_.begin();
+       iter != style_sheets_.end(); ++iter) {
+    StyleSheet* style_sheet = *iter;
+    style_sheet->AttachToStyleSheetList(this);
+    style_sheet->set_index(static_cast<int>(style_sheets_.size()));
+  }
+}
 
 scoped_refptr<StyleSheet> StyleSheetList::Item(unsigned int index) const {
   return index < style_sheets_.size() ? style_sheets_[index] : NULL;
@@ -39,16 +49,15 @@
   }
 }
 
-void StyleSheetList::Append(const scoped_refptr<StyleSheet>& style_sheet) {
-  style_sheet->AttachToStyleSheetList(this);
-  style_sheet->set_index(static_cast<int>(style_sheets_.size()));
-  style_sheets_.push_back(style_sheet);
-  if (mutation_observer_) {
-    mutation_observer_->OnCSSMutation();
+StyleSheetList::~StyleSheetList() {
+  for (StyleSheetVector::const_iterator iter = style_sheets_.begin();
+       iter != style_sheets_.end(); ++iter) {
+    StyleSheet* style_sheet = *iter;
+    if (style_sheet->ParentStyleSheetList() == this) {
+      style_sheet->DetachFromStyleSheetList();
+    }
   }
 }
 
-StyleSheetList::~StyleSheetList() {}
-
 }  // namespace cssom
 }  // namespace cobalt
diff --git a/src/cobalt/cssom/style_sheet_list.h b/src/cobalt/cssom/style_sheet_list.h
index e4a8d6f..cf6f968 100644
--- a/src/cobalt/cssom/style_sheet_list.h
+++ b/src/cobalt/cssom/style_sheet_list.h
@@ -28,13 +28,16 @@
 
 class StyleSheet;
 
+typedef std::vector<scoped_refptr<StyleSheet> > StyleSheetVector;
+
 // The StyleSheetList interface represents an ordered collection of CSS
 // style sheets.
 //   https://www.w3.org/TR/2013/WD-cssom-20131205/#the-stylesheetlist-interface
 class StyleSheetList : public script::Wrappable, public MutationObserver {
  public:
-  // If no layout mutation reporting needed, |observer| can be null.
-  explicit StyleSheetList(MutationObserver* observer);
+  StyleSheetList();
+  StyleSheetList(const StyleSheetVector& style_sheets,
+                 MutationObserver* observer);
 
   // Web API: StyleSheetList
   //
@@ -51,8 +54,6 @@
   // From MutationObserver.
   void OnCSSMutation() OVERRIDE;
 
-  void Append(const scoped_refptr<StyleSheet>& style_sheet);
-
   MutationObserver* mutation_observer() const { return mutation_observer_; }
 
   DEFINE_WRAPPABLE_TYPE(StyleSheetList);
@@ -60,7 +61,7 @@
  private:
   ~StyleSheetList();
 
-  std::vector<scoped_refptr<StyleSheet> > style_sheets_;
+  const StyleSheetVector style_sheets_;
   MutationObserver* const mutation_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(StyleSheetList);
diff --git a/src/cobalt/cssom/style_sheet_list_test.cc b/src/cobalt/cssom/style_sheet_list_test.cc
index d524446..3a0fce2 100644
--- a/src/cobalt/cssom/style_sheet_list_test.cc
+++ b/src/cobalt/cssom/style_sheet_list_test.cc
@@ -21,15 +21,21 @@
 namespace cssom {
 
 TEST(StyleSheetListTest, ItemAccess) {
-  scoped_refptr<StyleSheetList> style_sheet_list = new StyleSheetList(NULL);
+  scoped_refptr<StyleSheetList> style_sheet_list = new StyleSheetList();
   ASSERT_EQ(0, style_sheet_list->length());
   ASSERT_FALSE(style_sheet_list->Item(0).get());
 
   scoped_refptr<CSSStyleSheet> style_sheet = new CSSStyleSheet();
-  style_sheet_list->Append(style_sheet);
+  StyleSheetVector style_sheet_vector;
+  style_sheet_vector.push_back(style_sheet);
+  style_sheet_list = new StyleSheetList(style_sheet_vector, NULL);
   ASSERT_EQ(1, style_sheet_list->length());
   ASSERT_EQ(style_sheet, style_sheet_list->Item(0));
   ASSERT_FALSE(style_sheet_list->Item(1).get());
+  ASSERT_EQ(style_sheet->ParentStyleSheetList(), style_sheet_list);
+
+  style_sheet_list = NULL;
+  ASSERT_FALSE(style_sheet->ParentStyleSheetList());
 }
 
 }  // namespace cssom
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index d0a5b39..ff262c6 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -54,10 +54,8 @@
 #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"
@@ -71,10 +69,10 @@
       html_element_context_(html_element_context),
       window_(options.window),
       implementation_(new DOMImplementation(html_element_context)),
-      ALLOW_THIS_IN_INITIALIZER_LIST(
-          style_sheets_(new cssom::StyleSheetList(this))),
+      style_sheets_(new cssom::StyleSheetList()),
       loading_counter_(0),
       should_dispatch_load_event_(true),
+      are_style_sheets_dirty_(true),
       is_selector_tree_dirty_(true),
       is_computed_style_dirty_(true),
       are_font_faces_dirty_(true),
@@ -367,6 +365,11 @@
   return indicated_element_.get();
 }
 
+const scoped_refptr<cssom::StyleSheetList>& Document::style_sheets() {
+  UpdateStyleSheets();
+  return style_sheets_;
+}
+
 void Document::set_cookie(const std::string& cookie) {
 #if defined(COBALT_BUILD_TYPE_GOLD)
   UNREFERENCED_PARAMETER(cookie);
@@ -498,6 +501,11 @@
   FOR_EACH_OBSERVER(DocumentObserver, observers_, OnFocusChanged());
 }
 
+void Document::OnStyleSheetsModified() {
+  are_style_sheets_dirty_ = true;
+  OnCSSMutation();
+}
+
 void Document::OnCSSMutation() {
   // Something in the document's CSS rules has been modified, but we don't know
   // what, so set the flag indicating that rule matching needs to be done.
@@ -775,6 +783,7 @@
   if (is_selector_tree_dirty_) {
     TRACE_EVENT0("cobalt::dom", kBenchmarkStatUpdateSelectorTree);
 
+    UpdateStyleSheets();
     UpdateMediaRules();
 
     if (user_agent_style_sheet_) {
@@ -858,43 +867,6 @@
   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()");
 
@@ -925,6 +897,25 @@
   SignalOnLoadToObservers();
 }
 
+void Document::UpdateStyleSheets() {
+  if (are_style_sheets_dirty_) {
+    // "Each Document has an associated list of zero or more CSS style sheets,
+    // named the document CSS style sheets. This is an ordered list that
+    // contains all CSS style sheets associated with the Document, in tree
+    // order..."
+    // https://drafts.csswg.org/cssom/#document-css-style-sheets
+    // See also:
+    //   https://www.w3.org/TR/html4/present/styles.html#h-14.4
+    cssom::StyleSheetVector style_sheet_vector;
+    for (Element* child = first_element_child(); child;
+         child = child->next_element_sibling()) {
+      child->CollectStyleSheetsOfElementAndDescendants(&style_sheet_vector);
+    }
+    style_sheets_ = new cssom::StyleSheetList(style_sheet_vector, this);
+    are_style_sheets_dirty_ = false;
+  }
+}
+
 void Document::UpdateMediaRules() {
   TRACE_EVENT0("cobalt::dom", "Document::UpdateMediaRules()");
   if (viewport_size_) {
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index 2995d8b..d0f8853 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
@@ -39,12 +40,14 @@
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/location.h"
 #include "cobalt/dom/node.h"
+#include "cobalt/dom/pointer_state.h"
 #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 "cobalt/script/wrappable.h"
 #include "googleurl/src/gurl.h"
 
 namespace cobalt {
@@ -203,9 +206,7 @@
 
   // Web API: CSS Object Model (partial interface)
   //   http://dev.w3.org/csswg/cssom/#extensions-to-the-document-interface
-  const scoped_refptr<cssom::StyleSheetList>& style_sheets() const {
-    return style_sheets_;
-  }
+  const scoped_refptr<cssom::StyleSheetList>& style_sheets();
 
   // Web Animations API
   // https://www.w3.org/TR/2015/WD-web-animations-1-20150707/#extensions-to-the-document-interface
@@ -292,6 +293,9 @@
   // focus is shifted from one element to another.
   void OnFocusChange();
 
+  // Called when the DOM style sheets changed.
+  void OnStyleSheetsModified();
+
   // From cssom::MutationObserver.
   void OnCSSMutation() OVERRIDE;
 
@@ -384,11 +388,7 @@
 
   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();
+  PointerState* pointer_state() { return &pointer_state_; }
 
   DEFINE_WRAPPABLE_TYPE(Document);
 
@@ -406,6 +406,9 @@
  private:
   void DispatchOnLoadEvent();
 
+  // Updates the style sheets in the document.
+  void UpdateStyleSheets();
+
   // Updates the media rules in all the style sheets in the document.
   void UpdateMediaRules();
 
@@ -432,6 +435,9 @@
   int loading_counter_;
   // Whether the load event should be dispatched when loading counter hits zero.
   bool should_dispatch_load_event_;
+  // Indicates if the document's style sheets need to be re-collected before
+  // the next layout.
+  bool are_style_sheets_dirty_;
   // Indicates if rule matching/computed style is dirty and needs to be
   // recomputed before the next layout.
   bool is_selector_tree_dirty_;
@@ -482,7 +488,8 @@
   // The max depth of elements that are guaranteed to be rendered.
   int dom_max_element_depth_;
 
-  std::queue<scoped_refptr<Event> > pointer_events_;
+  // Various state related to pointer and mouse support.
+  PointerState pointer_state_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/document_test.cc b/src/cobalt/dom/document_test.cc
index b9e82b2..2a4ee06 100644
--- a/src/cobalt/dom/document_test.cc
+++ b/src/cobalt/dom/document_test.cc
@@ -335,7 +335,7 @@
   EXPECT_TRUE(document->style_sheets()->Item(1));
   EXPECT_TRUE(document->style_sheets()->Item(2));
 
-  // Each style sheet shoult represent the one from the corresponding style
+  // Each style sheet should represent the one from the corresponding style
   // element.
   EXPECT_EQ(document->style_sheets()->Item(0),
             element1->AsHTMLStyleElement()->sheet());
@@ -353,6 +353,51 @@
             document->style_sheets()->Item(2));
 }
 
+TEST_F(DocumentTest, StyleSheetsAddedToFront) {
+  scoped_refptr<Document> document = new Document(&html_element_context_);
+
+  scoped_refptr<HTMLElement> element1 =
+      html_element_context_.html_element_factory()->CreateHTMLElement(
+          document, base::Token(HTMLStyleElement::kTagName));
+  element1->set_text_content(std::string("body { background-color: #D3D3D3 }"));
+  document->AppendChild(element1);
+
+  scoped_refptr<HTMLElement> element2 =
+      html_element_context_.html_element_factory()->CreateHTMLElement(
+          document, base::Token(HTMLStyleElement::kTagName));
+  element2->set_text_content(std::string("h1 { color: #00F }"));
+  document->InsertBefore(element2, element1);
+
+  scoped_refptr<HTMLElement> element3 =
+      html_element_context_.html_element_factory()->CreateHTMLElement(
+          document, base::Token(HTMLStyleElement::kTagName));
+  element3->set_text_content(std::string("p { color: #008000 }"));
+  document->InsertBefore(element3, element2);
+
+  EXPECT_TRUE(document->style_sheets());
+  EXPECT_EQ(3, document->style_sheets()->length());
+  EXPECT_TRUE(document->style_sheets()->Item(0));
+  EXPECT_TRUE(document->style_sheets()->Item(1));
+  EXPECT_TRUE(document->style_sheets()->Item(2));
+
+  // Each style sheet should represent the one from the corresponding style
+  // element.
+  EXPECT_EQ(document->style_sheets()->Item(0),
+            element3->AsHTMLStyleElement()->sheet());
+  EXPECT_EQ(document->style_sheets()->Item(1),
+            element2->AsHTMLStyleElement()->sheet());
+  EXPECT_EQ(document->style_sheets()->Item(2),
+            element1->AsHTMLStyleElement()->sheet());
+
+  // Each style sheet should be unique.
+  EXPECT_NE(document->style_sheets()->Item(0),
+            document->style_sheets()->Item(1));
+  EXPECT_NE(document->style_sheets()->Item(0),
+            document->style_sheets()->Item(2));
+  EXPECT_NE(document->style_sheets()->Item(1),
+            document->style_sheets()->Item(2));
+}
+
 TEST_F(DocumentTest, HtmlElement) {
   scoped_refptr<Document> document = new Document(&html_element_context_);
   EXPECT_FALSE(document->html());
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp
index 5711b9b..88ef23d 100644
--- a/src/cobalt/dom/dom.gyp
+++ b/src/cobalt/dom/dom.gyp
@@ -212,6 +212,8 @@
         'plugin_array.h',
         'pointer_event.cc',
         'pointer_event.h',
+        'pointer_state.cc',
+        'pointer_state.h',
         'progress_event.cc',
         'progress_event.h',
         'pseudo_element.cc',
diff --git a/src/cobalt/dom/dom_exception.cc b/src/cobalt/dom/dom_exception.cc
index 3785e5c..e299285 100644
--- a/src/cobalt/dom/dom_exception.cc
+++ b/src/cobalt/dom/dom_exception.cc
@@ -45,17 +45,31 @@
       return "QuotaExceededError";
     case DOMException::kReadOnlyErr:
       return "ReadOnlyError";
+    case DOMException::kInvalidPointerIdErr:
+      return "InvalidPointerId";
   }
   NOTREACHED();
   return "";
 }
+
+// The code attribute's getter must return the legacy code indicated in the
+// error names table for this DOMException object's name, or 0 if no such entry
+// exists in the table.
+//   https://heycam.github.io/webidl/#dom-domexception-code
+DOMException::ExceptionCode GetLegacyCodeValue(
+    DOMException::ExceptionCode value) {
+  return value > DOMException::kHighestErrCodeValue ? DOMException::kNone
+                                                    : value;
+}
 }  // namespace
 
 DOMException::DOMException(ExceptionCode code)
-    : code_(code), name_(GetErrorName(code)) {}
+    : code_(GetLegacyCodeValue(code)), name_(GetErrorName(code)) {}
 
 DOMException::DOMException(ExceptionCode code, const std::string& message)
-    : code_(code), name_(GetErrorName(code)), message_(message) {}
+    : code_(GetLegacyCodeValue(code)),
+      name_(GetErrorName(code)),
+      message_(message) {}
 
 // static
 void DOMException::Raise(ExceptionCode code,
diff --git a/src/cobalt/dom/dom_exception.h b/src/cobalt/dom/dom_exception.h
index 1c9ef95..ad45aca 100644
--- a/src/cobalt/dom/dom_exception.h
+++ b/src/cobalt/dom/dom_exception.h
@@ -43,7 +43,9 @@
     kTypeMismatchErr = 17,
     kSecurityErr = 18,
     kQuotaExceededErr = 22,
-    kReadOnlyErr
+    kHighestErrCodeValue = kQuotaExceededErr,
+    kReadOnlyErr,
+    kInvalidPointerIdErr
   };
 
   explicit DOMException(ExceptionCode code);
diff --git a/src/cobalt/dom/element.cc b/src/cobalt/dom/element.cc
index d961ebc..30330d9 100644
--- a/src/cobalt/dom/element.cc
+++ b/src/cobalt/dom/element.cc
@@ -33,6 +33,7 @@
 #include "cobalt/dom/mutation_reporter.h"
 #include "cobalt/dom/named_node_map.h"
 #include "cobalt/dom/parser.h"
+#include "cobalt/dom/pointer_state.h"
 #include "cobalt/dom/serializer.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/math/rect_f.h"
@@ -553,6 +554,32 @@
   }
 }
 
+void Element::SetPointerCapture(int pointer_id,
+                                script::ExceptionState* exception_state) {
+  Document* document = node_document();
+  if (document) {
+    document->pointer_state()->SetPointerCapture(pointer_id, this,
+                                                 exception_state);
+  }
+}
+
+void Element::ReleasePointerCapture(int pointer_id,
+                                    script::ExceptionState* exception_state) {
+  Document* document = node_document();
+  if (document) {
+    document->pointer_state()->ReleasePointerCapture(pointer_id, this,
+                                                     exception_state);
+  }
+}
+
+bool Element::HasPointerCapture(int pointer_id) {
+  Document* document = node_document();
+  if (document) {
+    document->pointer_state()->HasPointerCapture(pointer_id, this);
+  }
+  return false;
+}
+
 void Element::Accept(NodeVisitor* visitor) { visitor->Visit(this); }
 
 void Element::Accept(ConstNodeVisitor* visitor) const { visitor->Visit(this); }
@@ -596,6 +623,16 @@
   attribute_map_.erase(kStyleAttributeName);
 }
 
+void Element::CollectStyleSheetsOfElementAndDescendants(
+    cssom::StyleSheetVector* style_sheets) const {
+  CollectStyleSheet(style_sheets);
+
+  for (Element* child = first_element_child(); child;
+       child = child->next_element_sibling()) {
+    child->CollectStyleSheetsOfElementAndDescendants(style_sheets);
+  }
+}
+
 scoped_refptr<HTMLElement> Element::AsHTMLElement() { return NULL; }
 
 Element::~Element() { --(element_count_log.Get().count); }
diff --git a/src/cobalt/dom/element.h b/src/cobalt/dom/element.h
index 337d467..0883c59 100644
--- a/src/cobalt/dom/element.h
+++ b/src/cobalt/dom/element.h
@@ -23,6 +23,8 @@
 #include "base/optional.h"
 #include "base/string_piece.h"
 #include "cobalt/base/token.h"
+#include "cobalt/cssom/style_sheet_list.h"
+#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/node.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/web_animations/animation_set.h"
@@ -115,6 +117,15 @@
   void set_outer_html(const std::string& outer_html,
                       script::ExceptionState* exception_state);
 
+  // Web API: Pointer Events: Extensions to the Element Interface (partial
+  // interface)
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#extensions-to-the-element-interface
+  void SetPointerCapture(int32_t pointer_id,
+                         script::ExceptionState* exception_state);
+  void ReleasePointerCapture(int32_t pointer_id,
+                             script::ExceptionState* exception_state);
+  bool HasPointerCapture(int32_t pointer_id);
+
   // Custom, not in any spec: Node.
   //
   Element* AsElement() OVERRIDE { return this; }
@@ -155,6 +166,12 @@
   virtual void SetStyleAttribute(const std::string& value);
   virtual void RemoveStyleAttribute();
 
+  // Adds all style sheets contained within the this element and its descendants
+  // to the style sheet vector. The style sheets are added in depth-first
+  // pre-order.
+  void CollectStyleSheetsOfElementAndDescendants(
+      cssom::StyleSheetVector* style_sheets) const;
+
   virtual scoped_refptr<HTMLElement> AsHTMLElement();
 
   const scoped_refptr<web_animations::AnimationSet>& animations() {
@@ -183,6 +200,12 @@
                               const std::string& /* value */) {}
   virtual void OnRemoveAttribute(const std::string& /* name */) {}
 
+  // Adds this element's style sheet to the style sheet vector. By default, this
+  // function does nothing, but is implemented by element subclasses that
+  // generate style sheets (HTMLStyleElement and HTMLLinkElement).
+  virtual void CollectStyleSheet(
+      cssom::StyleSheetVector* /*style_sheets*/) const {}
+
   // Callback for error when parsing inner / outer HTML.
   void HTMLParseError(const std::string& error);
 
diff --git a/src/cobalt/dom/element_pointer_events.idl b/src/cobalt/dom/element_pointer_events.idl
new file mode 100644
index 0000000..a415c77
--- /dev/null
+++ b/src/cobalt/dom/element_pointer_events.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/2015/REC-pointerevents-20150224/#extensions-to-the-element-interface
+// https://w3c.github.io/pointerevents/#extensions-to-the-element-interface
+
+partial interface Element {
+    attribute EventHandler ongotpointercapture;
+    attribute EventHandler onlostpointercapture;
+
+    [RaisesException] void setPointerCapture (long pointerId);
+    [RaisesException] void releasePointerCapture (long pointerId);
+    boolean hasPointerCapture (long pointerId);
+};
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index df9e286..896667c 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -237,6 +237,23 @@
     SetAttributeEventListener(base::Tokens::resize(), event_listener);
   }
 
+  const EventListenerScriptValue* ongotpointercapture() {
+    return GetAttributeEventListener(base::Tokens::gotpointercapture());
+  }
+  void set_ongotpointercapture(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::gotpointercapture(),
+                              event_listener);
+  }
+
+  const EventListenerScriptValue* onlostpointercapture() {
+    return GetAttributeEventListener(base::Tokens::lostpointercapture());
+  }
+  void set_onlostpointercapture(
+      const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::lostpointercapture(),
+                              event_listener);
+  }
+
   const EventListenerScriptValue* onpointerdown() {
     return GetAttributeEventListener(base::Tokens::pointerdown());
   }
diff --git a/src/cobalt/dom/global_event_handlers.idl b/src/cobalt/dom/global_event_handlers.idl
index 31627a2..372f2f7 100644
--- a/src/cobalt/dom/global_event_handlers.idl
+++ b/src/cobalt/dom/global_event_handlers.idl
@@ -46,6 +46,8 @@
 
   // Extensions for the Pointer Events recommendation.
   //  https://www.w3.org/TR/2015/REC-pointerevents-20150224/#extensions-to-the-globaleventhandlers-interface
+  attribute EventHandler ongotpointercapture;
+  attribute EventHandler onlostpointercapture;
   attribute EventHandler onpointerdown;
   attribute EventHandler onpointerenter;
   attribute EventHandler onpointerleave;
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc
index 8c09292..224cc16 100644
--- a/src/cobalt/dom/html_link_element.cc
+++ b/src/cobalt/dom/html_link_element.cc
@@ -66,6 +66,17 @@
   }
 }
 
+void HTMLLinkElement::OnRemovedFromDocument() {
+  HTMLElement::OnRemovedFromDocument();
+
+  if (style_sheet_) {
+    Document* document = node_document();
+    if (document) {
+      document->OnStyleSheetsModified();
+    }
+  }
+}
+
 void HTMLLinkElement::ResolveAndSetAbsoluteURL() {
   // Resolve the URL given by the href attribute, relative to the element.
   const GURL& base_url = node_document()->url_as_gurl();
@@ -204,11 +215,11 @@
 
 void HTMLLinkElement::OnStylesheetLoaded(Document* document,
                                          const std::string& content) {
-  scoped_refptr<cssom::CSSStyleSheet> style_sheet =
+  style_sheet_ =
       document->html_element_context()->css_parser()->ParseStyleSheet(
           content, base::SourceLocation(href(), 1, 1));
-  style_sheet->SetLocationUrl(absolute_url_);
-  document->style_sheets()->Append(style_sheet);
+  style_sheet_->SetLocationUrl(absolute_url_);
+  document->OnStyleSheetsModified();
 }
 
 void HTMLLinkElement::ReleaseLoader() {
@@ -217,5 +228,12 @@
   loader_.reset();
 }
 
+void HTMLLinkElement::CollectStyleSheet(
+    cssom::StyleSheetVector* style_sheets) const {
+  if (style_sheet_) {
+    style_sheets->push_back(style_sheet_);
+  }
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/html_link_element.h b/src/cobalt/dom/html_link_element.h
index c0abd2c..1390480 100644
--- a/src/cobalt/dom/html_link_element.h
+++ b/src/cobalt/dom/html_link_element.h
@@ -20,6 +20,7 @@
 
 #include "base/memory/scoped_ptr.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/cssom/style_sheet.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader.h"
@@ -57,6 +58,7 @@
 
   // From Node.
   void OnInsertedIntoDocument() OVERRIDE;
+  void OnRemovedFromDocument() OVERRIDE;
 
   DEFINE_WRAPPABLE_TYPE(HTMLLinkElement);
 
@@ -74,6 +76,9 @@
   void OnStylesheetLoaded(Document* document, const std::string& content);
   void ReleaseLoader();
 
+  // Add this element's style sheet to the style sheet vector.
+  void CollectStyleSheet(cssom::StyleSheetVector* style_sheets) const OVERRIDE;
+
   // Thread checker ensures all calls to DOM element are made from the same
   // thread that it is created in.
   base::ThreadChecker thread_checker_;
@@ -82,6 +87,9 @@
 
   // Absolute link url.
   GURL absolute_url_;
+
+  // The style sheet associated with this element.
+  scoped_refptr<cssom::StyleSheet> style_sheet_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 169ea07..0a4a3a7 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -1641,24 +1641,21 @@
   TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::PreferDecodeToTexture()");
 
 #if defined(ENABLE_MAP_TO_MESH)
-  if (!node_document()->UpdateComputedStyleOnElementAndAncestor(this)) {
-    LOG(WARNING) << "Could not update computed style.";
-    NOTREACHED();
-    return false;
-  }
-
-  if (!computed_style()) {
-    // This has been found to occur when HTMLMediaElement::OnLoadTimer() is on
-    // the callstack, though the visual inspection of the display shows that
-    // we are in the settings menu.
-    LOG(WARNING) << "PreferDecodeToTexture() could not get the computed style.";
-    NOTREACHED();
-    return false;
+  cssom::PropertyValue* filter = NULL;
+  if (node_document()->UpdateComputedStyleOnElementAndAncestor(this) &&
+      computed_style()) {
+    filter = computed_style()->filter();
+  } else {
+    LOG(WARNING) << "PreferDecodeToTexture() could not get the computed style "
+                    "in order to know if map-to-mesh is applied to the "
+                    "HTMLMediaElement or not. Perhaps the HTMLMediaElement is "
+                    "not attached to the document?  Falling back to checking "
+                    "the inline style instead.";
+    filter = style()->data()->GetPropertyValue(cssom::kFilterProperty);
   }
 
   const cssom::MapToMeshFunction* map_to_mesh_filter =
-      cssom::MapToMeshFunction::ExtractFromFilterList(
-          computed_style()->filter());
+      cssom::MapToMeshFunction::ExtractFromFilterList(filter);
 
   return map_to_mesh_filter;
 #else  // defined(ENABLE_MAP_TO_MESH)
diff --git a/src/cobalt/dom/html_style_element.cc b/src/cobalt/dom/html_style_element.cc
index 616851f..c55a3de 100644
--- a/src/cobalt/dom/html_style_element.cc
+++ b/src/cobalt/dom/html_style_element.cc
@@ -39,6 +39,17 @@
   }
 }
 
+void HTMLStyleElement::OnRemovedFromDocument() {
+  HTMLElement::OnRemovedFromDocument();
+
+  if (style_sheet_) {
+    Document* document = node_document();
+    if (document) {
+      document->OnStyleSheetsModified();
+    }
+  }
+}
+
 void HTMLStyleElement::OnParserStartTag(
     const base::SourceLocation& opening_tag_location) {
   inline_style_location_ = opening_tag_location;
@@ -71,17 +82,23 @@
   if (bypass_csp || text.empty() ||
       csp_delegate->AllowInline(CspDelegate::kStyle, inline_style_location_,
                                 text)) {
-    scoped_refptr<cssom::CSSStyleSheet> style_sheet =
+    style_sheet_ =
         document->html_element_context()->css_parser()->ParseStyleSheet(
             text, inline_style_location_);
-    style_sheet->SetLocationUrl(GURL(inline_style_location_.file_path));
-    document->style_sheets()->Append(style_sheet);
-    style_sheet_ = style_sheet;
+    style_sheet_->SetLocationUrl(GURL(inline_style_location_.file_path));
+    document->OnStyleSheetsModified();
   } else {
     // Report a violation.
     PostToDispatchEvent(FROM_HERE, base::Tokens::error());
   }
 }
 
+void HTMLStyleElement::CollectStyleSheet(
+    cssom::StyleSheetVector* style_sheets) const {
+  if (style_sheet_) {
+    style_sheets->push_back(style_sheet_);
+  }
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/html_style_element.h b/src/cobalt/dom/html_style_element.h
index d032137..535233f 100644
--- a/src/cobalt/dom/html_style_element.h
+++ b/src/cobalt/dom/html_style_element.h
@@ -50,6 +50,7 @@
   //
   // From Node.
   void OnInsertedIntoDocument() OVERRIDE;
+  void OnRemovedFromDocument() OVERRIDE;
 
   // From Element.
   void OnParserStartTag(
@@ -64,14 +65,18 @@
  private:
   ~HTMLStyleElement() OVERRIDE {}
 
-  scoped_refptr<cssom::StyleSheet> style_sheet_;
-
   void Process();
 
+  // Add this element's style sheet to the style sheet vector.
+  void CollectStyleSheet(cssom::StyleSheetVector* style_sheets) const OVERRIDE;
+
   // Whether the style element is inserted by parser.
   bool is_parser_inserted_;
   // SourceLocation for inline style.
   base::SourceLocation inline_style_location_;
+
+  // The style sheet associated with this element.
+  scoped_refptr<cssom::StyleSheet> style_sheet_;
 };
 
 }  // namespace dom
diff --git a/src/cobalt/dom/local_storage_database.cc b/src/cobalt/dom/local_storage_database.cc
index c87b942..844f584 100644
--- a/src/cobalt/dom/local_storage_database.cc
+++ b/src/cobalt/dom/local_storage_database.cc
@@ -156,20 +156,20 @@
   TRACK_MEMORY_SCOPE("Storage");
   Init();
   storage_->GetSqlContext(base::Bind(&SqlWrite, id, key, value));
-  storage_->Flush();
+  storage_->FlushOnChange();
 }
 
 void LocalStorageDatabase::Delete(const std::string& id,
                                   const std::string& key) {
   Init();
   storage_->GetSqlContext(base::Bind(&SqlDelete, id, key));
-  storage_->Flush();
+  storage_->FlushOnChange();
 }
 
 void LocalStorageDatabase::Clear(const std::string& id) {
   Init();
   storage_->GetSqlContext(base::Bind(&SqlClear, id));
-  storage_->Flush();
+  storage_->FlushOnChange();
 }
 
 void LocalStorageDatabase::Flush(const base::Closure& callback) {
diff --git a/src/cobalt/dom/mouse_event.cc b/src/cobalt/dom/mouse_event.cc
index 512014f..0590f10 100644
--- a/src/cobalt/dom/mouse_event.cc
+++ b/src/cobalt/dom/mouse_event.cc
@@ -34,33 +34,36 @@
 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()),
+      screen_x_(static_cast<float>(init_dict.screen_x())),
+      screen_y_(static_cast<float>(init_dict.screen_y())),
+      client_x_(static_cast<float>(init_dict.client_x())),
+      client_y_(static_cast<float>(init_dict.client_y())),
       button_(init_dict.button()),
-      buttons_(init_dict.buttons()) {}
+      buttons_(init_dict.buttons()),
+      related_target_(init_dict.related_target()) {}
 
 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()),
+      screen_x_(static_cast<float>(init_dict.screen_x())),
+      screen_y_(static_cast<float>(init_dict.screen_y())),
+      client_x_(static_cast<float>(init_dict.client_x())),
+      client_y_(static_cast<float>(init_dict.client_y())),
       button_(init_dict.button()),
-      buttons_(init_dict.buttons()) {}
+      buttons_(init_dict.buttons()),
+      related_target_(init_dict.related_target()) {}
 
 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()),
+      screen_x_(static_cast<float>(init_dict.screen_x())),
+      screen_y_(static_cast<float>(init_dict.screen_y())),
+      client_x_(static_cast<float>(init_dict.client_x())),
+      client_y_(static_cast<float>(init_dict.client_y())),
       button_(init_dict.button()),
-      buttons_(init_dict.buttons()) {}
+      buttons_(init_dict.buttons()),
+      related_target_(init_dict.related_target()) {}
 
 MouseEvent::MouseEvent(UninitializedFlag uninitialized_flag)
     : UIEventWithKeyState(uninitialized_flag),
diff --git a/src/cobalt/dom/mouse_event.h b/src/cobalt/dom/mouse_event.h
index 45c5109..be9909e 100644
--- a/src/cobalt/dom/mouse_event.h
+++ b/src/cobalt/dom/mouse_event.h
@@ -55,13 +55,14 @@
                       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_; }
+  float screen_x() const { return screen_x_; }
+  float screen_y() const { return screen_y_; }
+  float client_x() const { return client_x_; }
+  float client_y() const { return client_y_; }
 
   // Web API: CSSOM View Module: Extensions to the MouseEvent Interface
-  // (partial interface)
+  // (partial interface). This also changes screen_* and client_* above from
+  // long to double.
   //   https://www.w3.org/TR/2013/WD-cssom-view-20131217/#extensions-to-the-mouseevent-interface
   float page_x() const;
   float page_y() const;
@@ -92,10 +93,10 @@
   ~MouseEvent() OVERRIDE {}
 
  private:
-  int32_t screen_x_;
-  int32_t screen_y_;
-  int32_t client_x_;
-  int32_t client_y_;
+  float screen_x_;
+  float screen_y_;
+  float client_x_;
+  float client_y_;
   int16_t button_;
   uint16_t buttons_;
 
diff --git a/src/cobalt/dom/mouse_event.idl b/src/cobalt/dom/mouse_event.idl
index 759e615..c597efb 100644
--- a/src/cobalt/dom/mouse_event.idl
+++ b/src/cobalt/dom/mouse_event.idl
@@ -14,13 +14,14 @@
 
 // https://www.w3.org/TR/2016/WD-uievents-20160804/#interface-mouseevent
 // https://www.w3.org/TR/2016/WD-uievents-20160804/#idl-interface-MouseEvent-initializers
+// https://www.w3.org/TR/2013/WD-cssom-view-20131217/#extensions-to-the-mouseevent-interface
 
 [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 double screenX;
+  readonly attribute double screenY;
+  readonly attribute double clientX;
+  readonly attribute double clientY;
 
   readonly attribute boolean ctrlKey;
   readonly attribute boolean shiftKey;
diff --git a/src/cobalt/dom/mouse_event_init.idl b/src/cobalt/dom/mouse_event_init.idl
index 9b752d8..7111167 100644
--- a/src/cobalt/dom/mouse_event_init.idl
+++ b/src/cobalt/dom/mouse_event_init.idl
@@ -16,10 +16,10 @@
 // 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;
+  double screenX = 0;
+  double screenY = 0;
+  double clientX = 0;
+  double clientY = 0;
 
   short button = 0;
   unsigned short buttons = 0;
diff --git a/src/cobalt/dom/pointer_state.cc b/src/cobalt/dom/pointer_state.cc
new file mode 100644
index 0000000..6d517e8
--- /dev/null
+++ b/src/cobalt/dom/pointer_state.cc
@@ -0,0 +1,250 @@
+// 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_state.h"
+
+#include "cobalt/dom/mouse_event.h"
+#include "cobalt/dom/pointer_event.h"
+#include "cobalt/dom/wheel_event.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace dom {
+
+void PointerState::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);
+}
+
+namespace {
+int32_t GetPointerIdFromEvent(const scoped_refptr<Event>& event) {
+  if (event->GetWrappableType() == base::GetTypeId<PointerEvent>()) {
+    const PointerEvent* const pointer_event =
+        base::polymorphic_downcast<const PointerEvent* const>(event.get());
+    return pointer_event->pointer_id();
+  }
+  return 0;
+}
+}  // namespace
+
+scoped_refptr<Event> PointerState::GetNextQueuedPointerEvent() {
+  scoped_refptr<Event> event;
+  if (pointer_events_.empty()) {
+    return event;
+  }
+
+  // Ignore pointer move events when they are succeeded by additional pointer
+  // move events with the same pointerId.
+  scoped_refptr<Event> front_event(pointer_events_.front());
+  bool next_event_is_move_event =
+      front_event->type() == base::Tokens::pointermove() ||
+      front_event->type() == base::Tokens::mousemove();
+  int32_t next_event_pointer_id = GetPointerIdFromEvent(front_event);
+  int32_t current_event_pointer_id;
+  bool current_event_is_move_event;
+  do {
+    current_event_pointer_id = next_event_pointer_id;
+    current_event_is_move_event = next_event_is_move_event;
+    event = pointer_events_.front();
+    pointer_events_.pop();
+    if (pointer_events_.empty() || !current_event_is_move_event) {
+      break;
+    }
+    next_event_pointer_id = GetPointerIdFromEvent(event);
+    if (next_event_pointer_id != current_event_pointer_id) {
+      break;
+    }
+    front_event = pointer_events_.front();
+    next_event_is_move_event =
+        (front_event->type() == base::Tokens::pointermove() ||
+         front_event->type() == base::Tokens::mousemove());
+  } while (next_event_is_move_event);
+  return event;
+}
+
+void PointerState::SetPendingPointerCaptureTargetOverride(int32_t pointer_id,
+                                                          Element* element) {
+  pending_target_override_[pointer_id] = base::AsWeakPtr(element);
+}
+
+void PointerState::ClearPendingPointerCaptureTargetOverride(
+    int32_t pointer_id) {
+  pending_target_override_.erase(pointer_id);
+}
+
+scoped_refptr<HTMLElement> PointerState::GetPointerCaptureOverrideElement(
+    int32_t pointer_id, PointerEventInit* event_init) {
+  const scoped_refptr<Window>& view = event_init->view();
+  scoped_refptr<Element> target_override_element;
+  // Algorithm for Process Pending Pointer Capture.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#process-pending-pointer-capture
+  auto pending_override = pending_target_override_.find(pointer_id);
+  auto override = target_override_.find(pointer_id);
+
+  if (pending_override != pending_target_override_.end() &&
+      pending_override->second &&
+      pending_override->second->node_document() != view->document()) {
+    // When the pointer capture target override is removed from its
+    // ownerDocument's tree, clear the pending pointer capture target override
+    // and pointer capture target override nodes and fire a PointerEvent named
+    // lostpointercapture at the document.
+    pending_target_override_.erase(pending_override);
+    pending_override = pending_target_override_.end();
+  }
+
+  // 1. If the pointer capture target override for this pointer is set and is
+  // not equal to the pending pointer capture target override, then fire a
+  // pointer event named lostpointercapture at the pointer capture target
+  // override node.
+  if (override != target_override_.end() && override->second &&
+      (pending_override == pending_target_override_.end() ||
+       pending_override->second != override->second)) {
+    override->second->DispatchEvent(
+        new PointerEvent(base::Tokens::lostpointercapture(), Event::kBubbles,
+                         Event::kNotCancelable, view, *event_init));
+  }
+
+  // 2. If the pending pointer capture target override for this pointer is set
+  // and is not equal to the pointer capture target override, then fire a
+  // pointer event named gotpointercapture at the pending pointer capture
+  // target override.
+  if (pending_override != pending_target_override_.end() &&
+      pending_override->second &&
+      (override == target_override_.end() ||
+       pending_override->second != override->second)) {
+    pending_override->second->DispatchEvent(
+        new PointerEvent(base::Tokens::gotpointercapture(), Event::kBubbles,
+                         Event::kNotCancelable, view, *event_init));
+  }
+
+  // 3. Set the pointer capture target override to the pending pointer capture
+  // target override, if set. Otherwise, clear the pointer capture target
+  // override.
+  if (pending_override != pending_target_override_.end() &&
+      pending_override->second) {
+    target_override_[pending_override->first] = pending_override->second;
+    target_override_element = pending_override->second;
+  } else if (override != target_override_.end()) {
+    target_override_.erase(override);
+  }
+  scoped_refptr<HTMLElement> html_element;
+  if (target_override_element) {
+    html_element = target_override_element->AsHTMLElement();
+  }
+  return html_element;
+}
+
+void PointerState::SetPointerCapture(int32_t pointer_id, Element* element,
+                                     script::ExceptionState* exception_state) {
+  UNREFERENCED_PARAMETER(element);
+  // Algorithm for Setting Pointer Capture
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#setting-pointer-capture
+
+  // 1. If the pointerId provided as the method's argument does not match any of
+  // the active pointers, then throw a DOMException with the name
+  // InvalidPointerId.
+  if (active_pointers_.find(pointer_id) == active_pointers_.end()) {
+    DOMException::Raise(dom::DOMException::kInvalidPointerIdErr,
+                        exception_state);
+    return;
+  }
+
+  // 2. If the Element on which this method is invoked does not participate in
+  // its ownerDocument's tree, throw an exception with the name
+  // InvalidStateError.
+  if (!element || !element->owner_document()) {
+    DOMException::Raise(dom::DOMException::kInvalidStateErr, exception_state);
+    return;
+  }
+
+  // 3. If the pointer is not in the active buttons state, then terminate these
+  // steps.
+  if (pointers_with_active_buttons_.find(pointer_id) ==
+      pointers_with_active_buttons_.end()) {
+    return;
+  }
+
+  // 4. For the specified pointerId, set the pending pointer capture target
+  // override to the Element on which this method was invoked.
+  SetPendingPointerCaptureTargetOverride(pointer_id, element);
+}
+
+void PointerState::ReleasePointerCapture(
+    int32_t pointer_id, Element* element,
+    script::ExceptionState* exception_state) {
+  UNREFERENCED_PARAMETER(element);
+  // Algorithm for Releasing Pointer Capture
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#releasing-pointer-capture
+
+  // 1. If the pointerId provided as the method's argument does not match any of
+  // the active pointers and these steps are not being invoked as a result of
+  // the implicit release of pointer capture, then throw a DOMException with the
+  // name InvalidPointerId.
+  if (active_pointers_.find(pointer_id) == active_pointers_.end()) {
+    DOMException::Raise(dom::DOMException::kInvalidPointerIdErr,
+                        exception_state);
+    return;
+  }
+
+  // 2. If pointer capture is not currently set for the specified pointer, then
+  // terminate these steps.
+  auto pending_override = pending_target_override_.find(pointer_id);
+  if (pending_override == pending_target_override_.end()) {
+    return;
+  }
+
+  // 3. If the pointer capture target override for the specified pointerId is
+  // not the Element on which this method was invoked, then terminate these
+  // steps.
+  if (pending_override->second.get() != element) {
+    return;
+  }
+
+  // 4. For the specified pointerId, clear the pending pointer capture target
+  // override, if set.
+  ClearPendingPointerCaptureTargetOverride(pointer_id);
+}
+
+bool PointerState::HasPointerCapture(int32_t pointer_id, Element* element) {
+  auto pending_override = pending_target_override_.find(pointer_id);
+
+  if (pending_override != pending_target_override_.end()) {
+    return pending_override->second.get() == element;
+  }
+  return false;
+}
+
+void PointerState::SetActiveButtonsState(int32_t pointer_id, uint16_t buttons) {
+  if (buttons) {
+    pointers_with_active_buttons_.insert(pointer_id);
+  } else {
+    pointers_with_active_buttons_.erase(pointer_id);
+  }
+}
+
+void PointerState::SetActive(int32_t pointer_id) {
+  active_pointers_.insert(pointer_id);
+}
+
+void PointerState::ClearActive(int32_t pointer_id) {
+  active_pointers_.erase(pointer_id);
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/src/cobalt/dom/pointer_state.h b/src/cobalt/dom/pointer_state.h
new file mode 100644
index 0000000..f934580
--- /dev/null
+++ b/src/cobalt/dom/pointer_state.h
@@ -0,0 +1,96 @@
+// 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_STATE_H_
+#define COBALT_DOM_POINTER_STATE_H_
+
+#include <map>
+#include <queue>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/event.h"
+#include "cobalt/dom/html_element.h"
+#include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/math/vector2d_f.h"
+
+namespace cobalt {
+namespace dom {
+
+// This class contains various state related to pointer and mouse support.
+class PointerState {
+ public:
+  // Web API: Pointer Events: Extensions to the Element Interface (partial
+  // interface)
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#extensions-to-the-element-interface
+  void SetPointerCapture(int32_t pointer_id, Element* element,
+                         script::ExceptionState* exception_state);
+  void ReleasePointerCapture(int32_t pointer_id, Element* element,
+                             script::ExceptionState* exception_state);
+  bool HasPointerCapture(int32_t pointer_id, Element* element);
+
+  // Custom: Not in any Web API.
+  //
+
+  // Queue up pointer related events.
+  void QueuePointerEvent(const scoped_refptr<Event>& event);
+
+  // Get the next queued pointer event.
+  scoped_refptr<Event> GetNextQueuedPointerEvent();
+
+  // Set the pending pointer capture target override for a pointer.
+  void SetPendingPointerCaptureTargetOverride(int32_t pointer_id,
+                                              Element* element);
+
+  // Clear the pending pointer capture target override for a pointer.
+  void ClearPendingPointerCaptureTargetOverride(int32_t pointer_id);
+
+  // Process Pending Pointer Capture and return the capture override element.
+  scoped_refptr<HTMLElement> GetPointerCaptureOverrideElement(
+      int32_t pointer_id, PointerEventInit* event_init);
+
+  // Set the 'active buttons state' for a pointer.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#dfn-active-buttons-state
+  void SetActiveButtonsState(int32_t pointer_id, uint16_t buttons);
+
+  // Set of clear a pointer as 'active'.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#dfn-active-pointer
+  void SetActive(int32_t pointer_id);
+  void ClearActive(int32_t pointer_id);
+
+ private:
+  // Stores pointer events until they are handled after a layout.
+  std::queue<scoped_refptr<Event> > pointer_events_;
+
+  // This stores the elements with target overrides
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#pointer-capture
+  std::map<int32_t, base::WeakPtr<Element> > target_override_;
+  std::map<int32_t, base::WeakPtr<Element> > pending_target_override_;
+
+  // Store the set of active pointers.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#dfn-active-pointer
+  std::set<int32_t> active_pointers_;
+
+  // Store the set of pointers with active buttons.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#dfn-active-buttons-state
+  std::set<int32_t> pointers_with_active_buttons_;
+};
+
+}  // namespace dom
+}  // namespace cobalt
+
+#endif  // COBALT_DOM_POINTER_STATE_H_
diff --git a/src/cobalt/dom/rule_matching_test.cc b/src/cobalt/dom/rule_matching_test.cc
index eac8ded..73f9fdb 100644
--- a/src/cobalt/dom/rule_matching_test.cc
+++ b/src/cobalt/dom/rule_matching_test.cc
@@ -48,7 +48,11 @@
                               NULL, NULL, dom_stat_tracker_.get(), "",
                               base::kApplicationStateStarted),
         document_(new Document(&html_element_context_)),
-        root_(document_->CreateElement("html")->AsHTMLElement()) {
+        root_(document_->CreateElement("html")->AsHTMLElement()),
+        head_(document_->CreateElement("head")->AsHTMLElement()),
+        body_(document_->CreateElement("body")->AsHTMLElement()) {
+    root_->AppendChild(head_);
+    root_->AppendChild(body_);
     document_->AppendChild(root_);
   }
 
@@ -56,18 +60,23 @@
 
   void UpdateAllMatchingRules();
 
+  scoped_refptr<cssom::CSSStyleSheet> GetDocumentStyleSheet(
+      unsigned int index) {
+    return document_->style_sheets()->Item(index)->AsCSSStyleSheet();
+  }
+
   scoped_ptr<css_parser::Parser> css_parser_;
   scoped_ptr<dom_parser::Parser> dom_parser_;
   scoped_ptr<DomStatTracker> dom_stat_tracker_;
   HTMLElementContext html_element_context_;
+
   scoped_refptr<Document> document_;
   scoped_refptr<HTMLElement> root_;
-
-  scoped_refptr<cssom::CSSStyleSheet> css_style_sheet_;
+  scoped_refptr<HTMLElement> head_;
+  scoped_refptr<HTMLElement> body_;
 };
 
 void RuleMatchingTest::UpdateAllMatchingRules() {
-  document_->style_sheets()->Append(css_style_sheet_);
   document_->UpdateSelectorTree();
   NodeDescendantsIterator iterator(document_);
   Node* child = iterator.First();
@@ -87,145 +96,140 @@
 
 // * should match <div/>.
 TEST_F(RuleMatchingTest, UniversalSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "* {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/>");
+  head_->set_inner_html("<style>* {}</style>");
+  body_->set_inner_html("<div/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div should match <div/>.
 TEST_F(RuleMatchingTest, TypeSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/>");
+  head_->set_inner_html("<style>div {}</style>");
+  body_->set_inner_html("<div/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // [attr] should match <div attr/>.
 TEST_F(RuleMatchingTest, AttributeSelectorMatchNoValue) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "[attr] {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div attr/>");
+  head_->set_inner_html("<style>[attr] {}</style>");
+  body_->set_inner_html("<div attr/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // [attr=value] should match <div attr="value"/>.
 TEST_F(RuleMatchingTest, AttributeSelectorMatchEquals) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "[attr=value] {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div attr=\"value\"/>");
+  head_->set_inner_html("<style>[attr=value] {}</style>");
+  body_->set_inner_html("<div attr=\"value\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // [attr="value"] should match <div attr="value"/>.
 TEST_F(RuleMatchingTest, AttributeSelectorMatchEqualsWithQuote) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "[attr=\"value\"] {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div attr=\"value\"/>");
+  head_->set_inner_html("<style>[attr=\"value\"] {}</style>");
+  body_->set_inner_html("<div attr=\"value\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // [attr=value] should not match <div attr/>.
 TEST_F(RuleMatchingTest, AttributeSelectorNoMatchEquals) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "[attr=value] {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div attr/>");
+  head_->set_inner_html("<style>[attr=value] {}</style>");
+  body_->set_inner_html("<div attr/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(0, matching_rules->size());
 }
 
 // .my-class should match <div class="my-class"/>.
 TEST_F(RuleMatchingTest, ClassSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ".my-class {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div class=\"my-class\"/>");
+  head_->set_inner_html("<style>.my-class {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // #div1 should match <div id="div1"/>.
 TEST_F(RuleMatchingTest, IdSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "#div1 {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div id=\"div1\"/>");
+  head_->set_inner_html("<style>#div1 {}</style>");
+  body_->set_inner_html("<div id=\"div1\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // :empty should match <div></div>.
 TEST_F(RuleMatchingTest, EmptyPseudoClassMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div></div>");
+  head_->set_inner_html("<style>:empty {}</style>");
+  body_->set_inner_html("<div></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // :empty should match <div><!--comment--></div>.
 TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchCommentOnly) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><!--comment--></div>");
+  head_->set_inner_html("<style>:empty {}</style>");
+  body_->set_inner_html("<div><!--comment--></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // :empty shouldn't match <div> </div>.
 TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchTextOnly) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div> </div>");
+  head_->set_inner_html("<style>:empty {}</style>");
+  body_->set_inner_html("<div> </div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
 }
 
@@ -237,16 +241,16 @@
   // Give the document initial computed style.
   document_->SetViewport(math::Size(320, 240));
 
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":focus {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div tabIndex=\"-1\"/>");
-  root_->first_element_child()->AsHTMLElement()->Focus();
+  head_->set_inner_html("<style>:focus {}</style>");
+  body_->set_inner_html("<div tabIndex=\"-1\"/>");
+  body_->first_element_child()->AsHTMLElement()->Focus();
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
-  ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
+  ASSERT_EQ(2, matching_rules->size());
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div:focus shouldn't match unfocused div.
@@ -257,51 +261,46 @@
   // Give the document initial computed style.
   document_->SetViewport(math::Size(320, 240));
 
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":focus {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div tabIndex=\"-1\"/>");
+  head_->set_inner_html("<style>:focus {}</style>");
+  body_->set_inner_html("<div tabIndex=\"-1\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
 }
 
 // :not(.my-class) should match <div/>.
 TEST_F(RuleMatchingTest, NotPseudoClassMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":not(.my-class) {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/>");
+  head_->set_inner_html("<style>:not(.my-class) {}</style>");
+  body_->set_inner_html("<div/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // :not(.my-class) shouldn't match <div class="my-class"/>.
 TEST_F(RuleMatchingTest, NotPseudoClassNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":not(.my-class) {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div class=\"my-class\"/>");
+  head_->set_inner_html("<style>:not(.my-class) {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
 }
 
 // *:after should create and match the after pseudo element of all elements.
 TEST_F(RuleMatchingTest, AfterPseudoElementMatchGlobal) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "*:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
+  head_->set_inner_html("<style>*:after {}</style>");
+  body_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -309,26 +308,27 @@
   matching_rules =
       html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 
-  html_element = root_->last_element_child()->AsHTMLElement();
+  html_element = body_->last_element_child()->AsHTMLElement();
   matching_rules = html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
   matching_rules =
       html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div:after should create and match the after pseudo element of <div/>.
 TEST_F(RuleMatchingTest, AfterPseudoElementSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/>");
+  head_->set_inner_html("<style>div:after {}</style>");
+  body_->set_inner_html("<div/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -336,17 +336,17 @@
   matching_rules =
       html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div:after shouldn't create the after pseudo element of <span/>.
 TEST_F(RuleMatchingTest, AfterPseudoElementSelectorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<span/>");
+  head_->set_inner_html("<style>div:after {}</style>");
+  body_->set_inner_html("<span/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -355,12 +355,11 @@
 
 // *:before should create and match the before pseudo element of all elements.
 TEST_F(RuleMatchingTest, BeforePseudoElementMatchGlobal) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "*:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
+  head_->set_inner_html("<style>*:before {}</style>");
+  body_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -368,26 +367,27 @@
   matching_rules =
       html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 
-  html_element = root_->last_element_child()->AsHTMLElement();
+  html_element = body_->last_element_child()->AsHTMLElement();
   matching_rules = html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
   ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
   matching_rules =
       html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div:before should create and match the before pseudo element of <div/>.
 TEST_F(RuleMatchingTest, BeforePseudoElementSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/>");
+  head_->set_inner_html("<style>div:before {}</style>");
+  body_->set_inner_html("<div/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -395,17 +395,17 @@
   matching_rules =
       html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div:before shouldn't create the before pseudo element of <span/>.
 TEST_F(RuleMatchingTest, BeforePseudoElementSelectorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<span/>");
+  head_->set_inner_html("<style>div:before {}</style>");
+  body_->set_inner_html("<span/>");
   UpdateAllMatchingRules();
 
-  HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
+  HTMLElement* html_element = body_->first_element_child()->AsHTMLElement();
   cssom::RulesWithCascadePrecedence* matching_rules =
       html_element->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
@@ -414,72 +414,67 @@
 
 // :empty shouldn't match (the outer) <div><div></div></div>.
 TEST_F(RuleMatchingTest, EmptyPseudoClassNotMatchElement) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><div></div></div>");
+  head_->set_inner_html("<style>:empty {}</style>");
+  body_->set_inner_html("<div><div></div></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
 }
 
 // div.my-class should match <div class="my-class"/>.
 TEST_F(RuleMatchingTest, CompoundSelectorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div.my-class {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div class=\"my-class\"/>");
+  head_->set_inner_html("<style>div.my-class {}</style>");
+  body_->set_inner_html("<div class=\"my-class\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // div.my-class shouldn't match <div/> or <span class="my-class"/>.
 TEST_F(RuleMatchingTest, CompoundSelectorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div.my-class {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/><span class=\"my-class\"/>");
+  head_->set_inner_html("<style>div.my-class {}</style>");
+  body_->set_inner_html("<div/><span class=\"my-class\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
   matching_rules =
-      root_->last_element_child()->AsHTMLElement()->matching_rules();
+      body_->last_element_child()->AsHTMLElement()->matching_rules();
   EXPECT_EQ(0, matching_rules->size());
 }
 
 // "div span" should match inner span in <div><span><span></span></span></div>.
 TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><span><span></span></span></div>");
+  head_->set_inner_html("<style>div span {}</style>");
+  body_->set_inner_html("<div><span><span></span></span></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()
+      body_->first_element_child()
           ->first_element_child()
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // "span span" shouldn't match span in <div><span></span></div>.
 TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "span span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><span></span></div>");
+  head_->set_inner_html("<style>span span {}</style>");
+  body_->set_inner_html("<div><span></span></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()
+      body_->first_element_child()
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
@@ -488,29 +483,29 @@
 
 // "div > span" should match span in <div><span></span></div>.
 TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div > span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><span></span></div>");
+  head_->set_inner_html("<style>div > span {}</style>");
+  body_->set_inner_html("<div><span></span></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()
+      body_->first_element_child()
           ->first_element_child()
           ->AsHTMLElement()
           ->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // "div > span" shouldn't match inner span in
 // <div><span><span></span></span></div>.
 TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div > span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div><span><span></span></span></div>");
+  head_->set_inner_html("<style>div > span {}</style>");
+  body_->set_inner_html("<div><span><span></span></span></div>");
+  UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()
+      body_->first_element_child()
           ->first_element_child()
           ->first_element_child()
           ->AsHTMLElement()
@@ -520,71 +515,68 @@
 
 // "span + span" should match second span in <span/><span/>.
 TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "span + span {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<span/><span/>");
+  head_->set_inner_html("<style>span + span {}</style>");
+  body_->set_inner_html("<span/><span/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->last_element_child()->AsHTMLElement()->matching_rules();
+      body_->last_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // "span + span" shouldm't match first span in <span/><span/>.
 TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "span + span {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<span/><span/>");
+  head_->set_inner_html("<style>span + span {}</style>");
+  body_->set_inner_html("<span/><span/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(0, matching_rules->size());
 }
 
 // "div ~ span" should match second span in <div/><span/><span/>.
 TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div ~ span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/><span/><span/>");
+  head_->set_inner_html("<style>div ~ span {}</style>");
+  body_->set_inner_html("<div/><span/><span/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->last_element_child()->AsHTMLElement()->matching_rules();
+      body_->last_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 // "span ~ span" shouldn't match span in <div/><span/>.
 TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorNoMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "span ~ span {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html("<div/><span/>");
+  head_->set_inner_html("<style>span ~ span {}</style>");
+  body_->set_inner_html("<div/><span/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->last_element_child()->AsHTMLElement()->matching_rules();
+      body_->last_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(0, matching_rules->size());
 }
 
 TEST_F(RuleMatchingTest, SelectorListMatchShouldContainAllMatches) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      ".first-class, #my-id, .first-class.second-class {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html(
+  head_->set_inner_html(
+      "<style>.first-class, #my-id, .first-class.second-class {}</style>");
+  body_->set_inner_html(
       "<div class=\"first-class second-class\" id=\"my-id\"/>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->first_element_child()->AsHTMLElement()->matching_rules();
+      body_->first_element_child()->AsHTMLElement()->matching_rules();
   ASSERT_EQ(3, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[1].first);
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[2].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[1].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[2].first);
 
   std::vector<cssom::Specificity> vs;
   vs.push_back((*matching_rules)[0].second.specificity());
@@ -598,25 +590,25 @@
 
 // A complex example using several combinators.
 TEST_F(RuleMatchingTest, ComplexSelectorCombinedMatch) {
-  css_style_sheet_ = css_parser_->ParseStyleSheet(
-      "div ~ span + div ~ div + div > div + div {}",
-      base::SourceLocation("[object RuleMatchingTest]", 1, 1));
-  root_->set_inner_html(
+  head_->set_inner_html(
+      "<style>div ~ span + div ~ div + div > div + div {}</style>");
+  body_->set_inner_html(
       "<div/><span/><span/><div/><span/><div/>"
       "<div><div/><div/></div>");
   UpdateAllMatchingRules();
 
   cssom::RulesWithCascadePrecedence* matching_rules =
-      root_->last_element_child()
+      body_->last_element_child()
           ->last_element_child()
           ->AsHTMLElement()
           ->matching_rules();
   ASSERT_EQ(1, matching_rules->size());
-  EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
+  EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0),
+            (*matching_rules)[0].first);
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldReturnFirstMatch) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div id='div1'>"
       "  <div id='div2'/>"
       "  <div id='div3'/>"
@@ -631,7 +623,7 @@
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldLimitResultInSubtree) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div id='div1'>"
       "  <div id='div2'/>"
       "  <div id='div3'/>"
@@ -648,7 +640,7 @@
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorOutsideSubtree) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div class='out'>"
       "  <div id='div1'>"
       "    <span/>"
@@ -665,7 +657,7 @@
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsRecursively) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div/>"
       "<div/>"
       "<p/>"
@@ -678,7 +670,7 @@
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsCombined) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div/>"
       "<span/>"
       "<span/>"
@@ -696,7 +688,7 @@
 }
 
 TEST_F(RuleMatchingTest, QuerySelectorAllShouldReturnAllMatches) {
-  root_->set_inner_html(
+  body_->set_inner_html(
       "<div id='div1'>"
       "  <div id='div2'/>"
       "  <div id='div3'/>"
diff --git a/src/cobalt/dom/storage_area.cc b/src/cobalt/dom/storage_area.cc
index 78cb9e6..2e8abcd 100644
--- a/src/cobalt/dom/storage_area.cc
+++ b/src/cobalt/dom/storage_area.cc
@@ -94,6 +94,13 @@
   Init();
 
   base::optional<std::string> old_value = GetItem(key);
+
+  // If the previous value is equal to value, then the method must do nothing.
+  // https://www.w3.org/TR/2015/CR-webstorage-20150609/#storage-0
+  if (old_value == value) {
+    return;
+  }
+
   // TODO: Implement quota handling.
   size_bytes_ +=
       static_cast<int>(value.length() - old_value.value_or("").length());
@@ -107,13 +114,17 @@
 void StorageArea::RemoveItem(const std::string& key) {
   Init();
 
-  base::optional<std::string> old_value;
   StorageMap::iterator it = storage_map_->find(key);
-  if (it != storage_map_->end()) {
-    size_bytes_ -= static_cast<int>(it->second.length());
-    old_value = it->second;
-    storage_map_->erase(it);
+
+  // If no item with this key exists, the method must do nothing.
+  // https://www.w3.org/TR/2015/CR-webstorage-20150609/#storage-0
+  if (it == storage_map_->end()) {
+    return;
   }
+
+  size_bytes_ -= static_cast<int>(it->second.length());
+  std::string old_value = it->second;
+  storage_map_->erase(it);
   storage_node_->DispatchEvent(key, old_value, base::nullopt);
   if (db_interface_) {
     db_interface_->Delete(identifier_, key);
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 7252556..0d4e62e 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -110,6 +110,7 @@
     : width_(width),
       height_(height),
       device_pixel_ratio_(device_pixel_ratio),
+      is_resize_event_pending_(false),
 #if defined(ENABLE_TEST_RUNNER)
       test_runner_(new TestRunner()),
 #endif  // ENABLE_TEST_RUNNER
@@ -419,7 +420,7 @@
   } else if (event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
              event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
              event->GetWrappableType() == base::GetTypeId<WheelEvent>()) {
-    document_->QueuePointerEvent(event);
+    document_->pointer_state()->QueuePointerEvent(event);
   } else {
     SB_NOTREACHED();
   }
@@ -448,7 +449,12 @@
   // This will cause layout invalidation.
   document_->SetViewport(math::Size(width, height));
 
-  PostToDispatchEvent(FROM_HERE, base::Tokens::resize());
+  if (html_element_context_->page_visibility_state()->GetVisibilityState() ==
+      page_visibility::kVisibilityStateVisible) {
+    DispatchEvent(new Event(base::Tokens::resize()));
+  } else {
+    is_resize_event_pending_ = true;
+  }
 }
 
 void Window::SetCamera3D(const scoped_refptr<input::Camera3D>& camera_3d) {
@@ -463,7 +469,11 @@
 
 void Window::OnVisibilityStateChanged(
     page_visibility::VisibilityState visibility_state) {
-  UNREFERENCED_PARAMETER(visibility_state);
+  if (is_resize_event_pending_ &&
+      visibility_state == page_visibility::kVisibilityStateVisible) {
+    is_resize_event_pending_ = false;
+    DispatchEvent(new Event(base::Tokens::resize()));
+  }
 }
 
 void Window::TraceMembers(script::Tracer* tracer) {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 8e95a10..e3a3f83 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -317,13 +317,13 @@
 
   void TraceMembers(script::Tracer* tracer) OVERRIDE;
 
-  DEFINE_WRAPPABLE_TYPE(Window);
-
   const base::Callback<bool(const std::string&)> splash_screen_cache_callback()
       const {
     return splash_screen_cache_callback_;
   }
 
+  DEFINE_WRAPPABLE_TYPE(Window);
+
  private:
   void StartDocumentLoad(
       loader::FetcherFactory* fetcher_factory, const GURL& url,
@@ -342,6 +342,11 @@
   int height_;
   float device_pixel_ratio_;
 
+  // A resize event can be pending if a resize occurs and the current visibility
+  // state is not visible. In this case, the resize event will run when the
+  // visibility state changes to visible.
+  bool is_resize_event_pending_;
+
 #if defined(ENABLE_TEST_RUNNER)
   scoped_refptr<TestRunner> test_runner_;
 #endif  // ENABLE_TEST_RUNNER
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 7d1eb9a..91213b7 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -108,14 +108,17 @@
     // 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);
+    event->set_buttons(1);
   } 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);
+    event->set_buttons(2);
   } 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);
+    event->set_buttons(4);
   }
 }
 
@@ -141,7 +144,7 @@
     // often combined with a mouse wheel).
     buttons |= 4;
   }
-  event->set_buttons(buttons);
+  event->set_buttons(event->buttons() | buttons);
 }
 
 void UpdateMouseEventInit(const system_window::InputEvent* input_event,
@@ -150,10 +153,11 @@
   UpdateMouseEventInitButton(input_event->key_code(), mouse_event);
   UpdateMouseEventInitButtons(input_event->modifiers(), mouse_event);
 
-  mouse_event->set_screen_x(static_cast<float>(input_event->position().x()));
-  mouse_event->set_screen_y(static_cast<float>(input_event->position().y()));
-  mouse_event->set_client_x(static_cast<float>(input_event->position().x()));
-  mouse_event->set_client_y(static_cast<float>(input_event->position().y()));
+  const math::PointF& position = input_event->position();
+  mouse_event->set_screen_x(static_cast<float>(position.x()));
+  mouse_event->set_screen_y(static_cast<float>(position.y()));
+  mouse_event->set_client_x(static_cast<float>(position.x()));
+  mouse_event->set_client_y(static_cast<float>(position.y()));
 }
 
 // Returns the value or the default_value when value is NaN.
@@ -184,6 +188,16 @@
   dom::PointerEventInit pointer_event;
   UpdateMouseEventInit(input_event, &pointer_event);
 
+  switch (input_event->type()) {
+    case system_window::InputEvent::kTouchpadDown:
+    case system_window::InputEvent::kTouchpadUp:
+    case system_window::InputEvent::kTouchpadMove:
+      pointer_event.set_pointer_type("touchpad");
+      break;
+    default:
+      pointer_event.set_pointer_type("mouse");
+      break;
+  }
   pointer_event.set_pointer_id(input_event->device_id());
 #if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
   pointer_event.set_width(value_or(input_event->size().x(), 0.0f));
@@ -252,10 +266,17 @@
       }
       break;
     }
-    case system_window::InputEvent::kPointerMove: {
+    case system_window::InputEvent::kPointerMove:
+    case system_window::InputEvent::kTouchpadMove: {
       HandlePointerEvent(base::Tokens::pointermove(), input_event);
       break;
     }
+    case system_window::InputEvent::kTouchpadDown:
+      HandlePointerEvent(base::Tokens::pointerdown(), input_event);
+      break;
+    case system_window::InputEvent::kTouchpadUp:
+      HandlePointerEvent(base::Tokens::pointerup(), input_event);
+      break;
     case system_window::InputEvent::kWheel: {
       HandleWheelEvent(input_event);
     }
diff --git a/src/cobalt/input/input_poller_impl.cc b/src/cobalt/input/input_poller_impl.cc
index 847e4ef..f7aa0de 100644
--- a/src/cobalt/input/input_poller_impl.cc
+++ b/src/cobalt/input/input_poller_impl.cc
@@ -96,6 +96,9 @@
     case system_window::InputEvent::kPointerDown:
     case system_window::InputEvent::kPointerUp:
     case system_window::InputEvent::kPointerMove:
+    case system_window::InputEvent::kTouchpadDown:
+    case system_window::InputEvent::kTouchpadUp:
+    case system_window::InputEvent::kTouchpadMove:
     case system_window::InputEvent::kWheel:
       // Pointer and Wheel events are ignored here.
       break;
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 921466e..b1e6b70 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -144,6 +144,18 @@
 
 Vector2dLayoutUnit Box::GetContainingBlockOffsetFromRoot(
     bool transform_forms_root) const {
+  if (!parent_) {
+    return Vector2dLayoutUnit();
+  }
+
+  const ContainerBox* containing_block = GetContainingBlock();
+  return containing_block->GetContentBoxOffsetFromRoot(transform_forms_root) +
+         GetContainingBlockOffsetFromItsContentBox(containing_block);
+}
+
+Vector2dLayoutUnit Box::GetContainingBlockOffsetFromItsContentBox(
+    const ContainerBox* containing_block) const {
+  DCHECK(containing_block == GetContainingBlock());
   // If the box is absolutely positioned, then its containing block 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.
@@ -151,12 +163,9 @@
   // containing block of a 'fixed' position element must always be the viewport,
   // all major browsers use the padding box of a transformed ancestor as the
   // containing block for 'fixed' position elements.
-  return parent_ ? IsAbsolutelyPositioned()
-                       ? GetContainingBlock()->GetPaddingBoxOffsetFromRoot(
-                             transform_forms_root)
-                       : GetContainingBlock()->GetContentBoxOffsetFromRoot(
-                             transform_forms_root)
-                 : Vector2dLayoutUnit();
+  return IsAbsolutelyPositioned()
+             ? -containing_block->GetContentBoxOffsetFromPaddingBox()
+             : Vector2dLayoutUnit();
 }
 
 void Box::SetStaticPositionLeftFromParent(LayoutUnit left) {
@@ -356,8 +365,8 @@
 
 Vector2dLayoutUnit Box::GetContentBoxOffsetFromRoot(
     bool transform_forms_root) const {
-  return GetPaddingBoxOffsetFromRoot(transform_forms_root) +
-         GetContentBoxOffsetFromPaddingBox();
+  return GetMarginBoxOffsetFromRoot(transform_forms_root) +
+         GetContentBoxOffsetFromMarginBox();
 }
 
 Vector2dLayoutUnit Box::GetContentBoxOffsetFromMarginBox() const {
@@ -365,6 +374,11 @@
                             GetContentBoxTopEdgeOffsetFromMarginBox());
 }
 
+Vector2dLayoutUnit Box::GetContentBoxOffsetFromBorderBox() const {
+  return Vector2dLayoutUnit(border_left_width() + padding_left(),
+                            border_top_width() + padding_top());
+}
+
 LayoutUnit Box::GetContentBoxLeftEdgeOffsetFromMarginBox() const {
   return margin_left() + border_left_width() + padding_left();
 }
@@ -373,6 +387,17 @@
   return margin_top() + border_top_width() + padding_top();
 }
 
+Vector2dLayoutUnit Box::GetContentBoxOffsetFromContainingBlockContentBox(
+    const ContainerBox* containing_block) const {
+  return GetContainingBlockOffsetFromItsContentBox(containing_block) +
+         GetContentBoxOffsetFromContainingBlock();
+}
+
+Vector2dLayoutUnit Box::GetContentBoxOffsetFromContainingBlock() const {
+  return Vector2dLayoutUnit(GetContentBoxLeftEdgeOffsetFromContainingBlock(),
+                            GetContentBoxTopEdgeOffsetFromContainingBlock());
+}
+
 LayoutUnit Box::GetContentBoxLeftEdgeOffsetFromContainingBlock() const {
   return left() + GetContentBoxLeftEdgeOffsetFromMarginBox();
 }
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 580ccbd..51c1758 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -223,9 +223,14 @@
   Vector2dLayoutUnit GetContainingBlockOffsetFromRoot(
       bool transform_forms_root) const;
 
+  // Returns the offset from the containing block (which can be either the
+  // containing block's content box or padding box) to its content box.
+  Vector2dLayoutUnit GetContainingBlockOffsetFromItsContentBox(
+      const ContainerBox* containing_block) const;
+
   // Used values of "left" and "top" are publicly readable and writable so that
-  // they can be calculated and adjusted by the formatting context of
-  // the parent box.
+  // they can be calculated and adjusted by the formatting context of the parent
+  // box.
   void set_left(LayoutUnit left) {
     margin_box_offset_from_containing_block_.set_x(left);
   }
@@ -320,9 +325,13 @@
   Vector2dLayoutUnit GetContentBoxOffsetFromRoot(
       bool transform_forms_root) const;
   Vector2dLayoutUnit GetContentBoxOffsetFromMarginBox() const;
+  Vector2dLayoutUnit GetContentBoxOffsetFromBorderBox() const;
   Vector2dLayoutUnit GetContentBoxOffsetFromPaddingBox() const;
   LayoutUnit GetContentBoxLeftEdgeOffsetFromMarginBox() const;
   LayoutUnit GetContentBoxTopEdgeOffsetFromMarginBox() const;
+  Vector2dLayoutUnit GetContentBoxOffsetFromContainingBlockContentBox(
+      const ContainerBox* containing_block) const;
+  Vector2dLayoutUnit GetContentBoxOffsetFromContainingBlock() const;
   LayoutUnit GetContentBoxLeftEdgeOffsetFromContainingBlock() const;
   LayoutUnit GetContentBoxTopEdgeOffsetFromContainingBlock() const;
   LayoutUnit GetContentBoxStartEdgeOffsetFromContainingBlock(
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index e64f3b2..0fe4200 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -278,23 +278,34 @@
 
 namespace {
 
-Vector2dLayoutUnit GetOffsetFromContainingBlockToParent(Box* child_box) {
-  Vector2dLayoutUnit relative_position;
-  for (Box *ancestor_box = child_box->parent(),
-           *containing_block = child_box->GetContainingBlock();
-       ancestor_box != containing_block;
-       ancestor_box = ancestor_box->parent()) {
-    DCHECK(ancestor_box)
-        << "Unable to find containing block while traversing parents.";
-    // It is not possible for the containing block to be more distant than an
-    // ancestor that is transformed.
-    DCHECK(!ancestor_box->IsTransformed());
+Vector2dLayoutUnit
+GetOffsetFromContainingBlockToParentOfAbsolutelyPositionedBox(
+    const ContainerBox* containing_block, Box* child_box) {
+  DCHECK(child_box->IsAbsolutelyPositioned());
+  DCHECK_EQ(child_box->GetContainingBlock(), containing_block);
 
-    relative_position += ancestor_box->GetContentBoxOffsetFromMarginBox();
-    relative_position +=
-        ancestor_box->margin_box_offset_from_containing_block();
+  Vector2dLayoutUnit offset;
+
+  const ContainerBox* current_box = child_box->parent();
+  while (current_box != containing_block) {
+    DCHECK(current_box->parent());
+    DCHECK(!current_box->IsTransformed());
+    const ContainerBox* next_box = current_box->GetContainingBlock();
+    offset +=
+        current_box->GetContentBoxOffsetFromContainingBlockContentBox(next_box);
+    current_box = next_box;
   }
-  return relative_position;
+
+  // The containing block is formed by the padding box instead of the content
+  // box for absolutely positioned boxes, as described in
+  // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details.
+  // NOTE: While not explicitly stated in the spec, which specifies that
+  // the containing block of a 'fixed' position element must always be the
+  // viewport, all major browsers use the padding box of a transformed ancestor
+  // as the containing block for 'fixed' position elements.
+  offset += containing_block->GetContentBoxOffsetFromPaddingBox();
+
+  return offset;
 }
 
 }  // namespace
@@ -422,15 +433,9 @@
 void ContainerBox::UpdateRectOfAbsolutelyPositionedChildBox(
     Box* child_box, const LayoutParams& child_layout_params) {
   Vector2dLayoutUnit offset_from_containing_block_to_parent =
-      GetOffsetFromContainingBlockToParent(child_box);
-  // The containing block 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.
-  // NOTE: While not explicitly stated in the spec, which specifies that
-  // the containing block of a 'fixed' position element must always be the
-  // viewport, all major browsers use the padding box of a transformed ancestor
-  // as the containing block for 'fixed' position elements.
-  offset_from_containing_block_to_parent += GetContentBoxOffsetFromPaddingBox();
+      GetOffsetFromContainingBlockToParentOfAbsolutelyPositionedBox(this,
+                                                                    child_box);
+
   child_box->SetStaticPositionLeftFromContainingBlockToParent(
       offset_from_containing_block_to_parent.x());
   child_box->SetStaticPositionTopFromContainingBlockToParent(
@@ -490,8 +495,10 @@
   // it is not empty; otherwise, returns |base_node_builder_|.
   render_tree::CompositionNode::Builder* GetActiveNodeBuilder();
 
-  Vector2dLayoutUnit GetOffsetFromChildContainerToContainingBlock(
-      const Box* containing_block,
+  // Returns the offset from the child container's content box to the containing
+  // block's content box.
+  Vector2dLayoutUnit GetOffsetFromChildContainerToContainingBlockContentBox(
+      const ContainerBox* containing_block,
       const Box::RelationshipToBox
           containing_block_relationship_to_child_container) const;
 
@@ -518,20 +525,11 @@
   const ContainerBox* child_containing_block =
       child_info.box->GetContainingBlock();
   Vector2dLayoutUnit position_offset =
-      GetOffsetFromChildContainerToContainingBlock(
+      child_container_offset_from_parent_node_ +
+      GetOffsetFromChildContainerToContainingBlockContentBox(
           child_containing_block, child_info.containing_block_relationship) +
-      child_container_offset_from_parent_node_;
-  if (child_info.box->IsAbsolutelyPositioned()) {
-    // The containing block 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.
-    // NOTE: While not explicitly stated in the spec, which specifies that
-    // the containing block of a 'fixed' position element must always be the
-    // viewport, all major browsers use the padding box of a transformed
-    // ancestor as the containing block for 'fixed' position elements.
-    position_offset -=
-        child_containing_block->GetContentBoxOffsetFromPaddingBox();
-  }
+      child_info.box->GetContainingBlockOffsetFromItsContentBox(
+          child_containing_block);
 
   child_info.box->RenderAndAnimate(GetActiveNodeBuilder(), position_offset,
                                    stacking_context_);
@@ -580,12 +578,11 @@
             cssom::KeywordValue::GetHidden());
 
   // Determine the offset from the child container to this containing block's
-  // margin box.
+  // border box.
   Vector2dLayoutUnit containing_block_border_offset =
-      GetOffsetFromChildContainerToContainingBlock(containing_block,
-                                                   Box::kIsBoxAncestor) +
-      Vector2dLayoutUnit(containing_block->margin_left(),
-                         containing_block->margin_top());
+      GetOffsetFromChildContainerToContainingBlockContentBox(
+          containing_block, Box::kIsBoxAncestor) -
+      containing_block->GetContentBoxOffsetFromBorderBox();
 
   // Apply the overflow hidden from this containing block to its composition
   // node; the resulting filter node is added to the next active node builder.
@@ -608,8 +605,8 @@
 }
 
 Vector2dLayoutUnit RenderAndAnimateStackingContextChildrenCoordinator::
-    GetOffsetFromChildContainerToContainingBlock(
-        const Box* containing_block,
+    GetOffsetFromChildContainerToContainingBlockContentBox(
+        const ContainerBox* containing_block,
         const Box::RelationshipToBox
             containing_block_relationship_to_child_container) const {
   if (containing_block_relationship_to_child_container == Box::kIsBox) {
@@ -617,12 +614,12 @@
     return Vector2dLayoutUnit();
   }
 
-  Vector2dLayoutUnit relative_position;
-  const Box* current_box =
+  Vector2dLayoutUnit offset;
+  const ContainerBox* current_box =
       containing_block_relationship_to_child_container == Box::kIsBoxAncestor
           ? child_container_
           : containing_block;
-  const Box* end_box =
+  const ContainerBox* end_box =
       containing_block_relationship_to_child_container == Box::kIsBoxAncestor
           ? containing_block
           : child_container_;
@@ -637,13 +634,9 @@
   // box).
   while (current_box != end_box && current_box->parent() &&
          !current_box->IsTransformed()) {
-    relative_position += current_box->GetContentBoxOffsetFromMarginBox();
-    relative_position += current_box->margin_box_offset_from_containing_block();
-
-    const Box* next_box = current_box->GetContainingBlock();
-    if (current_box->IsAbsolutelyPositioned()) {
-      relative_position -= next_box->GetContentBoxOffsetFromPaddingBox();
-    }
+    const ContainerBox* next_box = current_box->GetContainingBlock();
+    offset +=
+        current_box->GetContentBoxOffsetFromContainingBlockContentBox(next_box);
     current_box = next_box;
   }
 
@@ -656,25 +649,20 @@
   while (current_box != end_box) {
     DCHECK(current_box->parent());
     DCHECK(!current_box->IsTransformed());
-
-    relative_position -= current_box->GetContentBoxOffsetFromMarginBox();
-    relative_position -= current_box->margin_box_offset_from_containing_block();
-
-    const Box* next_box = current_box->GetContainingBlock();
-    if (current_box->IsAbsolutelyPositioned()) {
-      relative_position += next_box->GetContentBoxOffsetFromPaddingBox();
-    }
+    const ContainerBox* next_box = current_box->GetContainingBlock();
+    offset -=
+        current_box->GetContentBoxOffsetFromContainingBlockContentBox(next_box);
     current_box = next_box;
   }
 
   // If the containing block is an ancestor of the child container, then
-  // reverse the relative position now. The earlier calculations were for the
-  // containing block being a descendant of the child container.
+  // reverse the offset now. The earlier calculations were for the containing
+  // block being a descendant of the child container.
   if (containing_block_relationship_to_child_container == Box::kIsBoxAncestor) {
-    relative_position = -relative_position;
+    offset = -offset;
   }
 
-  return relative_position;
+  return offset;
 }
 
 }  // namespace
@@ -849,8 +837,7 @@
     stacking_context->UpdateCrossReferences();
   }
 
-  Vector2dLayoutUnit content_box_offset(border_left_width() + padding_left(),
-                                        border_top_width() + padding_top());
+  Vector2dLayoutUnit content_box_offset(GetContentBoxOffsetFromBorderBox());
 
   // Render all child stacking contexts and positioned children in our stacking
   // context that have negative z-index values.
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc
index 44f2eb8..97e84fe 100644
--- a/src/cobalt/layout/topmost_event_target.cc
+++ b/src/cobalt/layout/topmost_event_target.cc
@@ -14,8 +14,7 @@
 
 #include "cobalt/layout/topmost_event_target.h"
 
-#include <string>
-
+#include "base/optional.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/keyword_value.h"
@@ -27,6 +26,7 @@
 #include "cobalt/dom/mouse_event_init.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/pointer_event_init.h"
+#include "cobalt/dom/pointer_state.h"
 #include "cobalt/dom/ui_event.h"
 #include "cobalt/dom/wheel_event.h"
 #include "cobalt/layout/container_box.h"
@@ -37,57 +37,63 @@
 namespace cobalt {
 namespace layout {
 
-void TopmostEventTarget::FindTopmostEventTarget(
+scoped_refptr<dom::HTMLElement> TopmostEventTarget::FindTopmostEventTarget(
     const scoped_refptr<dom::Document>& document,
     const math::Vector2dF& coordinate) {
-  const scoped_refptr<dom::HTMLElement>& html_element = document->html();
+  DCHECK(document);
   DCHECK(!box_);
   DCHECK(render_sequence_.empty());
-  html_element_ = html_element;
-  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);
-      }
-    }
-  }
+  html_element_ = document->html();
+  ConsiderElement(html_element_, coordinate);
   box_ = NULL;
   render_sequence_.clear();
+  document->SetIndicatedElement(html_element_);
+  scoped_refptr<dom::HTMLElement> topmost_element;
+  topmost_element.swap(html_element_);
+  DCHECK(!html_element_);
+  return topmost_element;
 }
 
-void TopmostEventTarget::ConsiderElement(
-    const scoped_refptr<dom::HTMLElement>& html_element,
-    const math::Vector2dF& coordinate) {
-  if (!html_element) return;
-  math::Vector2dF element_coordinate(coordinate);
-  if (html_element->CanbeDesignatedByPointerIfDisplayed()) {
+namespace {
+
+LayoutBoxes* GetLayoutBoxesIfNotEmpty(dom::Element* element) {
+  dom::HTMLElement* html_element = element->AsHTMLElement();
+  if (html_element && html_element->computed_style()) {
     dom::LayoutBoxes* dom_layout_boxes = html_element->layout_boxes();
     if (dom_layout_boxes &&
         dom_layout_boxes->type() == dom::LayoutBoxes::kLayoutLayoutBoxes) {
-      DCHECK(html_element->computed_style());
       LayoutBoxes* layout_boxes =
           base::polymorphic_downcast<LayoutBoxes*>(dom_layout_boxes);
-      const Boxes& boxes = layout_boxes->boxes();
-      if (!boxes.empty()) {
-        const Box* box = boxes.front();
-        if (box->computed_style() && box->IsTransformed()) {
-          box->ApplyTransformActionToCoordinate(Box::kEnterTransform,
-                                                &element_coordinate);
-        }
-        ConsiderBoxes(html_element, layout_boxes, element_coordinate);
+      if (!layout_boxes->boxes().empty()) {
+        return layout_boxes;
       }
     }
   }
+  return NULL;
+}
 
-  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);
+}  // namespace
+void TopmostEventTarget::ConsiderElement(dom::Element* element,
+                                         const math::Vector2dF& coordinate) {
+  if (!element) return;
+  math::Vector2dF element_coordinate(coordinate);
+  LayoutBoxes* layout_boxes = GetLayoutBoxesIfNotEmpty(element);
+  if (layout_boxes) {
+    const Box* box = layout_boxes->boxes().front();
+    if (box->computed_style() && box->IsTransformed()) {
+      box->ApplyTransformActionToCoordinate(Box::kEnterTransform,
+                                            &element_coordinate);
     }
+
+    scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement();
+    if (html_element && html_element->CanbeDesignatedByPointerIfDisplayed()) {
+      ConsiderBoxes(html_element, layout_boxes, element_coordinate);
+    }
+  }
+
+  for (dom::Element* child_element = element->first_element_child();
+       child_element; child_element = child_element->next_element_sibling()) {
+    ConsiderElement(child_element, element_coordinate);
   }
 }
 
@@ -114,152 +120,276 @@
   }
 }
 
+namespace {
+void SendStateChangeEvents(bool is_pointer_event,
+                           scoped_refptr<dom::HTMLElement> previous_element,
+                           scoped_refptr<dom::HTMLElement> target_element,
+                           dom::PointerEventInit* event_init) {
+  // Send enter/leave/over/out (status change) events when needed.
+  if (previous_element != target_element) {
+    const scoped_refptr<dom::Window>& view = event_init->view();
+
+    // The enter/leave status change events apply to all ancestors up to the
+    // nearest common ancestor between the previous and current element.
+    scoped_refptr<dom::Element> nearest_common_ancestor;
+
+    // Send out and leave events.
+    if (previous_element) {
+      event_init->set_related_target(target_element);
+      if (is_pointer_event) {
+        previous_element->DispatchEvent(new dom::PointerEvent(
+            base::Tokens::pointerout(), view, *event_init));
+      }
+      previous_element->DispatchEvent(
+          new dom::MouseEvent(base::Tokens::mouseout(), view, *event_init));
+
+      // Find the nearest common ancestor, if there is any.
+      dom::Document* previous_document = previous_element->node_document();
+      if (previous_document) {
+        if (target_element &&
+            previous_document == target_element->node_document()) {
+          // The nearest ancestor of the current element that is already
+          // designated is the nearest common ancestor of it and the previous
+          // element.
+          nearest_common_ancestor = target_element;
+          while (nearest_common_ancestor &&
+                 nearest_common_ancestor->AsHTMLElement() &&
+                 !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) {
+            nearest_common_ancestor = nearest_common_ancestor->parent_element();
+          }
+        }
+
+        for (scoped_refptr<dom::Element> element = previous_element;
+             element && element != nearest_common_ancestor;
+             element = element->parent_element()) {
+          if (is_pointer_event) {
+            element->DispatchEvent(new dom::PointerEvent(
+                base::Tokens::pointerleave(), dom::Event::kNotBubbles,
+                dom::Event::kNotCancelable, view, *event_init));
+          }
+          element->DispatchEvent(new dom::MouseEvent(
+              base::Tokens::mouseleave(), dom::Event::kNotBubbles,
+              dom::Event::kNotCancelable, view, *event_init));
+        }
+
+        if (!target_element ||
+            previous_document != target_element->node_document()) {
+          previous_document->SetIndicatedElement(NULL);
+        }
+      }
+    }
+
+    // Send over and enter events.
+    if (target_element) {
+      event_init->set_related_target(previous_element);
+      if (is_pointer_event) {
+        target_element->DispatchEvent(new dom::PointerEvent(
+            base::Tokens::pointerover(), view, *event_init));
+      }
+      target_element->DispatchEvent(
+          new dom::MouseEvent(base::Tokens::mouseover(), view, *event_init));
+
+      for (scoped_refptr<dom::Element> element = target_element;
+           element != nearest_common_ancestor;
+           element = element->parent_element()) {
+        if (is_pointer_event) {
+          element->DispatchEvent(new dom::PointerEvent(
+              base::Tokens::pointerenter(), dom::Event::kNotBubbles,
+              dom::Event::kNotCancelable, view, *event_init));
+        }
+        element->DispatchEvent(new dom::MouseEvent(
+            base::Tokens::mouseenter(), dom::Event::kNotBubbles,
+            dom::Event::kNotCancelable, view, *event_init));
+      }
+    }
+  }
+}
+
+void SendCompatibilityMappingMouseEvent(
+    const scoped_refptr<dom::HTMLElement>& target_element,
+    const scoped_refptr<dom::Event>& event,
+    const dom::PointerEvent* pointer_event,
+    const dom::PointerEventInit& event_init,
+    std::set<std::string>* mouse_event_prevent_flags) {
+  // Send compatibility mapping mouse event if needed.
+  //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#compatibility-mapping-with-mouse-events
+  bool has_compatibility_mouse_event = true;
+  base::Token type = pointer_event->type();
+  if (type == base::Tokens::pointerdown()) {
+    // If the pointer event dispatched was pointerdown and the event was
+    // canceled, then set the PREVENT MOUSE EVENT flag for this pointerType.
+    if (event->default_prevented()) {
+      mouse_event_prevent_flags->insert(pointer_event->pointer_type());
+      has_compatibility_mouse_event = false;
+    } else {
+      type = base::Tokens::mousedown();
+    }
+  } else {
+    has_compatibility_mouse_event =
+        mouse_event_prevent_flags->find(pointer_event->pointer_type()) ==
+        mouse_event_prevent_flags->end();
+    if (type == base::Tokens::pointerup()) {
+      // If the pointer event dispatched was pointerup, clear the PREVENT
+      // MOUSE EVENT flag for this pointerType.
+      mouse_event_prevent_flags->erase(pointer_event->pointer_type());
+      type = base::Tokens::mouseup();
+    } else if (type == base::Tokens::pointermove()) {
+      type = base::Tokens::mousemove();
+    } else {
+      has_compatibility_mouse_event = false;
+    }
+  }
+  if (has_compatibility_mouse_event) {
+    target_element->DispatchEvent(
+        new dom::MouseEvent(type, event_init.view(), event_init));
+  }
+}
+
+void InitializePointerEventInitFromEvent(
+    const dom::MouseEvent* const mouse_event,
+    const dom::PointerEvent* pointer_event, dom::PointerEventInit* event_init) {
+  // For EventInit
+  event_init->set_bubbles(mouse_event->bubbles());
+  event_init->set_cancelable(mouse_event->cancelable());
+
+  // For UIEventInit
+  event_init->set_view(mouse_event->view());
+  event_init->set_detail(mouse_event->detail());
+  event_init->set_which(mouse_event->which());
+
+  // For EventModifierInit
+  event_init->set_ctrl_key(mouse_event->ctrl_key());
+  event_init->set_shift_key(mouse_event->shift_key());
+  event_init->set_alt_key(mouse_event->alt_key());
+  event_init->set_meta_key(mouse_event->meta_key());
+
+  // For MouseEventInit
+  event_init->set_screen_x(mouse_event->screen_x());
+  event_init->set_screen_y(mouse_event->screen_y());
+  event_init->set_client_x(mouse_event->screen_x());
+  event_init->set_client_y(mouse_event->screen_y());
+  event_init->set_button(mouse_event->button());
+  event_init->set_buttons(mouse_event->buttons());
+  event_init->set_related_target(mouse_event->related_target());
+  if (pointer_event) {
+    // For PointerEventInit
+    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());
+  }
+}
+}  // namespace
+
 void TopmostEventTarget::MaybeSendPointerEvents(
     const scoped_refptr<dom::Event>& event) {
   const dom::MouseEvent* const mouse_event =
       base::polymorphic_downcast<const dom::MouseEvent* const>(event.get());
   DCHECK(mouse_event);
   DCHECK(!html_element_);
-  scoped_refptr<dom::Window> view = mouse_event->view();
+  const dom::PointerEvent* pointer_event =
+      (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>())
+          ? base::polymorphic_downcast<const dom::PointerEvent* const>(
+                event.get())
+          : NULL;
+  bool is_touchpad_event = false;
 
-  math::Vector2dF coordinate(static_cast<float>(mouse_event->client_x()),
-                             static_cast<float>(mouse_event->client_y()));
-  FindTopmostEventTarget(view->document(), coordinate);
+  // The target override element for the pointer event. This may not be the same
+  // as the hit test target, and it also may not be set.
+  scoped_refptr<dom::HTMLElement> target_override_element;
 
-  if (html_element_) {
-    html_element_->DispatchEvent(event);
+  // Store the data for the status change and pointer capture event(s).
+  dom::PointerEventInit event_init;
+  InitializePointerEventInitFromEvent(mouse_event, pointer_event, &event_init);
+  const scoped_refptr<dom::Window>& view = event_init.view();
+  if (!view) {
+    return;
   }
-  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;
+  dom::PointerState* pointer_state = view->document()->pointer_state();
+  if (pointer_event) {
+    pointer_state->SetActiveButtonsState(pointer_event->pointer_id(),
+                                         pointer_event->buttons());
+    is_touchpad_event = pointer_event->pointer_type() == "touchpad";
+    if (is_touchpad_event) {
       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_y());
-        mouse_event_init.set_client_x(pointer_event->screen_x());
-        mouse_event_init.set_client_y(pointer_event->screen_y());
-        mouse_event_init.set_button(pointer_event->button());
-        mouse_event_init.set_buttons(pointer_event->buttons());
-        html_element_->DispatchEvent(
-            new dom::MouseEvent(type, view, mouse_event_init));
-        if (pointer_event->type() == base::Tokens::pointerup()) {
-          type = base::Tokens::click();
-          html_element_->DispatchEvent(
-              new dom::MouseEvent(type, view, mouse_event_init));
+        pointer_state->SetActive(pointer_event->pointer_id());
+        // Implicitly capture the pointer to the active element.
+        //   https://www.w3.org/TR/pointerevents/#implicit-pointer-capture
+        scoped_refptr<dom::HTMLElement> html_element =
+            view->document()->active_element()->AsHTMLElement();
+        if (html_element) {
+          pointer_state->SetPendingPointerCaptureTargetOverride(
+              pointer_event->pointer_id(), html_element);
         }
       }
+    } else {
+      pointer_state->SetActive(pointer_event->pointer_id());
     }
+    target_override_element = pointer_state->GetPointerCaptureOverrideElement(
+        pointer_event->pointer_id(), &event_init);
+  }
 
-    scoped_refptr<dom::HTMLElement> previous_html_element(
-        previous_html_element_weak_);
+  scoped_refptr<dom::HTMLElement> target_element;
+  if (target_override_element) {
+    target_element = target_override_element;
+  } else {
+    // Do a hit test if there is no target override element.
+    math::Vector2dF coordinate(static_cast<float>(event_init.client_x()),
+                               static_cast<float>(event_init.client_y()));
+    target_element = FindTopmostEventTarget(view->document(), coordinate);
+  }
 
-    // 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);
-      event_init.set_screen_x(mouse_event->screen_x());
-      event_init.set_screen_y(mouse_event->screen_y());
-      event_init.set_client_x(mouse_event->screen_x());
-      event_init.set_client_y(mouse_event->screen_y());
-      if (event->GetWrappableType() == base::GetTypeId<dom::PointerEvent>()) {
-        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());
+  if (target_element) {
+    target_element->DispatchEvent(event);
+  }
+
+  if (pointer_event) {
+    if (pointer_event->type() == base::Tokens::pointerup()) {
+      if (is_touchpad_event) {
+        // A touchpad becomes inactive after a pointerup.
+        pointer_state->ClearActive(pointer_event->pointer_id());
       }
-
-      // 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(), view, event_init));
-        previous_html_element->DispatchEvent(
-            new dom::MouseEvent(base::Tokens::mouseout(), view, 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, view, event_init));
-            element->DispatchEvent(new dom::MouseEvent(
-                base::Tokens::mouseleave(), dom::Event::kNotBubbles,
-                dom::Event::kNotCancelable, view, 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(), view, event_init));
-        html_element_->DispatchEvent(
-            new dom::MouseEvent(base::Tokens::mouseover(), view, 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, view, event_init));
-          element->DispatchEvent(new dom::MouseEvent(
-              base::Tokens::mouseenter(), dom::Event::kNotBubbles,
-              dom::Event::kNotCancelable, view, event_init));
-        }
-
-        dom::Document* document = html_element_->node_document();
-        if (document) {
-          document->SetIndicatedElement(html_element_);
-        }
-      }
-      previous_html_element_weak_ = base::AsWeakPtr(html_element_.get());
+      // Implicit release of pointer capture.
+      //   https://www.w3.org/TR/pointerevents/#implicit-release-of-pointer-capture
+      pointer_state->ClearPendingPointerCaptureTargetOverride(
+          pointer_event->pointer_id());
+    }
+    if (target_element) {
+      SendCompatibilityMappingMouseEvent(target_element, event, pointer_event,
+                                         event_init,
+                                         &mouse_event_prevent_flags_);
     }
   }
-  html_element_ = NULL;
+
+  if (target_element && !is_touchpad_event) {
+    // Send the click event if needed, which is not prevented by canceling the
+    // pointerdown event.
+    //   https://www.w3.org/TR/uievents/#event-type-click
+    //   https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events
+    if (event_init.button() == 0 &&
+        ((mouse_event->type() == base::Tokens::pointerup()) ||
+         (mouse_event->type() == base::Tokens::mouseup()))) {
+      target_element->DispatchEvent(
+          new dom::MouseEvent(base::Tokens::click(), view, event_init));
+    }
+  }
+
+  scoped_refptr<dom::HTMLElement> previous_html_element(
+      previous_html_element_weak_);
+
+  SendStateChangeEvents(pointer_event, previous_html_element, target_element,
+                        &event_init);
+
+  if (target_element) {
+    previous_html_element_weak_ = base::AsWeakPtr(target_element.get());
+  } else {
+    previous_html_element_weak_.reset();
+  }
+  DCHECK(!html_element_);
 }
 
 }  // namespace layout
diff --git a/src/cobalt/layout/topmost_event_target.h b/src/cobalt/layout/topmost_event_target.h
index be73764..8ebf547 100644
--- a/src/cobalt/layout/topmost_event_target.h
+++ b/src/cobalt/layout/topmost_event_target.h
@@ -15,10 +15,13 @@
 #ifndef COBALT_LAYOUT_TOPMOST_EVENT_TARGET_H_
 #define COBALT_LAYOUT_TOPMOST_EVENT_TARGET_H_
 
+#include <set>
+#include <string>
+
 #include "base/memory/weak_ptr.h"
 #include "cobalt/dom/document.h"
+#include "cobalt/dom/event.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"
@@ -33,20 +36,26 @@
 
   void MaybeSendPointerEvents(const scoped_refptr<dom::Event>& event);
 
+ private:
+  scoped_refptr<dom::HTMLElement> FindTopmostEventTarget(
+      const scoped_refptr<dom::Document>& document,
+      const math::Vector2dF& coordinate);
+
+  void ConsiderElement(dom::Element* element,
+                       const math::Vector2dF& coordinate);
+  void ConsiderBoxes(const scoped_refptr<dom::HTMLElement>& html_element,
+                     LayoutBoxes* layout_boxes,
+                     const math::Vector2dF& coordinate);
+
   base::WeakPtr<dom::HTMLElement> previous_html_element_weak_;
   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);
+  // This map stores the pointer types for which the 'prevent mouse event' flag
+  // has been set as part of the compatibility mapping steps defined at
+  // https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events.
+  std::set<std::string> mouse_event_prevent_flags_;
 };
 
 }  // namespace layout
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
index 87dab78..13a4626 100644
--- a/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
+++ b/src/cobalt/layout_tests/testdata/web-platform-tests/XMLHttpRequest/web_platform_tests.txt
@@ -170,7 +170,7 @@
 send-redirect-infinite.htm,PASS
 send-redirect-infinite-sync.htm,FAIL
 send-redirect-no-location.htm,FAIL
-send-redirect-to-cors.htm,PASS
+send-redirect-to-cors.htm,DISABLE
 send-redirect-to-non-cors.htm,FAIL
 send-response-event-order.htm,FAIL
 send-response-upload-event-loadend.htm,PASS
diff --git a/src/cobalt/network/network_module.cc b/src/cobalt/network/network_module.cc
index bb4e074..f9fb043 100644
--- a/src/cobalt/network/network_module.cc
+++ b/src/cobalt/network/network_module.cc
@@ -122,6 +122,7 @@
   base::Thread::Options thread_options;
   thread_options.message_loop_type = MessageLoop::TYPE_IO;
   thread_options.stack_size = 256 * 1024;
+  thread_options.priority = base::kThreadPriority_High;
   thread_->StartWithOptions(thread_options);
 
   base::WaitableEvent creation_event(true, false);
diff --git a/src/cobalt/network/persistent_cookie_store.cc b/src/cobalt/network/persistent_cookie_store.cc
index f6e37de..19aa978 100644
--- a/src/cobalt/network/persistent_cookie_store.cc
+++ b/src/cobalt/network/persistent_cookie_store.cc
@@ -148,7 +148,7 @@
   insert_cookie.BindBool(11, cc.IsHttpOnly());
   bool ok = insert_cookie.Run();
   DCHECK(ok);
-  sql_context->Flush();
+  sql_context->FlushOnChange();
 }
 
 void SqlUpdateCookieAccessTime(const net::CanonicalCookie& cc,
@@ -171,7 +171,7 @@
   touch_cookie.BindString(3, cc.Path());
   bool ok = touch_cookie.Run();
   DCHECK(ok);
-  sql_context->Flush();
+  sql_context->FlushOnChange();
 }
 
 void SqlDeleteCookie(const net::CanonicalCookie& cc,
@@ -185,7 +185,7 @@
   delete_cookie.BindString(2, cc.Path());
   bool ok = delete_cookie.Run();
   DCHECK(ok);
-  sql_context->Flush();
+  sql_context->FlushOnChange();
 }
 
 void SqlSendEmptyCookieList(
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 6113bac..c82b19d 100644
--- a/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
+++ b/src/cobalt/network/starboard/user_agent_string_factory_starboard.cc
@@ -27,7 +27,8 @@
 
 #if SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION
 const char kStarboardStabilitySuffix[] = "-Experimental";
-#elif SB_API_VERSION == SB_RELEASE_CANDIDATE_API_VERSION
+#elif SB_API_VERSION >= SB_RELEASE_CANDIDATE_API_VERSION && \
+    SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION
 const char kStarboardStabilitySuffix[] = "-ReleaseCandidate";
 #else
 const char kStarboardStabilitySuffix[] = "";
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_object.h b/src/cobalt/renderer/rasterizer/egl/draw_object.h
index cc40d09..1e2fb12 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_object.h
+++ b/src/cobalt/renderer/rasterizer/egl/draw_object.h
@@ -17,12 +17,14 @@
 
 #include <GLES2/gl2.h>
 
+#include "base/callback.h"
 #include "base/optional.h"
 #include "cobalt/base/type_id.h"
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/math/rect.h"
 #include "cobalt/render_tree/color_rgba.h"
 #include "cobalt/render_tree/rounded_corners.h"
+#include "cobalt/renderer/backend/egl/texture.h"
 #include "cobalt/renderer/rasterizer/egl/graphics_state.h"
 #include "cobalt/renderer/rasterizer/egl/shader_program_manager.h"
 
@@ -36,6 +38,21 @@
  public:
   typedef base::optional<render_tree::RoundedCorners> OptionalRoundedCorners;
 
+  // Callback to get a scratch "1D" texture of the given |size|. If such a
+  // request was previously fulfilled for the relevant render tree node, then
+  // the cached |texture| and |region| will be returned, and |is_new| will be
+  // false; this is a hint as to whether the texture region needs to be
+  // initialized. If the request succeeded, then |texture| will point to a 2D
+  // texture whose |region| is a row meeting the size requirement; otherwise,
+  // |texture| will be nullptr.
+  struct TextureInfo {
+    const backend::TextureEGL* texture;
+    math::RectF region;
+    bool is_new;
+  };
+  typedef base::Callback<void(float size, TextureInfo* out_texture_info)>
+      GetScratchTextureFunction;
+
   // Structure containing the common attributes for all DrawObjects.
   struct BaseState {
     BaseState();
@@ -76,6 +93,14 @@
   DrawObject() {}
   explicit DrawObject(const BaseState& base_state);
 
+  // Utility function to get the render color for the blend modes that will
+  // be used. These modes expect alpha to be pre-multiplied.
+  static render_tree::ColorRGBA GetDrawColor(
+      const render_tree::ColorRGBA& color) {
+    return render_tree::ColorRGBA(color.r() * color.a(), color.g() * color.a(),
+                                  color.b() * color.a(), color.a());
+  }
+
   // Return a uint32_t suitable to be transferred as 4 unsigned bytes
   // representing color to a GL shader.
   static uint32_t GetGLRGBA(float r, float g, float b, float a);
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
index 7fbb6fe..d56c717 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_poly_color.cc
@@ -31,7 +31,7 @@
     : DrawObject(base_state),
       vertex_buffer_(NULL) {
   attributes_.reserve(4);
-  AddRect(rect, GetGLRGBA(color * base_state_.opacity));
+  AddRect(rect, GetGLRGBA(GetDrawColor(color) * base_state_.opacity));
   graphics_state->ReserveVertexData(
       attributes_.size() * sizeof(VertexAttributes));
 }
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
new file mode 100644
index 0000000..cdc1d88
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.cc
@@ -0,0 +1,206 @@
+// 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_rect_border.h"
+
+#include <GLES2/gl2.h>
+
+#include "base/logging.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 {
+
+namespace {
+// The border is drawn using three regions: an antialiased outer area, an
+// antialiased inner area, and the solid area in between.
+const int kRegionCount = 3;
+
+// Each region consists of 2 rects which use 4 vertices each. Each region is
+// drawn with 8 triangles, and indices are used to minimize vertex duplication.
+const int kVertexCountPerRegion = 4 * 2;
+const int kIndexCountPerRegion = 8 * 3;
+
+const int kVertexCount = kRegionCount * kVertexCountPerRegion;
+const int kIndexCount = kRegionCount * kIndexCountPerRegion;
+}  // namespace
+
+DrawRectBorder::DrawRectBorder(GraphicsState* graphics_state,
+    const BaseState& base_state,
+    const scoped_refptr<render_tree::RectNode>& node)
+    : DrawPolyColor(base_state),
+      index_buffer_(NULL) {
+  DCHECK(node->data().border);
+  const render_tree::Border& border = *(node->data().border);
+
+  // Only uniform, non-rounded borders are supported.
+  is_valid_ = !node->data().rounded_corners &&
+              border.left.style == border.right.style &&
+              border.left.style == border.top.style &&
+              border.left.style == border.bottom.style &&
+              border.left.color == border.right.color &&
+              border.left.color == border.top.color &&
+              border.left.color == border.bottom.color;
+
+  // If a background brush is used, then only solid colored ones are supported.
+  // This simplifies blending the inner-antialiased border with the content.
+  render_tree::ColorRGBA content_color(0);
+  if (is_valid_ && node->data().background_brush) {
+    is_valid_ = node->data().background_brush->GetTypeId() ==
+                base::GetTypeId<render_tree::SolidColorBrush>();
+    if (is_valid_) {
+      const render_tree::SolidColorBrush* solid_brush =
+          base::polymorphic_downcast<const render_tree::SolidColorBrush*>
+              (node->data().background_brush.get());
+      content_color = GetDrawColor(solid_brush->color()) * base_state_.opacity;
+    }
+  }
+
+  if (is_valid_) {
+    content_rect_ = node->data().rect;
+    content_rect_.Inset(border.left.width, border.top.width,
+                        border.right.width, border.bottom.width);
+    node_bounds_ = node->data().rect;
+
+    if (border.left.style == render_tree::kBorderStyleSolid) {
+      attributes_.reserve(kVertexCount);
+      indices_.reserve(kIndexCount);
+      render_tree::ColorRGBA border_color =
+          GetDrawColor(border.left.color) * base_state_.opacity;
+      is_valid_ = SetSquareBorder(border, node->data().rect, content_rect_,
+                                  border_color, content_color);
+      if (is_valid_ && attributes_.size() > 0) {
+        graphics_state->ReserveVertexData(
+            attributes_.size() * sizeof(VertexAttributes));
+        graphics_state->ReserveVertexIndices(indices_.size());
+      }
+    }
+  }
+}
+
+void DrawRectBorder::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  if (attributes_.size() > 0) {
+    vertex_buffer_ = graphics_state->AllocateVertexData(
+        attributes_.size() * sizeof(VertexAttributes));
+    SbMemoryCopy(vertex_buffer_, &attributes_[0],
+                 attributes_.size() * sizeof(VertexAttributes));
+    index_buffer_ = graphics_state->AllocateVertexIndices(indices_.size());
+    SbMemoryCopy(index_buffer_, &indices_[0],
+                 indices_.size() * sizeof(indices_[0]));
+  }
+}
+
+void DrawRectBorder::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  if (attributes_.size() > 0) {
+    SetupShader(graphics_state, program_manager);
+    GL_CALL(glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT,
+        graphics_state->GetVertexIndexPointer(index_buffer_)));
+  }
+}
+
+bool DrawRectBorder::SetSquareBorder(const render_tree::Border& border,
+    const math::RectF& border_rect, const math::RectF& content_rect,
+    const render_tree::ColorRGBA& border_color,
+    const render_tree::ColorRGBA& content_color) {
+  // Extract scale from the transform matrix. This can be reliably done if no
+  // rotations are involved.
+  const float kEpsilon = 0.0001f;
+  if (std::abs(base_state_.transform(0, 1)) >= kEpsilon ||
+      std::abs(base_state_.transform(1, 0)) >= kEpsilon) {
+    return false;
+  }
+
+  // If the scale is 0 in either direction, then there's nothing to render.
+  float scale_x = std::abs(base_state_.transform(0, 0));
+  float scale_y = std::abs(base_state_.transform(1, 1));
+  if (scale_x <= kEpsilon || scale_y <= kEpsilon) {
+    return true;
+  }
+
+  // Antialiased subpixel borders are not supported at this time. It can be
+  // done by attenuating the alpha, but this can get complicated if the borders
+  // are of different widths.
+  float pixel_size_x = 1.0f / scale_x;
+  float pixel_size_y = 1.0f / scale_y;
+  if (border.left.width < pixel_size_x || border.right.width < pixel_size_x ||
+      border.top.width < pixel_size_y || border.bottom.width < pixel_size_y) {
+    return false;
+  }
+
+  // To antialias the edges, shrink the borders by half a pixel, then add a
+  // 1-pixel edge which transitions to 0 (for outer) or content_color (for
+  // inner). The rasterizer will handle interpolating to the correct color.
+  math::RectF outer_rect(border_rect);
+  math::RectF inner_rect(content_rect);
+  outer_rect.Inset(0.5f * pixel_size_x, 0.5f * pixel_size_y);
+  inner_rect.Outset(0.5f * pixel_size_x, 0.5f * pixel_size_y);
+  math::RectF outer_outer(outer_rect);
+  math::RectF inner_inner(inner_rect);
+  outer_outer.Outset(pixel_size_x, pixel_size_y);
+  inner_inner.Inset(pixel_size_x, pixel_size_y);
+
+  uint32_t border_color32 = GetGLRGBA(border_color);
+  uint32_t content_color32 = GetGLRGBA(content_color);
+  AddRegion(outer_outer, 0, outer_rect, border_color32);
+  AddRegion(outer_rect, border_color32, inner_rect, border_color32);
+  AddRegion(inner_rect, border_color32, inner_inner, content_color32);
+
+  // Update the content and node bounds to account for the antialiasing edges.
+  node_bounds_ = outer_outer;
+  content_rect_ = inner_inner;
+  return true;
+}
+
+void DrawRectBorder::AddRegion(
+    const math::RectF& outer_rect, uint32_t outer_color,
+    const math::RectF& inner_rect, uint32_t inner_color) {
+  // Add triangles to render the area between the two rects.
+  uint16_t first_vertex = static_cast<uint16_t>(attributes_.size());
+  AddVertex(outer_rect.x(), outer_rect.y(), outer_color);
+  AddVertex(inner_rect.x(), inner_rect.y(), inner_color);
+  AddVertex(outer_rect.right(), outer_rect.y(), outer_color);
+  AddVertex(inner_rect.right(), inner_rect.y(), inner_color);
+  AddVertex(outer_rect.right(), outer_rect.bottom(), outer_color);
+  AddVertex(inner_rect.right(), inner_rect.bottom(), inner_color);
+  AddVertex(outer_rect.x(), outer_rect.bottom(), outer_color);
+  AddVertex(inner_rect.x(), inner_rect.bottom(), inner_color);
+
+  // Use indices to minimize duplication of vertex data. The last two triangles
+  // use the first one or two vertices of the region.
+  uint16_t wrap_start = static_cast<uint16_t>(attributes_.size()) - 2;
+  for (uint16_t i = first_vertex; i < wrap_start; ++i) {
+    indices_.push_back(i);
+    indices_.push_back(i + 1);
+    indices_.push_back(i + 2);
+  }
+  indices_.push_back(wrap_start);
+  indices_.push_back(wrap_start + 1);
+  indices_.push_back(first_vertex);
+  indices_.push_back(wrap_start + 1);
+  indices_.push_back(first_vertex);
+  indices_.push_back(first_vertex + 1);
+}
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_border.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.h
new file mode 100644
index 0000000..5139fff
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_border.h
@@ -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.
+
+#ifndef COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_BORDER_H_
+#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_BORDER_H_
+
+#include <vector>
+
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/render_tree/rect_node.h"
+#include "cobalt/renderer/rasterizer/egl/draw_poly_color.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+// Handles drawing the antialiased border for a RectNode. Only certain border
+// settings are supported, so check DrawRectBorder::IsValid to verify support.
+class DrawRectBorder : public DrawPolyColor {
+ public:
+  DrawRectBorder(GraphicsState* graphics_state,
+                 const BaseState& base_state,
+                 const scoped_refptr<render_tree::RectNode>& node);
+
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+
+  bool IsValid() const { return is_valid_; }
+  const math::RectF& GetContentRect() const { return content_rect_; }
+  const math::RectF& GetBounds() const { return node_bounds_; }
+
+ private:
+  bool SetSquareBorder(const render_tree::Border& border,
+                       const math::RectF& border_rect,
+                       const math::RectF& content_rect,
+                       const render_tree::ColorRGBA& border_color,
+                       const render_tree::ColorRGBA& content_color);
+  void AddRegion(const math::RectF& outer_rect, uint32_t outer_color,
+                 const math::RectF& inner_rect, uint32_t inner_color);
+
+  math::RectF content_rect_;
+  math::RectF node_bounds_;
+  std::vector<uint16_t> indices_;
+  uint16_t* index_buffer_;
+  bool is_valid_;
+};
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_BORDER_H_
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 cc74dad..88b753f 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_color_texture.cc
@@ -47,7 +47,7 @@
       vertex_buffer_(NULL),
       clamp_texcoords_(clamp_texcoords),
       tile_texture_(false) {
-  color_ = GetGLRGBA(color * base_state_.opacity);
+  color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
   graphics_state->ReserveVertexData(4 * sizeof(VertexAttributes));
 }
 
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 a4b3e4b..9dae961 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.cc
@@ -220,10 +220,7 @@
 
 uint32_t DrawRectLinearGradient::GetGLColor(
     const render_tree::ColorStop& color_stop) {
-  const render_tree::ColorRGBA& color = color_stop.color;
-  float alpha = base_state_.opacity * color.a();
-  return GetGLRGBA(color.r() * alpha, color.g() * alpha, color.b() * alpha,
-                   alpha);
+  return GetGLRGBA(GetDrawColor(color_stop.color) * base_state_.opacity);
 }
 
 void DrawRectLinearGradient::AddVertex(float x, float y, uint32_t color) {
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.cc
new file mode 100644
index 0000000..eac8882
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.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 "cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.h"
+
+#include <GLES2/gl2.h>
+#include <algorithm>
+
+#include "base/logging.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 {
+
+namespace {
+const int kVertexCount = 4;
+}  // namespace
+
+DrawRectRadialGradient::DrawRectRadialGradient(GraphicsState* graphics_state,
+    const BaseState& base_state, const math::RectF& rect,
+    const render_tree::RadialGradientBrush& brush,
+    const GetScratchTextureFunction& get_scratch_texture)
+    : DrawObject(base_state),
+      lookup_texture_(nullptr),
+      vertex_buffer_(nullptr) {
+  // Calculate the number of pixels needed for the color lookup texture. There
+  // should be enough so that each color stop has a pixel with the color
+  // blended at 100%. However, since the lookup texture will be used with
+  // linear filtering, reducing the texture size will only impact accuracy at
+  // the color stops (but not between them).
+  const float kLookupSizes[] = {
+    // These represent breakpoints that are likely to be used.
+    1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 8.0f, 10.0f, 20.0f, 50.0f, 100.0f,
+  };
+  const render_tree::ColorStopList& color_stops = brush.color_stops();
+
+  int lookup_size_index = 0;
+  for (int i = 0; i < color_stops.size();) {
+    float scaled = color_stops[i].position * kLookupSizes[lookup_size_index];
+    float fraction = scaled - std::floor(scaled);
+    if (fraction > 0.001f && lookup_size_index < arraysize(kLookupSizes) - 1) {
+      ++lookup_size_index;
+      i = 0;
+    } else {
+      ++i;
+    }
+  }
+
+  // Create and initialize the lookup texture if needed. Reserve an additional
+  // pixel to represent color stop position 1.0 blended to 100%.
+  float lookup_size = kLookupSizes[lookup_size_index] + 1.0f;
+  TextureInfo texture_info;
+  get_scratch_texture.Run(lookup_size, &texture_info);
+  lookup_texture_ = texture_info.texture;
+  lookup_region_ = texture_info.region;
+  if (texture_info.is_new) {
+    InitializeLookupTexture(brush);
+  }
+
+  // Add the geometry if a lookup texture was available.
+  if (lookup_texture_) {
+    attributes_.reserve(kVertexCount);
+    math::PointF offset_center(brush.center());
+    math::PointF offset_scale(1.0f / std::max(brush.radius_x(), 0.001f),
+                              1.0f / std::max(brush.radius_y(), 0.001f));
+    AddVertex(rect.x(), rect.y(), offset_center, offset_scale);
+    AddVertex(rect.right(), rect.y(), offset_center, offset_scale);
+    AddVertex(rect.right(), rect.bottom(), offset_center, offset_scale);
+    AddVertex(rect.x(), rect.bottom(), offset_center, offset_scale);
+    graphics_state->ReserveVertexData(
+        attributes_.size() * sizeof(VertexAttributes));
+  }
+}
+
+void DrawRectRadialGradient::ExecuteUpdateVertexBuffer(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  if (attributes_.size() > 0) {
+    vertex_buffer_ = graphics_state->AllocateVertexData(
+        attributes_.size() * sizeof(VertexAttributes));
+    SbMemoryCopy(vertex_buffer_, &attributes_[0],
+                 attributes_.size() * sizeof(VertexAttributes));
+  }
+}
+
+void DrawRectRadialGradient::ExecuteRasterize(
+    GraphicsState* graphics_state,
+    ShaderProgramManager* program_manager) {
+  if (attributes_.size() > 0) {
+    ShaderProgram<ShaderVertexOffset,
+                  ShaderFragmentOpacityTexcoord1d>* 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_offset(), 2, GL_FLOAT, GL_FALSE,
+        sizeof(VertexAttributes), vertex_buffer_ +
+        offsetof(VertexAttributes, offset));
+    graphics_state->VertexAttribFinish();
+
+    // Map radial length [0, 1] to texture coordinates for the lookup texture.
+    // |u_texcoord_transform| represents (u-scale, u-add, u-max, v-center).
+    const float kTextureWidthScale = 1.0f / lookup_texture_->GetSize().width();
+    GL_CALL(glUniform4f(program->GetFragmentShader().u_texcoord_transform(),
+        (lookup_region_.width() - 1.0f) * kTextureWidthScale,
+        (lookup_region_.x() + 0.5f) * kTextureWidthScale,
+        (lookup_region_.right() - 0.5f) * kTextureWidthScale,
+        (lookup_region_.y() + 0.5f) / lookup_texture_->GetSize().height()));
+    GL_CALL(glUniform1f(program->GetFragmentShader().u_opacity(),
+        base_state_.opacity));
+    graphics_state->ActiveBindTexture(
+        program->GetFragmentShader().u_texture_texunit(),
+        lookup_texture_->GetTarget(), lookup_texture_->gl_handle());
+    GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, attributes_.size()));
+  }
+}
+
+base::TypeId DrawRectRadialGradient::GetTypeId() const {
+  return ShaderProgram<ShaderVertexColorOffset,
+                       ShaderFragmentOpacityTexcoord1d>::GetTypeId();
+}
+
+void DrawRectRadialGradient::InitializeLookupTexture(
+    const render_tree::RadialGradientBrush& brush) {
+  const render_tree::ColorStopList& color_stops = brush.color_stops();
+
+  // The lookup texture is a row of RGBA pixels. Ensure the last pixel contains
+  // the last color stop blended to 100%.
+  // NOTE: The base_state_.opacity should not be baked into the lookup texture
+  //       as opacity can be animated.
+  const float kTexelToStop = 1.0f / (lookup_region_.width() - 1.0f);
+  const float kStopToTexel = lookup_region_.width() - 1.0f;
+  uint8_t* lookup_buffer =
+      new uint8_t[static_cast<int>(lookup_region_.width()) * 4];
+
+  size_t color_index = 0;
+  float position_prev = 0.0f;
+  float position_next = color_stops[color_index].position * kStopToTexel;
+  float position_scale = 1.0f / std::max(position_next - position_prev, 1.0f);
+  render_tree::ColorRGBA color_prev =
+      GetDrawColor(color_stops[color_index].color);
+  render_tree::ColorRGBA color_next = color_prev;
+  uint8_t* pixel = lookup_buffer;
+
+  // Fill in the blended colors according to the color stops.
+  for (float texel = 0.0f; texel < lookup_region_.width(); texel += 1.0f) {
+    while (texel > position_next) {
+      position_prev = position_next;
+      color_prev = color_next;
+      if (color_index < color_stops.size()) {
+        // Advance to the next color stop.
+        ++color_index;
+        position_next = color_stops[color_index].position * kStopToTexel;
+        color_next = GetDrawColor(color_stops[color_index].color);
+      } else {
+        // Persist the current color stop to the end.
+        position_next = lookup_region_.width();
+      }
+      position_scale = 1.0f / std::max(position_next - position_prev, 1.0f);
+    }
+
+    // Write the blended color to the lookup texture buffer.
+    float blend_ratio = (texel - position_prev) * position_scale;
+    render_tree::ColorRGBA color = color_prev * (1.0f - blend_ratio) +
+                                   color_next * blend_ratio;
+    pixel[0] = color.rgb8_r();
+    pixel[1] = color.rgb8_g();
+    pixel[2] = color.rgb8_b();
+    pixel[3] = color.rgb8_a();
+    pixel += 4;
+  }
+
+  // Update the lookup texture.
+  DCHECK_EQ(lookup_texture_->GetFormat(), GL_RGBA);
+  GL_CALL(glBindTexture(lookup_texture_->GetTarget(),
+                        lookup_texture_->gl_handle()));
+  GL_CALL(glTexSubImage2D(lookup_texture_->GetTarget(), 0,
+                          static_cast<GLint>(lookup_region_.x()),
+                          static_cast<GLint>(lookup_region_.y()),
+                          static_cast<GLsizei>(lookup_region_.width()), 1,
+                          lookup_texture_->GetFormat(), GL_UNSIGNED_BYTE,
+                          lookup_buffer));
+  GL_CALL(glBindTexture(lookup_texture_->GetTarget(), 0));
+
+  delete[] lookup_buffer;
+}
+
+void DrawRectRadialGradient::AddVertex(float x, float y,
+    const math::PointF& offset_center, const math::PointF& offset_scale) {
+  VertexAttributes attributes = {
+    { x, y },
+    { (x - offset_center.x()) * offset_scale.x(),
+      (y - offset_center.y()) * offset_scale.y() },
+  };
+  attributes_.push_back(attributes);
+}
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.h
new file mode 100644
index 0000000..e15fad1
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.h
@@ -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.
+
+#ifndef COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_RADIAL_GRADIENT_H_
+#define COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_RADIAL_GRADIENT_H_
+
+#include <vector>
+
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/brush.h"
+#include "cobalt/renderer/backend/egl/texture.h"
+#include "cobalt/renderer/rasterizer/egl/draw_object.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace egl {
+
+// Handles drawing a rectangle with a radial color gradient.
+class DrawRectRadialGradient : public DrawObject {
+ public:
+  DrawRectRadialGradient(GraphicsState* graphics_state,
+                         const BaseState& base_state,
+                         const math::RectF& rect,
+                         const render_tree::RadialGradientBrush& brush,
+                         const GetScratchTextureFunction& get_scratch_texture);
+
+  void ExecuteUpdateVertexBuffer(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  void ExecuteRasterize(GraphicsState* graphics_state,
+      ShaderProgramManager* program_manager) OVERRIDE;
+  base::TypeId GetTypeId() const OVERRIDE;
+
+  bool IsValid() const { return lookup_texture_ != nullptr; }
+
+ private:
+  struct VertexAttributes {
+    float position[2];
+    float offset[2];
+  };
+
+  void InitializeLookupTexture(const render_tree::RadialGradientBrush& brush);
+  void AddVertex(float x, float y, const math::PointF& offset_center,
+      const math::PointF& offset_scale);
+
+  std::vector<VertexAttributes> attributes_;
+  const backend::TextureEGL* lookup_texture_;
+  math::RectF lookup_region_;
+  uint8_t* vertex_buffer_;
+};
+
+}  // namespace egl
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_EGL_DRAW_RECT_RADIAL_GRADIENT_H_
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 eefc2fe..13401c9 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
@@ -128,7 +128,7 @@
     offset_center_ = spread_rect_.CenterPoint();
   }
 
-  color_ = GetGLRGBA(color * base_state_.opacity);
+  color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
 }
 
 void DrawRectShadowBlur::ExecuteRasterize(
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 56b7427..9dba0d3 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.cc
@@ -43,7 +43,7 @@
       offset_scale_(1.0f),
       vertex_buffer_(nullptr),
       vertex_count_(0) {
-  color_ = GetGLRGBA(color * base_state_.opacity);
+  color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
   if (inner_corners_ || outer_corners_) {
     // If using rounded corners, then both inner and outer rects must have
     // rounded corner definitions.
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
index f63e1b5..f12e7ed 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color.cc
@@ -51,7 +51,7 @@
       rect_(rect),
       corners_(corners),
       vertex_buffer_(NULL) {
-  color_ = GetGLRGBA(color * base_state_.opacity);
+  color_ = GetGLRGBA(GetDrawColor(color) * base_state_.opacity);
   graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
 }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
index bad11de..7ec422b 100644
--- a/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
+++ b/src/cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.cc
@@ -58,7 +58,7 @@
       clamp_texcoords_(clamp_texcoords),
       tile_texture_(false) {
   DCHECK(base_state_.rounded_scissor_corners);
-  color_ = color * base_state_.opacity;
+  color_ = GetDrawColor(color) * base_state_.opacity;
   graphics_state->ReserveVertexData(kVertexCount * sizeof(VertexAttributes));
 }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
index 7775f65..314b5a2 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
@@ -89,32 +89,42 @@
 
 void OffscreenTargetManager::Update(const math::Size& frame_size) {
   if (offscreen_atlases_.empty()) {
+    DCHECK(offscreen_atlases_1d_.empty());
     InitializeTargets(frame_size);
   }
 
+  SelectAtlasCache(&offscreen_atlases_, &offscreen_cache_);
+  SelectAtlasCache(&offscreen_atlases_1d_, &offscreen_cache_1d_);
+}
+
+void OffscreenTargetManager::SelectAtlasCache(
+    ScopedVector<OffscreenAtlas>* atlases_ptr,
+    scoped_ptr<OffscreenAtlas>* cache_ptr) {
+  ScopedVector<OffscreenAtlas>& atlases = *atlases_ptr;
+  scoped_ptr<OffscreenAtlas>& cache = *cache_ptr;
+
   // If any of the current atlases have more allocations used than the
   // current cache, then use that as the new cache.
   size_t most_used_atlas_index = 0;
-  for (size_t index = 1; index < offscreen_atlases_.size(); ++index) {
-    if (offscreen_atlases_[most_used_atlas_index]->allocations_used <
-        offscreen_atlases_[index]->allocations_used) {
+  for (size_t index = 1; index < atlases.size(); ++index) {
+    if (atlases[most_used_atlas_index]->allocations_used <
+        atlases[index]->allocations_used) {
       most_used_atlas_index = index;
     }
   }
 
-  OffscreenAtlas* most_used_atlas = offscreen_atlases_[most_used_atlas_index];
-  if (offscreen_cache_->allocations_used < most_used_atlas->allocations_used) {
-    OffscreenAtlas* new_atlas = offscreen_cache_.release();
-    offscreen_cache_.reset(offscreen_atlases_[most_used_atlas_index]);
-    offscreen_atlases_.weak_erase(offscreen_atlases_.begin() +
-                                  most_used_atlas_index);
-    offscreen_atlases_.push_back(new_atlas);
+  OffscreenAtlas* most_used_atlas = atlases[most_used_atlas_index];
+  if (cache->allocations_used < most_used_atlas->allocations_used) {
+    OffscreenAtlas* new_atlas = cache.release();
+    cache.reset(atlases[most_used_atlas_index]);
+    atlases.weak_erase(atlases.begin() + most_used_atlas_index);
+    atlases.push_back(new_atlas);
   }
-  offscreen_cache_->allocations_used = 0;
+  cache->allocations_used = 0;
 
   // Reset all current atlases for use this frame.
-  for (size_t index = 0; index < offscreen_atlases_.size(); ++index) {
-    OffscreenAtlas* atlas = offscreen_atlases_[index];
+  for (size_t index = 0; index < atlases.size(); ++index) {
+    OffscreenAtlas* atlas = atlases[index];
     atlas->allocator.Reset();
     atlas->allocation_map.clear();
     atlas->allocations_used = 0;
@@ -162,6 +172,34 @@
   return false;
 }
 
+bool OffscreenTargetManager::GetCachedOffscreenTarget(
+    const render_tree::Node* node, const CacheErrorFunction1D& error_function,
+    TargetInfo* out_target_info) {
+  // Find the cache of the given node (if any) with the lowest error.
+  AllocationMap::iterator best_iter = offscreen_cache_1d_->allocation_map.end();
+  float best_error = 2.0f;
+
+  auto range = offscreen_cache_1d_->allocation_map.equal_range(node->GetId());
+  for (auto iter = range.first; iter != range.second; ++iter) {
+    float error = error_function.Run(iter->second.error_data.width());
+    if (best_error > error) {
+      best_error = error;
+      best_iter = iter;
+    }
+  }
+
+  // A cache entry matches the caller's criteria only if error < 1.
+  if (best_error < 1.0f) {
+    offscreen_cache_1d_->allocations_used += 1;
+    out_target_info->framebuffer = offscreen_cache_1d_->framebuffer.get();
+    out_target_info->skia_canvas = nullptr;
+    out_target_info->region = best_iter->second.target_region;
+    return true;
+  }
+
+  return false;
+}
+
 void OffscreenTargetManager::AllocateOffscreenTarget(
     const render_tree::Node* node, const math::SizeF& size,
     const ErrorData& error_data, TargetInfo* out_target_info) {
@@ -227,6 +265,51 @@
   }
 }
 
+void OffscreenTargetManager::AllocateOffscreenTarget(
+    const render_tree::Node* node, float size,
+    const ErrorData1D& error_data, TargetInfo* out_target_info) {
+  // 1D targets do not use any padding to avoid interpolation with neighboring
+  // allocations in the atlas.
+  math::Size target_size(static_cast<int>(std::ceil(size)), 1);
+  math::RectF target_rect(0.0f, 0.0f, 0.0f, 0.0f);
+  OffscreenAtlas* atlas = NULL;
+
+  // See if there's room in the offscreen cache for additional targets.
+  atlas = offscreen_cache_1d_.get();
+  target_rect = atlas->allocator.Allocate(target_size);
+
+  if (target_rect.IsEmpty()) {
+    // See if there's room in the other atlases.
+    for (size_t index = offscreen_atlases_1d_.size(); index > 0;) {
+      atlas = offscreen_atlases_1d_[--index];
+      target_rect = atlas->allocator.Allocate(target_size);
+      if (!target_rect.IsEmpty()) {
+        break;
+      }
+    }
+  }
+
+  if (target_rect.IsEmpty()) {
+    // 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 {
+    DCHECK_LE(size, target_rect.width());
+    target_rect.set_width(size);
+
+    atlas->allocation_map.insert(AllocationMap::value_type(
+        node->GetId(), AllocationMapValue(
+            ErrorData(error_data, 1), target_rect)));
+    atlas->allocations_used += 1;
+    atlas->needs_flush = false;
+
+    out_target_info->framebuffer = atlas->framebuffer.get();
+    out_target_info->skia_canvas = nullptr;
+    out_target_info->region = target_rect;
+  }
+}
+
 void OffscreenTargetManager::InitializeTargets(const math::Size& frame_size) {
   DLOG(INFO) << "offscreen render target memory limit: " << memory_limit_;
 
@@ -284,25 +367,41 @@
     DLOG(WARNING) << "More memory was allotted for offscreen render targets"
                   << " than will be used.";
   }
-  offscreen_cache_.reset(CreateOffscreenAtlas(atlas_size));
+  offscreen_cache_.reset(CreateOffscreenAtlas(atlas_size, true));
   for (int i = 1; i < num_atlases; ++i) {
-    offscreen_atlases_.push_back(CreateOffscreenAtlas(atlas_size));
+    offscreen_atlases_.push_back(CreateOffscreenAtlas(atlas_size, true));
   }
 
   DLOG(INFO) << "Created " << num_atlases << " offscreen atlases of size "
              << atlas_size.width() << " x " << atlas_size.height();
+
+  // Create 1D texture atlases. These are just regular 2D textures that will
+  // be used as 1D row textures. These atlases are not intended to be used by
+  // skia.
+  const int kAtlasHeight1D =
+      std::min(std::min(16, frame_size.width()), frame_size.height());
+  math::Size atlas_size_1d(
+      std::max(frame_size.width(), frame_size.height()), kAtlasHeight1D);
+  offscreen_atlases_1d_.push_back(CreateOffscreenAtlas(atlas_size_1d, false));
+  offscreen_cache_1d_.reset(CreateOffscreenAtlas(atlas_size_1d, false));
+  DLOG(INFO) << "Created " << offscreen_atlases_1d_.size() + 1
+             << " offscreen atlases of size " << atlas_size_1d.width() << " x "
+             << atlas_size_1d.height();
 }
 
 OffscreenTargetManager::OffscreenAtlas*
-    OffscreenTargetManager::CreateOffscreenAtlas(const math::Size& size) {
+    OffscreenTargetManager::CreateOffscreenAtlas(const math::Size& size,
+        bool create_canvas) {
   OffscreenAtlas* atlas = new OffscreenAtlas(size);
 
   // Create a new framebuffer.
   atlas->framebuffer = new backend::FramebufferRenderTargetEGL(
       graphics_context_, size);
 
-  // Wrap the framebuffer as a skia surface.
-  atlas->skia_surface.reset(create_fallback_surface_.Run(atlas->framebuffer));
+  if (create_canvas) {
+    // Wrap the framebuffer as a skia surface.
+    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 3dc7803..d851096 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
@@ -55,6 +55,8 @@
   // this criteria, then the entry with the lowest error is chosen.
   typedef math::RectF ErrorData;
   typedef base::Callback<float(const ErrorData&)> CacheErrorFunction;
+  typedef float ErrorData1D;
+  typedef base::Callback<float(const ErrorData1D&)> CacheErrorFunction1D;
 
   OffscreenTargetManager(backend::GraphicsContextEGL* graphics_context,
       const CreateFallbackSurfaceFunction& create_fallback_surface,
@@ -72,22 +74,28 @@
   // available. If a cache does exist, then the output parameters are set,
   // otherwise, they are untouched.
   // The returned values are only valid until the next call to Update().
-  bool GetCachedOffscreenTarget(
-      const render_tree::Node* node, const CacheErrorFunction& error_function,
-      TargetInfo* out_target_info);
+  bool GetCachedOffscreenTarget(const render_tree::Node* node,
+      const CacheErrorFunction& error_function, TargetInfo* out_target_info);
+  bool GetCachedOffscreenTarget(const render_tree::Node* node,
+      const CacheErrorFunction1D& error_function, TargetInfo* out_target_info);
 
   // Allocate an offscreen target of the specified size.
   // The returned values are only valid until the next call to Update().
-  void AllocateOffscreenTarget(
-      const render_tree::Node* node, const math::SizeF& size,
-      const ErrorData& error_data, TargetInfo* out_target_info);
+  void AllocateOffscreenTarget(const render_tree::Node* node,
+      const math::SizeF& size, const ErrorData& error_data,
+      TargetInfo* out_target_info);
+  void AllocateOffscreenTarget(const render_tree::Node* node,
+      float size, const ErrorData1D& error_data, TargetInfo* out_target_info);
 
  private:
   // Use an atlas for offscreen targets.
   struct OffscreenAtlas;
 
   void InitializeTargets(const math::Size& frame_size);
-  OffscreenAtlas* CreateOffscreenAtlas(const math::Size& size);
+  OffscreenAtlas* CreateOffscreenAtlas(const math::Size& size,
+      bool create_canvas);
+  void SelectAtlasCache(ScopedVector<OffscreenAtlas>* atlases,
+      scoped_ptr<OffscreenAtlas>* cache);
 
   backend::GraphicsContextEGL* graphics_context_;
   CreateFallbackSurfaceFunction create_fallback_surface_;
@@ -95,6 +103,9 @@
   ScopedVector<OffscreenAtlas> offscreen_atlases_;
   scoped_ptr<OffscreenAtlas> offscreen_cache_;
 
+  ScopedVector<OffscreenAtlas> offscreen_atlases_1d_;
+  scoped_ptr<OffscreenAtlas> offscreen_cache_1d_;
+
   // 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
   // mask can be used for the alignment calculation.
diff --git a/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp b/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
index 9fc7942..ac16299 100644
--- a/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/egl/rasterizer.gyp
@@ -47,10 +47,14 @@
         'draw_object_manager.cc',
         'draw_poly_color.h',
         'draw_poly_color.cc',
+        'draw_rect_border.h',
+        'draw_rect_border.cc',
         'draw_rect_color_texture.h',
         'draw_rect_color_texture.cc',
         'draw_rect_linear_gradient.h',
         'draw_rect_linear_gradient.cc',
+        'draw_rect_radial_gradient.h',
+        'draw_rect_radial_gradient.cc',
         'draw_rect_shadow_spread.h',
         'draw_rect_shadow_spread.cc',
         'draw_rect_shadow_blur.h',
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 c065daf..72b7da7 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -29,8 +29,10 @@
 #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_border.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h"
+#include "cobalt/renderer/rasterizer/egl/draw_rect_radial_gradient.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h"
 #include "cobalt/renderer/rasterizer/egl/draw_rect_texture.h"
@@ -49,6 +51,10 @@
 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);
 
+bool IsOpaque(float opacity) {
+  return opacity >= 0.999f;
+}
+
 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);
@@ -139,6 +145,10 @@
   return (error_x + error_y) * 0.49f;
 }
 
+float OffscreenTargetErrorFunction1D(float desired, const float& cached) {
+  return std::abs(cached - desired);
+}
+
 }  // namespace
 
 RenderTreeNodeVisitor::RenderTreeNodeVisitor(GraphicsState* graphics_state,
@@ -287,11 +297,14 @@
 
         // The content rect is already in screen space, so reset the transform.
         math::Matrix3F old_transform = draw_state_.transform;
+        float old_opacity = draw_state_.opacity;
         draw_state_.transform = math::Matrix3F::Identity();
+        draw_state_.opacity *= filter_opacity;
         scoped_ptr<DrawObject> draw(new DrawRectColorTexture(graphics_state_,
-            draw_state_, content_rect, kOpaqueWhite * filter_opacity, texture,
+            draw_state_, content_rect, kOpaqueWhite, texture,
             texcoord_transform, false /* clamp_texcoords */));
         AddTransparentDraw(draw.Pass(), content_rect);
+        draw_state_.opacity = old_opacity;
         draw_state_.transform = old_transform;
         return;
       }
@@ -344,7 +357,7 @@
   skia::Image* skia_image =
       base::polymorphic_downcast<skia::Image*>(data.source.get());
   bool clamp_texcoords = false;
-  bool is_opaque = skia_image->IsOpaque() && draw_state_.opacity == 1.0f;
+  bool is_opaque = skia_image->IsOpaque() && IsOpaque(draw_state_.opacity);
 
   // Ensure any required backend processing is done to create the necessary
   // GPU resource.
@@ -439,74 +452,101 @@
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) {
-  if (!IsVisible(rect_node->GetBounds())) {
+  math::RectF node_bounds(rect_node->GetBounds());
+  if (!IsVisible(node_bounds)) {
     return;
   }
 
   const render_tree::RectNode::Builder& data = rect_node->data();
   const scoped_ptr<render_tree::Brush>& brush = data.background_brush;
+  math::RectF content_rect(data.rect);
 
-  // Only solid color brushes are supported at this time.
-  const bool brush_supported = !brush ||
-      brush->GetTypeId() == base::GetTypeId<render_tree::SolidColorBrush>() ||
-      brush->GetTypeId() == base::GetTypeId<render_tree::LinearGradientBrush>();
-
-  // Borders are not supported natively by this rasterizer at this time. The
-  // difficulty lies in getting anti-aliased borders and minimizing state
-  // switches (due to anti-aliased borders requiring transparency). However,
-  // by using the fallback rasterizer, both can be accomplished -- sort to
-  // minimize state switches while rendering anti-aliased borders to the
-  // offscreen target, then use a single shader to render those.
-  const bool border_supported = !data.border;
-
+  // Only solid color brushes are natively supported with rounded corners.
   if (data.rounded_corners && brush &&
       brush->GetTypeId() != base::GetTypeId<render_tree::SolidColorBrush>()) {
     FallbackRasterize(rect_node);
-  } else if (!brush_supported) {
-    FallbackRasterize(rect_node);
-  } else if (!border_supported) {
-    FallbackRasterize(rect_node);
-  } else {
-    DCHECK(!data.border);
+    return;
+  }
 
-    // Handle drawing the content.
-    if (brush) {
-      base::TypeId brush_type = brush->GetTypeId();
-      if (brush_type == base::GetTypeId<render_tree::SolidColorBrush>()) {
-        const render_tree::SolidColorBrush* solid_brush =
-            base::polymorphic_downcast<const render_tree::SolidColorBrush*>
-                (brush.get());
-        const render_tree::ColorRGBA& brush_color(solid_brush->color());
-        render_tree::ColorRGBA color(
-            brush_color.r() * brush_color.a(),
-            brush_color.g() * brush_color.a(),
-            brush_color.b() * brush_color.a(),
-            brush_color.a());
-        if (data.rounded_corners) {
-          scoped_ptr<DrawObject> draw(new DrawRRectColor(graphics_state_,
-              draw_state_, data.rect, *data.rounded_corners, color));
-          // Transparency is used for anti-aliasing.
-          AddTransparentDraw(draw.Pass(), rect_node->GetBounds());
-        } else {
-          scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
-              draw_state_, data.rect, color));
-          if (draw_state_.opacity * color.a() == 1.0f) {
-            AddOpaqueDraw(draw.Pass(), rect_node->GetBounds());
-          } else {
-            AddTransparentDraw(draw.Pass(), rect_node->GetBounds());
-          }
-        }
+  // Determine whether the RectNode's border attribute is supported. Update
+  // the content and bounds if so.
+  scoped_ptr<DrawRectBorder> draw_border;
+  if (data.border) {
+    draw_border.reset(new DrawRectBorder(graphics_state_, draw_state_,
+        rect_node));
+    if (draw_border->IsValid()) {
+      content_rect = draw_border->GetContentRect();
+      node_bounds = draw_border->GetBounds();
+    }
+  }
+  const bool border_supported = !data.border || draw_border->IsValid();
+
+  // Determine whether the RectNode's background brush is supported.
+  base::TypeId brush_type = brush ? brush->GetTypeId() :
+      base::GetTypeId<render_tree::Brush>();
+  bool brush_is_solid_and_supported =
+      brush_type == base::GetTypeId<render_tree::SolidColorBrush>();
+  bool brush_is_linear_and_supported =
+      brush_type == base::GetTypeId<render_tree::LinearGradientBrush>();
+  bool brush_is_radial_and_supported =
+      brush_type == base::GetTypeId<render_tree::RadialGradientBrush>();
+
+  scoped_ptr<DrawRectRadialGradient> draw_radial;
+  if (brush_is_radial_and_supported) {
+    const render_tree::RadialGradientBrush* radial_brush =
+        base::polymorphic_downcast<const render_tree::RadialGradientBrush*>
+            (brush.get());
+    draw_radial.reset(new DrawRectRadialGradient(graphics_state_, draw_state_,
+        content_rect, *radial_brush,
+        base::Bind(&RenderTreeNodeVisitor::GetScratchTexture,
+                   base::Unretained(this), make_scoped_refptr(rect_node))));
+    brush_is_radial_and_supported = draw_radial->IsValid();
+  }
+
+  const bool brush_supported = !brush || brush_is_solid_and_supported ||
+      brush_is_linear_and_supported || brush_is_radial_and_supported;
+
+  if (!brush_supported || !border_supported) {
+    FallbackRasterize(rect_node);
+    return;
+  }
+
+  if (draw_border) {
+    AddTransparentDraw(draw_border.PassAs<DrawObject>(), node_bounds);
+  }
+
+  // Handle drawing the content.
+  if (brush_is_solid_and_supported) {
+    const render_tree::SolidColorBrush* solid_brush =
+        base::polymorphic_downcast<const render_tree::SolidColorBrush*>
+            (brush.get());
+    if (data.rounded_corners) {
+      scoped_ptr<DrawObject> draw(new DrawRRectColor(graphics_state_,
+          draw_state_, content_rect, *data.rounded_corners,
+          solid_brush->color()));
+      // Transparency is used for anti-aliasing.
+      AddTransparentDraw(draw.Pass(), node_bounds);
+    } else {
+      scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
+          draw_state_, content_rect, solid_brush->color()));
+      if (IsOpaque(draw_state_.opacity * solid_brush->color().a())) {
+        AddOpaqueDraw(draw.Pass(), node_bounds);
       } else {
-        const render_tree::LinearGradientBrush* linear_brush =
-            base::polymorphic_downcast<const render_tree::LinearGradientBrush*>
-                (brush.get());
-        scoped_ptr<DrawObject> draw(new DrawRectLinearGradient(graphics_state_,
-            draw_state_, data.rect, *linear_brush));
-        // The draw may use transparent pixels to ensure only pixels in the
-        // specified area are modified.
-        AddTransparentDraw(draw.Pass(), rect_node->GetBounds());
+        AddTransparentDraw(draw.Pass(), node_bounds);
       }
     }
+  } else if (brush_is_linear_and_supported) {
+    const render_tree::LinearGradientBrush* linear_brush =
+        base::polymorphic_downcast<const render_tree::LinearGradientBrush*>
+            (brush.get());
+    scoped_ptr<DrawObject> draw(new DrawRectLinearGradient(graphics_state_,
+        draw_state_, content_rect, *linear_brush));
+    // The draw may use transparent pixels to ensure only pixels in the
+    // specified area are modified.
+    AddTransparentDraw(draw.Pass(), node_bounds);
+  } else if (brush_is_radial_and_supported) {
+    // The colors in the brush may be transparent.
+    AddTransparentDraw(draw_radial.PassAs<DrawObject>(), node_bounds);
   }
 }
 
@@ -521,11 +561,7 @@
       data.rounded_corners;
 
   scoped_ptr<DrawObject> draw;
-  render_tree::ColorRGBA shadow_color(
-      data.shadow.color.r() * data.shadow.color.a(),
-      data.shadow.color.g() * data.shadow.color.a(),
-      data.shadow.color.b() * data.shadow.color.a(),
-      data.shadow.color.a());
+  render_tree::ColorRGBA shadow_color(data.shadow.color);
 
   math::RectF spread_rect(data.rect);
   spread_rect.Offset(data.shadow.offset);
@@ -577,6 +613,25 @@
   FallbackRasterize(text_node);
 }
 
+// Get a scratch texture row region for use in rendering |node|.
+void RenderTreeNodeVisitor::GetScratchTexture(
+    scoped_refptr<render_tree::Node> node, float size,
+    DrawObject::TextureInfo* out_texture_info) {
+  // Get the cached texture region or create one.
+  OffscreenTargetManager::TargetInfo target_info;
+  bool cached = offscreen_target_manager_->GetCachedOffscreenTarget(node,
+      base::Bind(&OffscreenTargetErrorFunction1D, size), &target_info);
+  if (!cached) {
+    offscreen_target_manager_->AllocateOffscreenTarget(node, size, size,
+        &target_info);
+  }
+
+  out_texture_info->texture = target_info.framebuffer == nullptr ? nullptr :
+      target_info.framebuffer->GetColorTexture();
+  out_texture_info->region = target_info.region;
+  out_texture_info->is_new = !cached;
+}
+
 // 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.
@@ -689,7 +744,7 @@
   // 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) {
+  if (IsOpaque(draw_state_.opacity)) {
     scoped_ptr<DrawObject> draw(new DrawRectTexture(graphics_state_,
         draw_state_, content_rect, texture, texcoord_transform));
     AddTransparentDraw(draw.Pass(), content_rect);
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 51044f8..81aaa9b 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h
@@ -81,6 +81,8 @@
   void Visit(render_tree::TextNode* text_node) OVERRIDE;
 
  private:
+  void GetScratchTexture(scoped_refptr<render_tree::Node> node, float size,
+                         DrawObject::TextureInfo* out_texture_info);
   void GetOffscreenTarget(scoped_refptr<render_tree::Node> node,
                           bool* out_content_cached,
                           OffscreenTargetManager::TargetInfo* out_target_info,
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_base.h b/src/cobalt/renderer/rasterizer/egl/shader_base.h
index 55dd757..b4dbf10 100644
--- a/src/cobalt/renderer/rasterizer/egl/shader_base.h
+++ b/src/cobalt/renderer/rasterizer/egl/shader_base.h
@@ -29,10 +29,8 @@
 // be used as part of the ShaderProgram template class.
 class ShaderBase {
  public:
-  ShaderBase() : handle_(0) {}
   virtual ~ShaderBase() {}
 
-  GLuint GetHandle() const { return handle_; }
   virtual const char* GetSource() const = 0;
 
  protected:
@@ -47,8 +45,6 @@
   virtual void InitializePreLink(GLuint program) = 0;
   virtual void InitializePostLink(GLuint program) = 0;
   virtual void InitializePostUse() = 0;
-
-  GLuint handle_;
 };
 
 }  // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/egl/shader_program.cc b/src/cobalt/renderer/rasterizer/egl/shader_program.cc
index 24dd2f7..88bb7aa 100644
--- a/src/cobalt/renderer/rasterizer/egl/shader_program.cc
+++ b/src/cobalt/renderer/rasterizer/egl/shader_program.cc
@@ -31,17 +31,15 @@
   GL_CALL(glShaderSource(handle, 1, &source, &source_length));
   GL_CALL(glCompileShader(handle));
 
-  GLint is_compiled = GL_FALSE;
-  GL_CALL(glGetShaderiv(handle, GL_COMPILE_STATUS, &is_compiled));
-  if (is_compiled != GL_TRUE) {
-    const GLsizei kMaxLogLength = 2048;
-    GLsizei log_length = 0;
-    GLchar log[kMaxLogLength];
-    glGetShaderInfoLog(handle, kMaxLogLength, &log_length, log);
-    DLOG(ERROR) << "shader error: " << log;
+  GLint compiled = GL_FALSE;
+  GL_CALL(glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled));
+  if (compiled != GL_TRUE) {
+    GLchar log[2048] = { 0 };
+    glGetShaderInfoLog(handle, arraysize(log) - 1, NULL, log);
+    DLOG(ERROR) << "shader compile error:\n" << log;
     DLOG(ERROR) << "shader source:\n" << source;
   }
-  DCHECK_EQ(is_compiled, GL_TRUE);
+  DCHECK(compiled == GL_TRUE);
 }
 
 }  // namespace
@@ -57,30 +55,43 @@
                                ShaderBase* fragment_shader) {
   handle_ = glCreateProgram();
 
-  vertex_shader->handle_ = glCreateShader(GL_VERTEX_SHADER);
-  CompileShader(vertex_shader->handle_, vertex_shader->GetSource());
-  GL_CALL(glAttachShader(handle_, vertex_shader->handle_));
+  GLuint vertex_shader_handle = glCreateShader(GL_VERTEX_SHADER);
+  CompileShader(vertex_shader_handle, vertex_shader->GetSource());
+  GL_CALL(glAttachShader(handle_, vertex_shader_handle));
 
-  fragment_shader->handle_ = glCreateShader(GL_FRAGMENT_SHADER);
-  CompileShader(fragment_shader->handle_, fragment_shader->GetSource());
-  GL_CALL(glAttachShader(handle_, fragment_shader->handle_));
+  GLuint fragment_shader_handle = glCreateShader(GL_FRAGMENT_SHADER);
+  CompileShader(fragment_shader_handle, fragment_shader->GetSource());
+  GL_CALL(glAttachShader(handle_, fragment_shader_handle));
 
   vertex_shader->InitializePreLink(handle_);
   fragment_shader->InitializePreLink(handle_);
   GL_CALL(glLinkProgram(handle_));
+
+  GLint linked = GL_FALSE;
+  GL_CALL(glGetProgramiv(handle_, GL_LINK_STATUS, &linked));
+  if (linked != GL_TRUE) {
+    GLchar log[2048] = { 0 };
+    glGetProgramInfoLog(handle_, arraysize(log) - 1, NULL, log);
+    DLOG(ERROR) << "shader link error:\n" << log;
+    DLOG(ERROR) << "vertex source:\n" << vertex_shader->GetSource();
+    DLOG(ERROR) << "fragment source:\n" << fragment_shader->GetSource();
+  }
+  DCHECK(linked == GL_TRUE);
+
   vertex_shader->InitializePostLink(handle_);
   fragment_shader->InitializePostLink(handle_);
   GL_CALL(glUseProgram(handle_));
   vertex_shader->InitializePostUse();
   fragment_shader->InitializePostUse();
+
+  GL_CALL(glDeleteShader(vertex_shader_handle));
+  GL_CALL(glDeleteShader(fragment_shader_handle));
 }
 
 void ShaderProgramBase::Destroy(ShaderBase* vertex_shader,
                                 ShaderBase* fragment_shader) {
   DCHECK_NE(handle_, 0);
   GL_CALL(glDeleteProgram(handle_));
-  GL_CALL(glDeleteShader(vertex_shader->handle_));
-  GL_CALL(glDeleteShader(fragment_shader->handle_));
   handle_ = 0;
 }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_opacity_texcoord1d.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_opacity_texcoord1d.glsl
new file mode 100644
index 0000000..d600dc4
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_opacity_texcoord1d.glsl
@@ -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.

+

+precision mediump float;

+uniform sampler2D u_texture;

+uniform vec4 u_texcoord_transform;  // (u-scale, u-add, u-max, v-center)

+uniform float u_opacity;

+varying vec2 v_offset;

+

+void main() {

+  vec2 texcoord = vec2(

+      min(length(v_offset) * u_texcoord_transform.x + u_texcoord_transform.y,

+          u_texcoord_transform.z),

+      u_texcoord_transform.w);

+  gl_FragColor = texture2D(u_texture, texcoord) * u_opacity;

+}

diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
index 82e71d6..9d00899 100644
--- a/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/shaders.gyp
@@ -26,6 +26,7 @@
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_include.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_rrect.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_color_texcoord.glsl',
+      '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_opacity_texcoord1d.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/fragment_texcoord_color_rrect.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/function_gaussian_integral.inc',
@@ -33,6 +34,7 @@
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_offset.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_color_texcoord.glsl',
+      '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_offset.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_texcoord.glsl',
       '<(DEPTH)/cobalt/renderer/rasterizer/egl/shaders/vertex_texcoord.glsl',
     ],
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset.glsl
new file mode 100644
index 0000000..597acff
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset.glsl
@@ -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.

+

+uniform vec4 u_clip_adjustment;

+uniform mat3 u_view_matrix;

+attribute vec2 a_position;

+attribute vec2 a_offset;

+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_offset = a_offset;

+}

diff --git a/src/cobalt/storage/savegame.cc b/src/cobalt/storage/savegame.cc
index 906a367..8f8f027 100644
--- a/src/cobalt/storage/savegame.cc
+++ b/src/cobalt/storage/savegame.cc
@@ -20,6 +20,10 @@
 namespace cobalt {
 namespace storage {
 
+// An arbitrary max size for |last_bytes_| to prevent it from growing larger
+// than desired.
+size_t kMaxLastBytesSize = 128 * 1024;
+
 Savegame::Savegame(const Options& options) : options_(options) {}
 
 Savegame::~Savegame() {}
@@ -34,7 +38,21 @@
 bool Savegame::Write(const std::vector<uint8>& bytes) {
   DCHECK(thread_checker_.CalledOnValidThread());
   TRACE_EVENT0("cobalt::storage", "Savegame::Write()");
+  if (bytes == last_bytes_) {
+    return true;
+  }
+
   bool ret = PlatformWrite(bytes);
+
+  if (bytes.size() <= kMaxLastBytesSize) {
+    last_bytes_ = bytes;
+  } else {
+    DLOG(WARNING) << "Unable to cache last savegame, which is used to prevent "
+                     "duplicate saves, because it is too large: "
+                  << bytes.size() << " bytes";
+    last_bytes_.clear();
+  }
+
   return ret;
 }
 
diff --git a/src/cobalt/storage/savegame.h b/src/cobalt/storage/savegame.h
index fa7e88f..d3036ba 100644
--- a/src/cobalt/storage/savegame.h
+++ b/src/cobalt/storage/savegame.h
@@ -89,6 +89,9 @@
  private:
   base::ThreadChecker thread_checker_;
 
+  // Caching of last written bytes, which is used to prevent duplicate writes.
+  ByteVector last_bytes_;
+
   DISALLOW_COPY_AND_ASSIGN(Savegame);
 };
 
diff --git a/src/cobalt/storage/storage_manager.cc b/src/cobalt/storage/storage_manager.cc
index ac4d7a2..99e0414 100644
--- a/src/cobalt/storage/storage_manager.cc
+++ b/src/cobalt/storage/storage_manager.cc
@@ -32,9 +32,10 @@
 
 namespace {
 
-// Flush() delays for a while to avoid spamming writes to disk.
-// We often get a bunch of Flush() calls in a row.
-const int kDatabaseFlushDelayMs = 100;
+// FlushOnChange() delays for a while to avoid spamming writes to disk; we often
+// get several FlushOnChange() calls in a row.
+const int kDatabaseFlushOnLastChangeDelayMs = 500;
+const int kDatabaseFlushOnChangeMaxDelayMs = 2000;
 
 const char kDefaultSaveFile[] = "cobalt_save.bin";
 
@@ -143,7 +144,7 @@
       loaded_database_version_(0),
       initialized_(false),
       flush_processing_(false),
-      flush_requested_(false),
+      flush_pending_(false),
       no_flushes_pending_(true /* manual reset */,
                           true /* initially signalled */) {
   DCHECK(upgrade_handler_);
@@ -152,6 +153,10 @@
   // Start the savegame load immediately.
   sql_thread_->Start();
   sql_message_loop_ = sql_thread_->message_loop_proxy();
+
+  flush_on_last_change_timer_.reset(new base::OneShotTimer<StorageManager>());
+  flush_on_change_max_delay_timer_.reset(
+      new base::OneShotTimer<StorageManager>());
 }
 
 StorageManager::~StorageManager() {
@@ -176,18 +181,31 @@
                               base::Bind(callback, sql_context_.get()));
 }
 
-void StorageManager::Flush() {
+void StorageManager::FlushOnChange() {
   TRACE_EVENT0("cobalt::storage", __FUNCTION__);
   // Make sure this runs on the correct thread.
   if (MessageLoop::current()->message_loop_proxy() != sql_message_loop_) {
     sql_message_loop_->PostTask(
-        FROM_HERE, base::Bind(&StorageManager::Flush, base::Unretained(this)));
+        FROM_HERE,
+        base::Bind(&StorageManager::FlushOnChange, base::Unretained(this)));
     return;
   }
 
-  flush_timer_->Start(FROM_HERE,
-                      base::TimeDelta::FromMilliseconds(kDatabaseFlushDelayMs),
-                      this, &StorageManager::OnFlushTimerFired);
+  // Only start the timers if there isn't already a flush pending.
+  if (!flush_pending_) {
+    // The last change timer is always re-started on any change.
+    flush_on_last_change_timer_->Start(
+        FROM_HERE,
+        base::TimeDelta::FromMilliseconds(kDatabaseFlushOnLastChangeDelayMs),
+        this, &StorageManager::OnFlushOnChangeTimerFired);
+    // The max delay timer is never re-started after it starts running.
+    if (!flush_on_change_max_delay_timer_->IsRunning()) {
+      flush_on_change_max_delay_timer_->Start(
+          FROM_HERE,
+          base::TimeDelta::FromMilliseconds(kDatabaseFlushOnChangeMaxDelayMs),
+          this, &StorageManager::OnFlushOnChangeTimerFired);
+    }
+  }
 }
 
 void StorageManager::FlushNow(const base::Closure& callback) {
@@ -200,6 +218,7 @@
     return;
   }
 
+  StopFlushOnChangeTimers();
   QueueFlush(callback);
 }
 
@@ -249,7 +268,6 @@
 
   vfs_.reset(new VirtualFileSystem());
   sql_vfs_.reset(new SqlVfs("cobalt_vfs", vfs_.get()));
-  flush_timer_.reset(new base::OneShotTimer<StorageManager>());
   // Savegame has finished loading. Now initialize the database connection.
   // Check if this is upgrade data, if so, handle it, otherwise:
   // Check if the savegame data contains a VFS header.
@@ -316,10 +334,20 @@
   initialized_ = true;
 }
 
-void StorageManager::OnFlushTimerFired() {
+void StorageManager::StopFlushOnChangeTimers() {
+  if (flush_on_last_change_timer_->IsRunning()) {
+    flush_on_last_change_timer_->Stop();
+  }
+  if (flush_on_change_max_delay_timer_->IsRunning()) {
+    flush_on_change_max_delay_timer_->Stop();
+  }
+}
+
+void StorageManager::OnFlushOnChangeTimerFired() {
   TRACE_EVENT0("cobalt::storage", __FUNCTION__);
   DCHECK(sql_message_loop_->BelongsToCurrentThread());
 
+  StopFlushOnChangeTimers();
   QueueFlush(base::Closure());
 }
 
@@ -342,11 +370,11 @@
   }
   flush_processing_callbacks_.clear();
 
-  if (flush_requested_) {
+  if (flush_pending_) {
     // If another flush has been requested while we were processing the one that
     // just completed, start that next flush now.
-    flush_processing_callbacks_.swap(flush_requested_callbacks_);
-    flush_requested_ = false;
+    flush_processing_callbacks_.swap(flush_pending_callbacks_);
+    flush_pending_ = false;
     FlushInternal();
     DCHECK(flush_processing_);
   } else {
@@ -368,9 +396,9 @@
   } else {
     // Otherwise, indicate that we would like to re-flush as soon as the
     // current one completes.
-    flush_requested_ = true;
+    flush_pending_ = true;
     if (!callback.is_null()) {
-      flush_requested_callbacks_.push_back(callback);
+      flush_pending_callbacks_.push_back(callback);
     }
   }
 }
@@ -431,7 +459,8 @@
   savegame_thread_.reset();
 
   // Ensure these objects are destroyed on the proper thread.
-  flush_timer_.reset(NULL);
+  flush_on_last_change_timer_.reset(NULL);
+  flush_on_change_max_delay_timer_.reset(NULL);
   sql_vfs_.reset(NULL);
   vfs_.reset(NULL);
 }
diff --git a/src/cobalt/storage/storage_manager.h b/src/cobalt/storage/storage_manager.h
index 53cd425..26a990f 100644
--- a/src/cobalt/storage/storage_manager.h
+++ b/src/cobalt/storage/storage_manager.h
@@ -85,10 +85,10 @@
   void GetSqlContext(const SqlCallback& callback);
 
   // Schedule a write of our database to disk to happen at some point in the
-  // future. Multiple calls to Flush() do not necessarily result in multiple
-  // writes to disk.
+  // future after a change occurs. Multiple calls to Flush() do not necessarily
+  // result in multiple writes to disk.
   // This call returns immediately.
-  void Flush();
+  void FlushOnChange();
 
   // Triggers a write to disk to happen immediately.  Each call to FlushNow()
   // will result in a write to disk.
@@ -121,8 +121,11 @@
   // complete.
   void FinishInit();
 
+  // Stops any timers that are currently running.
+  void StopFlushOnChangeTimers();
+
   // Callback when flush timer has elapsed.
-  void OnFlushTimerFired();
+  void OnFlushOnChangeTimerFired();
 
   // Logic to be executed on the SQL thread when a flush completes.  Will
   // dispatch |flush_processing_callbacks_| callbacks and execute a new flush
@@ -171,10 +174,17 @@
   // until we can initialize the database on the correct thread.
   scoped_ptr<Savegame::ByteVector> loaded_raw_bytes_;
 
-  // Timer that starts running when Flush() is called. When the time elapses,
-  // we actually perform the write. This is a simple form of rate limiting
-  // for I/O writes.
-  scoped_ptr<base::OneShotTimer<StorageManager> > flush_timer_;
+  // Timers that start running when FlushOnChange() is called. When the time
+  // elapses, we actually perform the write. This is a simple form of rate
+  // limiting I/O writes.
+  // |flush_on_last_change_timer_| is re-started on each change, enabling
+  // changes to collect if several happen within a short period of time.
+  // |flush_on_change_max_delay_timer_| starts on the first change and is never
+  // re-started, ensuring that the flush always occurs within its delay and
+  // cannot be pushed back indefinitely.
+  scoped_ptr<base::OneShotTimer<StorageManager> > flush_on_last_change_timer_;
+  scoped_ptr<base::OneShotTimer<StorageManager> >
+      flush_on_change_max_delay_timer_;
 
   // See comments for for kDatabaseUserVersion.
   int loaded_database_version_;
@@ -193,12 +203,12 @@
 
   // True if |flush_processing_| is true, but we would like to perform a new
   // flush as soon as it completes.
-  bool flush_requested_;
+  bool flush_pending_;
 
   // The queue of callbacks that will be called when the flush that follows
-  // the current flush completes.  If this is non-empty, then |flush_requested_|
+  // the current flush completes.  If this is non-empty, then |flush_pending_|
   // must be true.
-  std::vector<base::Closure> flush_requested_callbacks_;
+  std::vector<base::Closure> flush_pending_callbacks_;
 
   base::WaitableEvent no_flushes_pending_;
 
@@ -236,7 +246,7 @@
     return storage_manager_->UpdateSchemaVersion(table_name, version);
   }
 
-  void Flush() { storage_manager_->Flush(); }
+  void FlushOnChange() { storage_manager_->FlushOnChange(); }
 
   void FlushNow(const base::Closure& callback) {
     storage_manager_->FlushNow(callback);
diff --git a/src/cobalt/storage/storage_manager_test.cc b/src/cobalt/storage/storage_manager_test.cc
index f2c5de0..2d94612 100644
--- a/src/cobalt/storage/storage_manager_test.cc
+++ b/src/cobalt/storage/storage_manager_test.cc
@@ -22,6 +22,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/path_service.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/storage/savegame_fake.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -58,6 +59,7 @@
   bool TimedWait() {
     return was_called_event_.TimedWait(base::TimeDelta::FromSeconds(5));
   }
+  bool IsSignaled() { return was_called_event_.IsSignaled(); }
 
  protected:
   void Signal() { was_called_event_.Signal(); }
@@ -158,17 +160,6 @@
   EXPECT_EQ(true, waiter.TimedWait());
 }
 
-TEST_F(StorageManagerTest, FlushNow) {
-  // Ensure the Flush callback is called.
-  Init<StorageManager>();
-  storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
-  message_loop_.RunUntilIdle();
-  FlushWaiter waiter;
-  storage_manager_->FlushNow(
-      base::Bind(&FlushWaiter::OnFlushDone, base::Unretained(&waiter)));
-  EXPECT_EQ(true, waiter.TimedWait());
-}
-
 TEST_F(StorageManagerTest, QuerySchemaVersion) {
   Init<StorageManager>(false /* delete_savegame */);
   storage_manager_->GetSqlContext(base::Bind(&QuerySchemaCallback));
@@ -185,9 +176,20 @@
   message_loop_.RunUntilIdle();
 }
 
-TEST_F(StorageManagerTest, Flush) {
-  // Test that the Flush callback is called once (and only once, despite us
-  // calling Flush() multiple times).
+TEST_F(StorageManagerTest, FlushNow) {
+  // Ensure the Flush callback is called.
+  Init<StorageManager>();
+  storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
+  message_loop_.RunUntilIdle();
+  FlushWaiter waiter;
+  storage_manager_->FlushNow(
+      base::Bind(&FlushWaiter::OnFlushDone, base::Unretained(&waiter)));
+  EXPECT_EQ(true, waiter.TimedWait());
+}
+
+TEST_F(StorageManagerTest, FlushNowWithFlushOnChange) {
+  // Test that the Flush callback is called exactly once, despite calling both
+  // FlushOnChange() and FlushNow().
   Init<MockStorageManager>();
 
   storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
@@ -197,18 +199,74 @@
   MockStorageManager& storage_manager =
       *dynamic_cast<MockStorageManager*>(storage_manager_.get());
 
-  // When QueueFlush() is called, have it also call
-  // FlushWaiter::OnFlushDone(). We will wait for this in TimedWait().
+  // When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
+  // We will wait for this in TimedWait().
+  ON_CALL(storage_manager, QueueFlush(_))
+      .WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
+  EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
+
+  storage_manager_->FlushOnChange();
+  storage_manager_->FlushNow(
+      base::Bind(&FlushWaiter::OnFlushDone, base::Unretained(&waiter)));
+
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(3000));
+
+  EXPECT_EQ(true, waiter.IsSignaled());
+}
+
+TEST_F(StorageManagerTest, FlushOnChange) {
+  // Test that the Flush callback is called exactly once, despite calling
+  // FlushOnChange() multiple times.
+  Init<MockStorageManager>();
+
+  storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
+  message_loop_.RunUntilIdle();
+
+  FlushWaiter waiter;
+  MockStorageManager& storage_manager =
+      *dynamic_cast<MockStorageManager*>(storage_manager_.get());
+
+  // When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
+  // We will wait for this in TimedWait().
   ON_CALL(storage_manager, QueueFlush(_))
       .WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
   EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
 
   for (int i = 0; i < 10; ++i) {
-    storage_manager_->Flush();
+    storage_manager_->FlushOnChange();
   }
+
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(3000));
+
   EXPECT_EQ(true, waiter.TimedWait());
 }
 
+TEST_F(StorageManagerTest, FlushOnChangeMaxDelay) {
+  // Test that the Flush callback is called once from hitting the max delay when
+  // there are constant calls to FlushOnChange().
+  Init<MockStorageManager>();
+
+  storage_manager_->GetSqlContext(base::Bind(&FlushCallback));
+  message_loop_.RunUntilIdle();
+
+  FlushWaiter waiter;
+  MockStorageManager& storage_manager =
+      *dynamic_cast<MockStorageManager*>(storage_manager_.get());
+
+  // When QueueFlush() is called, have it also call FlushWaiter::OnFlushDone().
+  // We will wait for this in TimedWait().
+  ON_CALL(storage_manager, QueueFlush(_))
+      .WillByDefault(InvokeWithoutArgs(&waiter, &FlushWaiter::OnFlushDone));
+  EXPECT_CALL(storage_manager, QueueFlush(_)).Times(1);
+
+  for (int i = 0; i < 30; ++i) {
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+    storage_manager_->FlushOnChange();
+  }
+
+  EXPECT_EQ(true, waiter.IsSignaled());
+}
+
 TEST_F(StorageManagerTest, Upgrade) {
   Savegame::ByteVector initial_data;
   initial_data.push_back('U');
diff --git a/src/cobalt/system_window/input_event.h b/src/cobalt/system_window/input_event.h
index 78c3620..2235657 100644
--- a/src/cobalt/system_window/input_event.h
+++ b/src/cobalt/system_window/input_event.h
@@ -31,6 +31,9 @@
     kPointerDown,
     kPointerUp,
     kPointerMove,
+    kTouchpadDown,
+    kTouchpadUp,
+    kTouchpadMove,
     kWheel,
   };
 
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index c7c1c6d..3a21efb 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -14,6 +14,9 @@
 
 #include "cobalt/system_window/system_window.h"
 
+#include <algorithm>
+#include <cmath>
+
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/stringprintf.h"
@@ -100,22 +103,33 @@
   // uses.
   int key_code = static_cast<int>(data.key);
 #if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  float pressure = data.pressure;
   uint32 modifiers = data.key_modifiers;
   if (((data.device_type == kSbInputDeviceTypeTouchPad) ||
-       (data.device_type == kSbInputDeviceTypeTouchScreen)) &&
-      ((type == InputEvent::kPointerDown) ||
-       (type == InputEvent::kPointerMove))) {
-    // For touch contact input, ensure that the device button state is also
-    // reported as pressed.
-    //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#button-states
-    key_code = kSbKeyMouse1;
-    modifiers |= InputEvent::kLeftButton;
+       (data.device_type == kSbInputDeviceTypeTouchScreen))) {
+    switch (type) {
+      case InputEvent::kPointerDown:
+      case InputEvent::kPointerMove:
+      case InputEvent::kTouchpadDown:
+      case InputEvent::kTouchpadMove:
+        // For touch contact input, ensure that the device button state is also
+        // reported as pressed.
+        //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#button-states
+        key_code = kSbKeyMouse1;
+        modifiers |= InputEvent::kLeftButton;
+        if (!std::isnan(pressure)) {
+          pressure = std::max(pressure, 0.5f);
+        }
+        break;
+      default:
+        break;
+    }
   }
 
   scoped_ptr<InputEvent> input_event(
       new InputEvent(type, data.device_id, key_code, modifiers, is_repeat,
                      math::PointF(data.position.x, data.position.y),
-                     math::PointF(data.delta.x, data.delta.y), data.pressure,
+                     math::PointF(data.delta.x, data.delta.y), pressure,
                      math::PointF(data.size.x, data.size.y),
                      math::PointF(data.tilt.x, data.tilt.y)));
 #else
@@ -129,11 +143,19 @@
 
 void SystemWindow::HandlePointerInputEvent(const SbInputData& data) {
   switch (data.type) {
-    case kSbInputEventTypePress:
+    case kSbInputEventTypePress: {
+      InputEvent::Type input_event_type =
+          data.device_type == kSbInputDeviceTypeTouchPad
+              ? InputEvent::kTouchpadDown
+              : InputEvent::kPointerDown;
+      DispatchInputEvent(data, input_event_type, false /* is_repeat */);
+      break;
+    }
     case kSbInputEventTypeUnpress: {
-      InputEvent::Type input_event_type = data.type == kSbInputEventTypePress
-                                              ? InputEvent::kPointerDown
-                                              : InputEvent::kPointerUp;
+      InputEvent::Type input_event_type =
+          data.device_type == kSbInputDeviceTypeTouchPad
+              ? InputEvent::kTouchpadUp
+              : InputEvent::kPointerUp;
       DispatchInputEvent(data, input_event_type, false /* is_repeat */);
       break;
     }
@@ -143,9 +165,14 @@
       break;
     }
 #endif
-    case kSbInputEventTypeMove:
-      DispatchInputEvent(data, InputEvent::kPointerMove, false /* is_repeat */);
+    case kSbInputEventTypeMove: {
+      InputEvent::Type input_event_type =
+          data.device_type == kSbInputDeviceTypeTouchPad
+              ? InputEvent::kTouchpadMove
+              : InputEvent::kPointerMove;
+      DispatchInputEvent(data, input_event_type, false /* is_repeat */);
       break;
+    }
     default:
       SB_NOTREACHED();
       break;
@@ -157,7 +184,8 @@
 
   // Handle supported pointer device types.
   if ((kSbInputDeviceTypeMouse == data.device_type) ||
-      (kSbInputDeviceTypeTouchScreen == data.device_type)) {
+      (kSbInputDeviceTypeTouchScreen == data.device_type) ||
+      (kSbInputDeviceTypeTouchPad == data.device_type)) {
     HandlePointerInputEvent(data);
     return;
   }
@@ -185,13 +213,11 @@
 
 void OnDialogClose(SbSystemPlatformErrorResponse response, void* user_data) {
   DCHECK(user_data);
-  SystemWindow* system_window =
-      static_cast<SystemWindow*>(user_data);
+  SystemWindow* system_window = static_cast<SystemWindow*>(user_data);
   system_window->HandleDialogClose(response);
 }
 
-void SystemWindow::ShowDialog(
-    const SystemWindow::DialogOptions& options) {
+void SystemWindow::ShowDialog(const SystemWindow::DialogOptions& options) {
   SbSystemPlatformErrorType error_type =
       kSbSystemPlatformErrorTypeConnectionError;
   switch (options.message_code) {
@@ -208,13 +234,11 @@
   if (SbSystemPlatformErrorIsValid(handle)) {
     current_dialog_callback_ = options.callback;
   } else {
-    DLOG(WARNING) << "Failed to notify user of error: "
-                  << options.message_code;
+    DLOG(WARNING) << "Failed to notify user of error: " << options.message_code;
   }
 }
 
-void SystemWindow::HandleDialogClose(
-    SbSystemPlatformErrorResponse response) {
+void SystemWindow::HandleDialogClose(SbSystemPlatformErrorResponse response) {
   DCHECK(!current_dialog_callback_.is_null());
   switch (response) {
     case kSbSystemPlatformErrorResponsePositive:
diff --git a/src/cobalt/tools/deps_substitutions.txt b/src/cobalt/tools/deps_substitutions.txt
new file mode 100644
index 0000000..5464f62
--- /dev/null
+++ b/src/cobalt/tools/deps_substitutions.txt
@@ -0,0 +1,3 @@
+<(DEPTH)/starboard/examples/blitter/blitter.gyp:starboard_blitter_example	//starboard/examples/blitter
+<(DEPTH)/starboard/examples/glclear/glclear.gyp:starboard_glclear_example	//starboard/examples/glclear
+<(DEPTH)/starboard/examples/window/window.gyp:starboard_window_example	//starboard/examples/window
diff --git a/src/cobalt/tools/gyp_to_gn.py b/src/cobalt/tools/gyp_to_gn.py
new file mode 100755
index 0000000..d4d235b
--- /dev/null
+++ b/src/cobalt/tools/gyp_to_gn.py
@@ -0,0 +1,799 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Tries to translate a GYP file into a GN file.
+
+Output from this script should be piped to ``gn format --stdin``. It most likely
+needs to be manually fixed after that as well.
+"""
+
+import argparse
+import ast
+import collections
+import itertools
+import os
+import os.path
+import re
+import sys
+
+sys.path.append(
+    os.path.abspath(
+        os.path.join(__file__, os.pardir, os.pardir, os.pardir)))
+import cobalt.tools.paths  # pylint: disable=g-import-not-at-top
+
+VariableRewrite = collections.namedtuple('VariableRewrite',
+                                         ['name', 'value_rewrites'])
+
+# The directory containing the input file, as a repository-absolute (//foo/bar)
+# path
+repo_abs_input_file_dir = ''
+deps_substitutions = {}
+variable_rewrites = {}
+
+
+class GNException(Exception):
+  pass
+
+
+class GYPCondToGNNodeVisitor(ast.NodeVisitor):
+  """An AST NodeVisitor which translates GYP conditions to GN strings.
+
+  Given a GYP condition as an AST with mode eval, outputs a string containing
+  the GN equivalent of that condition. Simplifies conditions involving the
+  variables OS and os_posix, and performs variable substitutions.
+
+  Example:
+  (Assume arm_neon is renamed to arm_use_neon and converted from 0/1 to
+  true/false):
+
+  >>> g = GYPCondToGNNodeVisitor()
+  >>> g.visit(ast.parse('arm_neon and target_arch=="xb1"', mode='eval'))
+  '(arm_use_neon && target_cpu == "xb1")'
+  >>> g.visit(ast.parse('use_system_libjpeg and target_arch=="xb1"',
+  ...                   mode='eval'))
+  '(use_system_libjpeg && target_cpu == "xb1")'
+  >>> g.visit(ast.parse('arm_neon == 1', mode='eval'))
+  'arm_use_neon == true'
+  >>> g.visit(ast.parse('1', mode='eval'))
+  'true'
+  >>> g.visit(ast.parse('0', mode='eval'))
+  'false'
+  >>> g.visit(ast.parse('arm_neon != 0 and target_arch != "xb1" and
+  use_system_libjpeg or enable_doom_melon', mode='eval'))
+  '((arm_use_neon != false && target_cpu != "xb1" && use_system_libjpeg) ||
+  enable_doom_melon)'
+  >>> g.visit(ast.parse('arm_neon != 0 and target_arch != "xb1" or
+  use_system_libjpeg and enable_doom_melon', mode='eval'))
+  '((arm_use_neon != false && target_cpu != "xb1") || (use_system_libjpeg &&
+  enable_doom_melon))'
+  """
+
+  def visit_Expression(self, expr):  # pylint: disable=invalid-name
+    return self.visit(expr.body)
+
+  def visit_Num(self, num):  # pylint: disable=invalid-name
+    # A number that doesn't occur inside a Compare is taken in boolean context
+    return GYPValueToGNString(bool(num.n))
+
+  def visit_Str(self, string):  # pylint: disable=invalid-name
+    return GYPValueToGNString(string.s)
+
+  def visit_Name(self, name):  # pylint: disable=invalid-name
+    if name.id in variable_rewrites:
+      return variable_rewrites[name.id].name
+    else:
+      return name.id
+
+  def visit_BoolOp(self, boolop):  # pylint: disable=invalid-name
+    glue = ' && ' if isinstance(boolop.op, ast.And) else ' || '
+    return '(' + glue.join(itertools.imap(self.visit, boolop.values)) + ')'
+
+  def visit_Compare(self, compare):  # pylint: disable=invalid-name
+    if len(compare.ops) != 1:
+      raise GNException("This script doesn't support multiple operators in "
+                        'comparisons')
+
+    if isinstance(compare.ops[0], ast.Eq):
+      op = ' == '
+    elif isinstance(compare.ops[0], ast.NotEq):
+      op = ' != '
+    else:
+      raise GNException('Operator ' + str(compare.ops[0]) + ' not supported')
+
+    if isinstance(compare.left, ast.Name):
+      if isinstance(compare.comparators[0], ast.Name):  # var1 == var2
+        left = self.visit_Name(compare.left)
+        right = self.visit_Name(compare.comparators[0])
+      elif isinstance(compare.comparators[0], ast.Num):  # var1 == 42
+        left, right = TransformVariable(compare.left.id,
+                                        compare.comparators[0].n)
+      elif isinstance(compare.comparators[0], ast.Str):  # var1 == "some string"
+        left, right = TransformVariable(compare.left.id,
+                                        compare.comparators[0].s)
+      else:
+        raise GNException('Unknown RHS type ' + str(compare.comparators[0]))
+    else:
+      raise GNException('Non-variables on LHS of comparison are not supported')
+
+    if right == 'true' and op == ' == ' or right == 'false' and op == ' != ':
+      return left
+    elif right == 'false' and op == ' == ' or right == 'true' and op == ' != ':
+      return '!(' + left + ')'
+    else:
+      return left + op + right
+
+  def generic_visit(self, node):
+    raise GNException("I don't know how to convert node " + str(node))
+
+
+class OSComparisonRewriter(ast.NodeTransformer):
+
+  def visit_Compare(self, compare):  # pylint: disable=invalid-name
+    """Substitute instances of comparisons involving the OS and os_posix
+    variables with their known values in Compare nodes.
+
+    Examples:
+      ``OS == "starboard"`` -> ``True``
+      ``OS != "starboard"`` -> ``False``
+      ``OS == "linux"`` -> ``False``
+      ``os_posix == 1`` -> ``False``
+      ``os_bsd == 1`` -> ``False``
+    """
+    if len(compare.ops) != 1:
+      raise GNException("This script doesn't support multiple operators in "
+                        'comparisons')
+
+    if not isinstance(compare.left, ast.Name):
+      return compare
+
+    elif compare.left.id == 'OS':
+      if (isinstance(compare.comparators[0], ast.Str) and
+          compare.comparators[0].s == 'starboard'):
+        # OS == "starboard" -> True, OS != "starboard" -> False
+        new_node = ast.Num(1 if isinstance(compare.ops[0], ast.Eq) else 0)
+      else:
+        # OS == "something else" -> False, OS != "something else" -> True
+        new_node = ast.Num(0 if isinstance(compare.ops[0], ast.Eq) else 1)
+
+      return ast.copy_location(new_node, compare)
+
+    elif compare.left.id in {'os_posix', 'os_bsd'}:
+      if (isinstance(compare.comparators[0], ast.Num) and
+          compare.comparators[0].n == 0):
+        # os_posix == 0 -> True, os_posix != 0 -> False
+        # ditto for os_bsd
+        new_node = ast.Num(1 if isinstance(compare.ops[0], ast.Eq) else 0)
+      else:
+        # os_posix == (not 0) -> False, os_posix != (not 0) -> True
+        # ditto for os_bsd
+        new_node = ast.Num(0 if isinstance(compare.ops[0], ast.Eq) else 1)
+
+      return ast.copy_location(new_node, compare)
+
+    else:
+      return compare
+
+  def visit_BoolOp(self, boolop):  # pylint: disable=invalid-name
+    """Simplify BoolOp nodes by weeding out Falses and Trues resulting from
+    the elimination of OS comparisons.
+    """
+    doing_and = isinstance(boolop.op, ast.And)
+    new_values = map(self.visit, boolop.values)
+    new_values_filtered = []
+
+    for v in new_values:
+      if isinstance(v, ast.Num):
+        # "x and False", or "y or True" - short circuit
+        if (doing_and and v.n == 0) or (not doing_and and v.n == 1):
+          new_values_filtered = None
+          break
+        # "x and True", or "y or False" - skip the redundant value
+        elif (doing_and and v.n == 1) or (not doing_and and v.n == 0):
+          pass
+        else:
+          new_values_filtered.append(v)
+      else:
+        new_values_filtered.append(v)
+
+    if new_values_filtered is None:
+      new_node = ast.Num(0 if doing_and else 1)
+    elif len(new_values_filtered) == 0:
+      new_node = ast.Num(1 if doing_and else 0)
+    elif len(new_values_filtered) == 1:
+      new_node = new_values_filtered[0]
+    else:
+      new_node = ast.BoolOp(boolop.op, new_values_filtered)
+
+    return ast.copy_location(new_node, boolop)
+
+
+def Warn(msg):
+  print >> sys.stderr, '\x1b[1;33mWarning:\x1b[0m', msg
+
+
+def WarnAboutRemainingKeys(dic):
+  for key in dic:
+    Warn("I don't know what {} is".format(key))
+
+
+def TransformVariable(var, value):
+  """Given a variable and value, substitutes the variable name/value with
+  the new name/new value from the variable_rewrites dict, if applicable.
+
+  Example:
+    ``('arm_neon', 1)`` -> ``('arm_use_neon', 'true')``
+    ``('gl_type', 'none')`` -> ``('gl_type', 'none')``
+  """
+  if var in variable_rewrites:
+    var, value_rewrites = variable_rewrites[var]
+    if value_rewrites is not None:
+      value = value_rewrites[value]
+
+  return var, GYPValueToGNString(value)
+
+
+def GYPValueToGNString(value, allow_dicts=True):
+  """Returns a stringified GN equivalent of the Python value.
+
+  allow_dicts indicates if this function will allow converting dictionaries
+  to GN scopes. This is only possible at the top level, you can't nest a
+  GN scope in a list, so this should be set to False for recursive calls."""
+
+  if isinstance(value, str):
+    if value.find('\n') >= 0:
+      raise GNException("GN strings don't support newlines")
+    # Escape characters
+    ret = value.replace('\\', r'\\').replace('"', r'\"') \
+               .replace('$', r'\$')
+    # Convert variable substitutions
+    ret = re.sub(r'[<>]\((\w+)\)', r'$\1', ret)
+    return '"' + ret + '"'
+
+  if isinstance(value, unicode):
+    if value.find(u'\n') >= 0:
+      raise GNException("GN strings don't support newlines")
+    # Escape characters
+    ret = value.replace(u'\\', ur'\\').replace(u'"', ur'\"') \
+               .replace(u'$', ur'\$')
+    # Convert variable substitutions
+    ret = re.sub(ur'[<>]\((\w+)\)', ur'$\1', ret)
+    return (u'"' + ret + u'"').encode('utf-8')
+
+  if isinstance(value, bool):
+    if value:
+      return 'true'
+    return 'false'
+
+  if isinstance(value, list):
+    return '[ %s ]' % ', '.join(GYPValueToGNString(v) for v in value)
+
+  if isinstance(value, dict):
+    if not allow_dicts:
+      raise GNException('Attempting to recursively print a dictionary.')
+    result = ''
+    for key in sorted(value):
+      if not isinstance(key, basestring):
+        raise GNException('Dictionary key is not a string.')
+      result += '%s = %s\n' % (key, GYPValueToGNString(value[key], False))
+    return result
+
+  if isinstance(value, int):
+    return str(value)
+
+  raise GNException('Unsupported type when printing to GN.')
+
+
+def GYPTargetToGNString(target_dict):
+  """Given a target dict, output the GN equivalent."""
+
+  target_header_text = ''
+
+  target_name = target_dict.pop('target_name')
+  target_type = target_dict.pop('type')
+  if target_type in {'executable', 'shared_library', 'static_library'}:
+    if target_type == 'static_library':
+      Warn('converting static library, check to see if it should be a '
+           'source_set')
+    target_header_text += '{}("{}") {{\n'.format(target_type, target_name)
+  elif target_type == 'none':
+    target_header_text += 'group("{}") {{\n'.format(target_name)
+  elif target_type == '<(gtest_target_type)':
+    target_header_text += 'test("{}") {{\n'.format(target_name)
+  elif target_type == '<(final_executable_type)':
+    target_header_text += 'final_executable("{}") {{\n'.format(target_name)
+  elif target_type == '<(component)' or target_type == '<(library)':
+    Warn('converting static library, check to see if it should be a '
+         'source_set')
+    target_header_text += 'static_library("{}") {{\n'.format(target_name)
+  else:
+    raise GNException("I don't understand target type {}".format(target_type))
+
+  target_body_text, configs_text = \
+      GYPTargetToGNString_Inner(target_dict, target_name)
+
+  return configs_text + target_header_text + target_body_text + '}\n\n'
+
+
+def ProcessIncludeExclude(dic, param):
+  """Translate dic[param] and dic[param + '!'] lists to GN.
+
+  Example input:
+    {
+      'sources': [ 'foo.cc', 'bar.cc', 'baz.cc' ],
+      'sources!': [ 'bar.cc' ]
+    }
+  Example output:
+    sources = [ 'foo.cc', 'bar.cc', 'baz.cc' ]
+    sources -= [ 'bar.cc' ]
+  """
+  ret = ''
+  if param in dic:
+    value = dic.pop(param)
+    ret += '{} = [ {} ]\n'.format(param, ', '.join(
+        GYPValueToGNString(s) for s in value))
+  if param + '!' in dic:
+    value = dic.pop(param + '!')
+    ret += '{} -= [ {} ]\n'.format(param, ', '.join(
+        GYPValueToGNString(s) for s in value))
+  ret += '\n'
+  return ret
+
+
+def ProcessDependentSettings(dependent_settings_dict, target_dict, param,
+                             renamed_param, config_name):
+  """Translates direct_dependent_settings and all_dependent_settings blocks
+  to their GN equivalents. This is done by creating a new GN config, putting
+  the settings in that config, and adding the config to the target's
+  public_configs/all_dependent_configs. Returns a tuple of
+  (target_text, config_text).
+
+  Also eliminates the translated settings from the target_dict so they aren't
+  translated twice.
+
+  Example input:
+    {
+      'target_name': 'abc',
+      'direct_dependent_settings': {
+        'defines': [ "FOO" ]
+      }
+    }
+
+  Example target text output:
+    public_configs = [ ":abc_direct_config" ]
+
+  Example config text output:
+    config("abc_direct_config") {
+      defines = [ "FOO" ]
+    }
+  """
+  ret_target = ''
+  ret_config = 'config("{}") {{\n'.format(config_name)
+
+  def FilterList(key):
+    if key in target_dict and key in dependent_settings_dict:
+      filtered_list = sorted(
+          set(target_dict[key]) - set(dependent_settings_dict[key]))
+      if filtered_list:
+        target_dict[key] = filtered_list
+      else:
+        del target_dict[key]
+
+  for inner_param in [
+      'include_dirs', 'defines', 'asmflags', 'cflags', 'cflags_c', 'cflags_cc',
+      'cflags_objc', 'cflags_objcc', 'ldflags'
+  ]:
+    FilterList(inner_param)
+    FilterList(inner_param + '!')
+    ret_config += ProcessIncludeExclude(dependent_settings_dict, inner_param)
+
+  if 'variables' in dependent_settings_dict:
+    Warn("variables block inside {}. You'll need to handle that manually."
+         .format(param))
+    del dependent_settings_dict['variables']
+
+  if 'conditions' in dependent_settings_dict:
+    for i, cond_block in enumerate(dependent_settings_dict['conditions']):
+      cond_config_name = '{}_{}'.format(config_name, i)
+      t, c = GYPConditionToGNString(cond_block,
+               lambda dsd: ProcessDependentSettings(dsd, target_dict, param,
+                                                    renamed_param,
+                                                    cond_config_name))
+      ret_config += c
+      ret_target += t
+    del dependent_settings_dict['conditions']
+
+  if 'target_conditions' in dependent_settings_dict:
+    for i, cond_block in \
+        enumerate(dependent_settings_dict['target_conditions']):
+      cond_config_name = '{}_t{}'.format(config_name, i)
+      t, c = GYPConditionToGNString(cond_block,
+               lambda dsd: ProcessDependentSettings(dsd, target_dict, param,
+                                                    renamed_param,
+                                                    cond_config_name))
+      ret_config += c
+      ret_target += t
+    del dependent_settings_dict['target_conditions']
+
+  ret_config += '}\n\n'
+  ret_target += '{} = [ ":{}" ]\n\n'.format(renamed_param, config_name)
+  WarnAboutRemainingKeys(dependent_settings_dict)
+  return ret_target, ret_config
+
+
+def GYPTargetToGNString_Inner(target_dict, target_name):
+  """Translates the body of a GYP target into a GN string."""
+  configs_text = ''
+  target_text = ''
+  dependent_text = ''
+
+  if 'variables' in target_dict:
+    target_text += GYPVariablesToGNString(target_dict['variables'])
+    del target_dict['variables']
+
+  target_text += ProcessIncludeExclude(target_dict, 'sources')
+
+  for param, renamed_param, config_name_elem in \
+      [('direct_dependent_settings', 'public_configs', 'direct'),
+       ('all_dependent_settings', 'all_dependent_configs', 'dependent')]:
+    if param in target_dict:
+      config_name = '{}_{}_config'.format(target_name, config_name_elem)
+      # Append dependent_text to target_text after include_dirs/defines/etc.
+      dependent_text, c = ProcessDependentSettings(
+          target_dict[param], target_dict, param, renamed_param, config_name)
+      configs_text += c
+      del target_dict[param]
+
+  for param in [
+      'include_dirs', 'defines', 'asmflags', 'cflags', 'cflags_c', 'cflags_cc',
+      'cflags_objc', 'cflags_objcc', 'ldflags'
+  ]:
+    target_text += ProcessIncludeExclude(target_dict, param)
+
+  target_text += dependent_text
+
+  if 'export_dependent_settings' in target_dict:
+    target_text += GYPDependenciesToGNString(
+        'public_deps', target_dict['export_dependent_settings'])
+
+    # Remove dependencies covered here from the ordinary dependencies list
+    target_dict['dependencies'] = sorted(
+        set(target_dict['dependencies']) -
+        set(target_dict['export_dependent_settings']))
+    if not target_dict['dependencies']:
+      del target_dict['dependencies']
+
+    del target_dict['export_dependent_settings']
+
+  if 'dependencies' in target_dict:
+    target_text += GYPDependenciesToGNString('deps',
+                                             target_dict['dependencies'])
+    del target_dict['dependencies']
+
+  if 'conditions' in target_dict:
+    for cond_block in target_dict['conditions']:
+      t, c = GYPConditionToGNString(
+          cond_block, lambda td: GYPTargetToGNString_Inner(td, target_name))
+      configs_text += c
+      target_text += t
+    del target_dict['conditions']
+
+  if 'target_conditions' in target_dict:
+    for cond_block in target_dict['target_conditions']:
+      t, c = GYPConditionToGNString(
+          cond_block, lambda td: GYPTargetToGNString_Inner(td, target_name))
+      configs_text += c
+      target_text += t
+    del target_dict['target_conditions']
+
+  WarnAboutRemainingKeys(target_dict)
+  return target_text, configs_text
+
+
+def GYPDependenciesToGNString(param, dep_list):
+  r"""Translates a GYP dependency into a GN dependency. Tries to intelligently
+  perform target renames as according to GN style conventions.
+
+  Examples:
+  (Note that <(DEPTH) has already been translated into // by the time this
+  function is called)
+  >>> GYPDependenciesToGNString('deps', ['//cobalt/math/math.gyp:math'])
+  'deps = [ "//cobalt/math" ]\n\n'
+  >>> GYPDependenciesToGNString('public_deps',
+  ...                           ['//testing/gtest.gyp:gtest'])
+  'public_deps = [ "//testing/gtest" ]\n\n'
+  >>> GYPDependenciesToGNString('deps', ['//third_party/icu/icu.gyp:icui18n'])
+  'deps = [ "//third_party/icu:icui18n" ]\n\n'
+  >>> GYPDependenciesToGNString('deps',
+  ...     ['//cobalt/browser/browser_bindings_gen.gyp:generated_types'])
+  'deps = [ "//cobalt/browser/browser_bindings_gen:generated_types" ]\n\n'
+  >>> GYPDependenciesToGNString('deps', ['allocator'])
+  'deps = [ ":allocator" ]\n\n'
+  >>> # Suppose the input file is in //cobalt/foo/bar/
+  >>> GYPDependenciesToGNString('deps', ['../baz.gyp:quux'])
+  'deps = [ "//cobalt/foo/baz:quux" ]\n\n'
+  """
+  new_dep_list = []
+  for dep in dep_list:
+    if dep in deps_substitutions:
+      new_dep_list.append(deps_substitutions[dep])
+    else:
+      m1 = re.match(r'(.*/)?(\w+)/\2\.gyp:\2$', dep)
+      m2 = re.match(r'(.*/)?(\w+)\.gyp:\2$', dep)
+      m3 = re.match(r'(.*/)?(\w+)/\2.gyp:(\w+)$', dep)
+      m4 = re.match(r'(.*)\.gyp:(\w+)$', dep)
+      m5 = re.match(r'\w+', dep)
+      if m1:
+        new_dep = '{}{}'.format(m1.group(1) or '', m1.group(2))
+      elif m2:
+        new_dep = '{}{}'.format(m2.group(1) or '', m2.group(2))
+      elif m3:
+        new_dep = '{}{}:{}'.format(m3.group(1) or '', m3.group(2), m3.group(3))
+      elif m4:
+        new_dep = '{}:{}'.format(m4.group(1), m4.group(2))
+      elif m5:
+        new_dep = ':' + dep
+      else:
+        Warn("I don't know how to translate dependency " + dep)
+        continue
+
+      if not (new_dep.startswith('//') or new_dep.startswith(':') or
+              os.path.isabs(new_dep)):
+        # Rebase new_dep to be repository-absolute
+        new_dep = os.path.normpath(repo_abs_input_file_dir + new_dep)
+
+      new_dep_list.append(new_dep)
+
+  quoted_deps = ['"{}"'.format(d) for d in new_dep_list]
+  return '{} = [ {} ]\n\n'.format(param, ', '.join(quoted_deps))
+
+
+def GYPVariablesToGNString(varblock):
+  """Translates a GYP variables block into its GN equivalent, performing
+  variable substitutions as necessary."""
+  ret = ''
+
+  if 'variables' in varblock:
+    # Recursively flatten nested variables dicts.
+    ret += GYPVariablesToGNString(varblock['variables'])
+
+  for k, v in varblock.viewitems():
+    if k in {'variables', 'conditions'}:
+      continue
+
+    if k.endswith('%'):
+      k = k[:-1]
+
+    if not k.endswith('!'):
+      ret += '{} = {}\n'.format(*TransformVariable(k, v))
+    else:
+      ret += '{} -= {}\n'.format(*TransformVariable(k[:-1], v))
+
+  if 'conditions' in varblock:
+    for cond_block in varblock['conditions']:
+      ret += GYPConditionToGNString(cond_block, GYPVariablesToGNString)[0]
+
+  ret += '\n'
+  return ret
+
+
+def GYPConditionToGNString(cond_block, recursive_translate):
+  """Translates a GYP condition block into a GN string. The recursive_translate
+  param is a function that is called with the true subdict and the false
+  subdict if applicable. The recursive_translate function returns either a
+  single string that should go inside the if/else block, or a tuple of
+  (target_text, config_text), in which the target_text goes inside the if/else
+  block and the config_text goes outside.
+
+  Returns a tuple (target_text, config_text), where config_text is the empty
+  string if recursive_translate only returns one string.
+  """
+  cond = cond_block[0]
+  iftrue = cond_block[1]
+  iffalse = cond_block[2] if len(cond_block) == 3 else None
+
+  ast_cond = ast.parse(cond, mode='eval')
+  ast_cond = OSComparisonRewriter().visit(ast_cond)
+  translated_cond = GYPCondToGNNodeVisitor().visit(ast_cond)
+
+  if translated_cond == 'false' and iffalse is None:
+    # if (false) { ... } -> nothing
+    # Special cased to avoid printing warnings from the unnecessary translation
+    # of the true clause
+    return '', ''
+
+  translated_iftrue = recursive_translate(iftrue)
+  if isinstance(translated_iftrue, tuple):
+    iftrue_text, aux_iftrue_text = translated_iftrue
+  else:
+    iftrue_text, aux_iftrue_text = translated_iftrue, ''
+
+  if translated_cond == 'true':  # Tautology - just return the body
+    return iftrue_text, aux_iftrue_text
+
+  elif iffalse is None:  # Non-tautology, non-contradiction, no else clause
+    return ('if (' + translated_cond + ') {\n' + iftrue_text + '}\n\n',
+            aux_iftrue_text)
+
+  else:  # Non-tautology, else clause present
+    translated_iffalse = recursive_translate(iffalse)
+    if isinstance(translated_iffalse, tuple):
+      iffalse_text, aux_iffalse_text = translated_iffalse
+    else:
+      iffalse_text, aux_iffalse_text = translated_iffalse, ''
+
+    if translated_cond == 'false':  # if (false) { blah } else { ... } -> ...
+      return iffalse_text, aux_iffalse_text
+
+    else:
+      return ('if (' + translated_cond + ') {\n' + iftrue_text + '} else {\n' +
+              iffalse_text + '}\n\n', aux_iftrue_text + aux_iffalse_text)
+
+
+def ToplevelGYPToGNString(toplevel_dict):
+  """Translates a toplevel GYP dict to GN. This is the main function which is
+  called to perform the GYP to GN translation.
+  """
+  ret = ''
+
+  if 'variables' in toplevel_dict:
+    ret += GYPVariablesToGNString(toplevel_dict['variables'])
+    del toplevel_dict['variables']
+
+  if 'targets' in toplevel_dict:
+    for t in toplevel_dict['targets']:
+      ret += GYPTargetToGNString(t)
+    del toplevel_dict['targets']
+
+  if 'conditions' in toplevel_dict:
+    for cond_block in toplevel_dict['conditions']:
+      ret += GYPConditionToGNString(cond_block, ToplevelGYPToGNString)[0]
+    del toplevel_dict['conditions']
+
+  if 'target_conditions' in toplevel_dict:
+    for cond_block in toplevel_dict['target_conditions']:
+      ret += GYPConditionToGNString(cond_block, ToplevelGYPToGNString)[0]
+    del toplevel_dict['target_conditions']
+
+  WarnAboutRemainingKeys(toplevel_dict)
+  return ret
+
+
+def LoadPythonDictionary(path):
+  with open(path, 'r') as fin:
+    file_string = fin.read()
+  try:
+    file_data = eval(file_string, {'__builtins__': None}, None)  # pylint: disable=eval-used
+  except SyntaxError, e:
+    e.filename = path
+    raise
+  except Exception, e:
+    raise Exception('Unexpected error while reading %s: %s' % (path, str(e)))
+
+  assert isinstance(file_data, dict), '%s does not eval to a dictionary' % path
+
+  return file_data
+
+
+def ReplaceSubstrings(values, search_for, replace_with):
+  """Recursively replaces substrings in a value.
+
+  Replaces all substrings of the "search_for" with "replace_with" for all
+  strings occurring in "values". This is done by recursively iterating into
+  lists as well as the keys and values of dictionaries.
+  """
+  if isinstance(values, str):
+    return values.replace(search_for, replace_with)
+
+  if isinstance(values, list):
+    return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
+
+  if isinstance(values, dict):
+    # For dictionaries, do the search for both the key and values.
+    result = {}
+    for key, value in values.viewitems():
+      new_key = ReplaceSubstrings(key, search_for, replace_with)
+      new_value = ReplaceSubstrings(value, search_for, replace_with)
+      result[new_key] = new_value
+    return result
+
+  # Assume everything else is unchanged.
+  return values
+
+
+def CalculatePaths(filename):
+  global repo_abs_input_file_dir
+
+  abs_input_file_dir = os.path.abspath(os.path.dirname(filename))
+  abs_repo_root = cobalt.tools.paths.REPOSITORY_ROOT + '/'
+  if not abs_input_file_dir.startswith(abs_repo_root):
+    raise ValueError('Input file is not in repository')
+
+  repo_abs_input_file_dir = '//' + abs_input_file_dir[len(abs_repo_root):] + '/'
+
+
+def LoadDepsSubstitutions():
+  dirname = os.path.dirname(__file__)
+  if dirname:
+    dirname += '/'
+
+  with open(dirname + 'deps_substitutions.txt', 'r') as f:
+    for line in itertools.ifilter(lambda lin: lin.strip(), f):
+      dep, new_dep = line.split()
+      deps_substitutions[dep.replace('<(DEPTH)/', '//')] = new_dep
+
+
+def LoadVariableRewrites():
+  dirname = os.path.dirname(__file__)
+  if dirname:
+    dirname += '/'
+
+  vr = LoadPythonDictionary(dirname + 'variable_rewrites.dict')
+
+  if 'rewrites' in vr:
+    for old_name, rewrite in vr['rewrites'].viewitems():
+      variable_rewrites[old_name] = VariableRewrite(*rewrite)
+
+  if 'renames' in vr:
+    for old_name, new_name in vr['renames'].viewitems():
+      variable_rewrites[old_name] = VariableRewrite(new_name, None)
+
+  if 'booleans' in vr:
+    bool_mapping = {0: False, 1: True}
+    for v in vr['booleans']:
+      if v in variable_rewrites:
+        variable_rewrites[v] = \
+            variable_rewrites[v]._replace(value_rewrites=bool_mapping)
+      else:
+        variable_rewrites[v] = VariableRewrite(v, bool_mapping)
+
+
+def main():
+  parser = argparse.ArgumentParser(description='Convert a subset of GYP to GN.')
+  parser.add_argument(
+      '-r',
+      '--replace',
+      action='append',
+      help='Replaces substrings. If passed a=b, replaces all '
+      'substrs a with b.')
+  parser.add_argument('file', help='The GYP file to read.')
+  args = parser.parse_args()
+
+  CalculatePaths(args.file)
+
+  data = LoadPythonDictionary(args.file)
+  if args.replace:
+    # Do replacements for all specified patterns.
+    for replace in args.replace:
+      split = replace.split('=')
+      # Allow 'foo=' to replace with nothing.
+      if len(split) == 1:
+        split.append('')
+      assert len(split) == 2, "Replacement must be of the form 'key=value'."
+      data = ReplaceSubstrings(data, split[0], split[1])
+  # Also replace <(DEPTH)/ with //
+  data = ReplaceSubstrings(data, '<(DEPTH)/', '//')
+
+  LoadDepsSubstitutions()
+  LoadVariableRewrites()
+
+  print ToplevelGYPToGNString(data)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/src/cobalt/tools/variable_rewrites.dict b/src/cobalt/tools/variable_rewrites.dict
new file mode 100644
index 0000000..e8165b3
--- /dev/null
+++ b/src/cobalt/tools/variable_rewrites.dict
@@ -0,0 +1,47 @@
+{
+  # Variables which need to be renamed, and whose values need to be
+  # simultaneously changed
+  "rewrites": {
+  },
+
+  # Variables which only need to be renamed
+  "renames": {
+    "cobalt_fastbuild": "cobalt_use_fastbuild",
+    "cobalt_media_source_2016": "cobalt_use_media_source_2016",
+    "custom_media_session_client": "enable_custom_media_session_client",
+    "cobalt_version": "cobalt_build_id",
+    "in_app_dial": "enable_in_app_dial",
+    "sb_allows_memory_tracking": "sb_allow_memory_tracking",
+    "target_arch": "target_cpu",
+    "tizen_os": "is_tizen_os",
+  },
+
+  # List of variables which are actually booleans
+  # Note that we use the old names here!
+  "booleans": [
+    "abort_on_allocation_failure",
+    "cobalt_copy_debug_console",
+    "cobalt_copy_test_data",
+    "cobalt_enable_jit",
+    "cobalt_encrypted_media_extension_enable_key_statuses_update",
+    "cobalt_fastbuild",
+    "cobalt_media_source_2016",
+    "custom_media_session_client",
+    "enable_about_scheme",
+    "enable_account_manager",
+    "enable_crash_log",
+    "enable_fake_microphone",
+    "enable_file_scheme",
+    "enable_network_logging",
+    "enable_remote_debugging",
+    "enable_screenshot",
+    "enable_webdriver",
+    "enable_spdy",
+    "enable_xhr_header_filtering",
+    "in_app_dial",
+    "render_dirty_region_only",
+    "sb_allows_memory_tracking",
+    "sb_enable_lib",
+    "tizen_os",
+  ]
+}
diff --git a/src/starboard/CHANGELOG.md b/src/starboard/CHANGELOG.md
index 4b2c85c..a77e4e1 100644
--- a/src/starboard/CHANGELOG.md
+++ b/src/starboard/CHANGELOG.md
@@ -6,6 +6,16 @@
 this file describes the changes made to the Starboard interface since the
 version previous to it.
 
+## Version 7
+
+### `SbDecodeTargetInfoPlane` can specify color plane information
+
+Previously: Planes of type `kSbDecodeTargetFormat2PlaneYUVNV12`
+were assumed to have the luma mapped to the alpha channel (`GL_ALPHA`)
+and the chroma mapped to blue and alpha (`GL_LUMINANCE_ALPHA`). However,
+some graphics systems require that luma is on `GL_RED_EXT` and the chroma
+is on `GL_RG_EXT`.
+
 ## Version 6
 
 ### Named `SbStorageRecord`s
diff --git a/src/starboard/common/common.cc b/src/starboard/common/common.cc
index e1e90c1..82102fb 100644
--- a/src/starboard/common/common.cc
+++ b/src/starboard/common/common.cc
@@ -23,8 +23,9 @@
     "own risk!  We don't recommend this for third parties.")
 #endif
 
-#if SB_API_VERSION == SB_RELEASE_CANDIDATE_API_VERSION
+#if SB_API_VERSION >= SB_RELEASE_CANDIDATE_API_VERSION && \
+    SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION
 #pragma message( \
-    "Your platform's SB_API_VERSION == SB_RELEASE_CANDIDATE_API_VERSION. " \
+    "Your platform's SB_API_VERSION >= SB_RELEASE_CANDIDATE_API_VERSION. " \
     "Note that the RC version of Starboard is still subject to change.")
 #endif
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index cb8d84a..e8a4f90 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -39,12 +39,12 @@
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MAXIMUM_API_VERSION 7
+#define SB_MAXIMUM_API_VERSION 8
 
 // 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 7
+#define SB_EXPERIMENTAL_API_VERSION 8
 
 // 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
@@ -64,16 +64,8 @@
 //   //   exposes functionality for my new feature.
 //   #define SB_MY_EXPERIMENTAL_FEATURE_VERSION SB_EXPERIMENTAL_API_VERSION
 
-// SbDecodeTargetInfoPlane's now can specify color plane information.
-// Previously: Planes of type kSbDecodeTargetFormat2PlaneYUVNV12
-// were assumed to have the luma mapped to the alpha channel (GL_ALPHA)
-// and the chroma mapped to blue and alpha (GL_LUMINANCE_ALPHA). However,
-// some graphics systems require that luma is on GL_RED_EXT and the chroma
-// is on GL_RG_EXT.
-
-#define SB_DECODE_TARGET_PLANE_FORMAT_VERSION SB_EXPERIMENTAL_API_VERSION
-
 // --- Release Candidate Feature Defines -------------------------------------
+#define SB_DECODE_TARGET_PLANE_FORMAT_VERSION 7
 
 #define SB_POINTER_INPUT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
 #define SB_AUDIO_SPECIFIC_CONFIG_AS_POINTER SB_RELEASE_CANDIDATE_API_VERSION
@@ -81,14 +73,11 @@
 #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_DECODE_TARGET_UYVY_SUPPORT_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
-#define SB_PLAYER_WRITE_SAMPLE_EXTRA_CONST_API_VERSION \
-  SB_RELEASE_CANDIDATE_API_VERSION
-#define SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION \
-  SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_PLAYER_WRITE_SAMPLE_EXTRA_CONST_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
+#define SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
 #define SB_STORAGE_NAMES_API_VERSION SB_RELEASE_CANDIDATE_API_VERSION
 
 // --- Common Detected Features ----------------------------------------------
diff --git a/src/starboard/doc/versioning.md b/src/starboard/doc/versioning.md
index 6931625..1ac18df 100644
--- a/src/starboard/doc/versioning.md
+++ b/src/starboard/doc/versioning.md
@@ -80,7 +80,7 @@
 
 ### The "Release Candidate" Starboard Version
 
-At any given time, zero or one versions of Starboard will be denoted as the
+At any given time, zero or more versions of Starboard will be denoted as the
 "release candidate" version, as defined by the
 `SB_RELEASE_CANDIDATE_API_VERSION` macro in `starboard/configuration.h`. The
 "release candidate" version is a set of API changes that have been considered
@@ -88,7 +88,8 @@
 through some stabilization and may become frozen as it currently is. But, be
 aware that it is possible that minor incompatible changes may be made to this
 version if an unexpected situation arises. `SB_RELEASE_CANDIDATE_API_VERSION` is
-not defined if there is no "release candidate" version.
+not defined if there is no "release candidate" version. Every API version
+greater than `SB_RELEASE_CANDIDATE_API_VERSION` but less than `SB_EXPERIMENTAL_API_VERSION` is also considered a release candidate.
 
 ### "Frozen" Starboard versions
 
diff --git a/src/starboard/key.h b/src/starboard/key.h
index e44b05f..9c5a6c1 100644
--- a/src/starboard/key.h
+++ b/src/starboard/key.h
@@ -208,7 +208,7 @@
   kSbKeyMediaRewind = 0xE3,
   kSbKeyMediaFastForward = 0xE4,
 
-#if SB_API_VESRION >= SB_COLOR_KEYCODES_API_VERSION
+#if SB_API_VERSION >= SB_COLOR_KEYCODES_API_VERSION
   // The colored keys found on most contemporary TV remotes.
   kSbKeyRed = 0x193,
   kSbKeyGreen = 0x194,
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi
index 9500981..dcc5f4d 100644
--- a/src/starboard/linux/shared/gyp_configuration.gypi
+++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -22,7 +22,6 @@
     #'starboard_path%': 'starboard/linux/x64x11',
 
     'in_app_dial%': 1,
-    'gl_type%': 'system_gles3',
 
     # This should have a default value in cobalt/base.gypi. See the comment
     # there for acceptable values for this variable.
diff --git a/src/starboard/nplb/memory_map_test.cc b/src/starboard/nplb/memory_map_test.cc
index 05ad242..2751e00 100644
--- a/src/starboard/nplb/memory_map_test.cc
+++ b/src/starboard/nplb/memory_map_test.cc
@@ -50,34 +50,44 @@
   EXPECT_TRUE(SbMemoryUnmap(memory, SB_MEMORY_PAGE_SIZE));
 }
 
-TEST(SbMemoryMapTest, DoesNotLeak) {
-  // Map 4x the amount of system memory (sequentially, not at once).
-  int64_t bytes_mapped = SbSystemGetTotalCPUMemory() / 4;
-  for (int64_t total_bytes_mapped = 0;
-       total_bytes_mapped < SbSystemGetTotalCPUMemory() * 3;
-       total_bytes_mapped += bytes_mapped) {
-    void* memory = SbMemoryMap(bytes_mapped, kSbMemoryMapProtectWrite, "test");
+// Disabled because it is too slow -- currently ~5 seconds on a Linux desktop
+// with lots of memory.
+TEST(SbMemoryMapTest, DISABLED_DoesNotLeak) {
+  const int64_t kIterations = 16;
+  const double kFactor = 1.25;
+  const size_t kSparseCommittedPages = 256;
+
+  const int64_t kBytesMappedPerIteration =
+      static_cast<int64_t>(SbSystemGetTotalCPUMemory() * kFactor) / kIterations;
+  const int64_t kMaxBytesMapped = kBytesMappedPerIteration * kIterations;
+
+  for (int64_t total_bytes_mapped = 0; total_bytes_mapped < kMaxBytesMapped;
+       total_bytes_mapped += kBytesMappedPerIteration) {
+    void* memory =
+        SbMemoryMap(kBytesMappedPerIteration, kSbMemoryMapProtectWrite, "test");
     ASSERT_NE(kFailed, memory);
 
     // If this is the last iteration of the loop, then force a page commit for
     // every single page.  For any other iteration, force a page commit for
-    // roughly 1000 of the pages.
+    // |kSparseCommittedPages|.
     bool last_iteration =
-        !(total_bytes_mapped + bytes_mapped < SbSystemGetTotalCPUMemory() * 4);
+        !(total_bytes_mapped + kBytesMappedPerIteration < kMaxBytesMapped);
     uint8_t* first_page = static_cast<uint8_t*>(memory);
     const size_t page_increment_factor =
         (last_iteration)
             ? size_t(1u)
-            : std::max(static_cast<size_t>(bytes_mapped /
-                                           (1000 * SB_MEMORY_PAGE_SIZE)),
+            : std::max(static_cast<size_t>(
+                           kBytesMappedPerIteration /
+                           (kSparseCommittedPages * SB_MEMORY_PAGE_SIZE)),
                        size_t(1u));
 
-    for (uint8_t* page = first_page; page < first_page + bytes_mapped;
+    for (uint8_t* page = first_page;
+         page < first_page + kBytesMappedPerIteration;
          page += SB_MEMORY_PAGE_SIZE * page_increment_factor) {
       *page = 0x55;
     }
 
-    EXPECT_TRUE(SbMemoryUnmap(memory, bytes_mapped));
+    EXPECT_TRUE(SbMemoryUnmap(memory, kBytesMappedPerIteration));
   }
 }
 
diff --git a/src/starboard/shared/linux/dev_input/dev_input.cc b/src/starboard/shared/linux/dev_input/dev_input.cc
index 27a6d10..10393dd 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.cc
+++ b/src/starboard/shared/linux/dev_input/dev_input.cc
@@ -1062,7 +1062,7 @@
         device_info->touchpad_position_state = kTouchPadPositionNone;
         if (touchpad_position_is_known) {
           // Touch point is released, report last known position as unpress.
-          input_vector.x = device_info->axis_value[ABS_MT_POSITION_X] * 2;
+          input_vector.x = device_info->axis_value[ABS_MT_POSITION_X];
           input_vector.y = device_info->axis_value[ABS_MT_POSITION_Y];
           return CreateTouchPadEvent(window_, kSbInputEventTypeUnpress, key,
                                      location, modifiers, input_vector);
@@ -1079,7 +1079,7 @@
       if (IsTouchpadPositionKnown(device_info)) {
         // For touchpads, the unit range is [-1..1]. Negative values for top
         // left.
-        input_vector.x = axis_value * 2;  // Touch are is twice as wide.
+        input_vector.x = axis_value;
         input_vector.y = device_info->axis_value[ABS_MT_POSITION_Y];
         return CreateTouchPadEvent(window_, type, key, location, modifiers,
                                    input_vector);
@@ -1096,7 +1096,7 @@
       device_info->touchpad_position_state |= kTouchPadPositionY;
       if (IsTouchpadPositionKnown(device_info)) {
         // For touchpads, the range is [-1..1]. Negative values for top left.
-        input_vector.x = device_info->axis_value[ABS_MT_POSITION_X] * 2;
+        input_vector.x = device_info->axis_value[ABS_MT_POSITION_X];
         input_vector.y = axis_value;
         return CreateTouchPadEvent(window_, type, key, location, modifiers,
                                    input_vector);
diff --git a/src/starboard/shared/starboard/thread_checker.h b/src/starboard/shared/starboard/thread_checker.h
index b1842ad..8c1b498 100644
--- a/src/starboard/shared/starboard/thread_checker.h
+++ b/src/starboard/shared/starboard/thread_checker.h
@@ -32,6 +32,8 @@
     SB_UNREFERENCED_PARAMETER(type);
   }
 
+  void Detach() {}
+
   bool CalledOnValidThread() const { return true; }
 };
 
@@ -48,6 +50,16 @@
       thread_id_ = kSbThreadInvalidId;
   }
 
+  // Detached the thread checker from its current thread.  The thread checker
+  // will re-attach to a thread when CalledOnValidThread() is called again.
+  // This essentially reset the thread checker to a status just like that it
+  // was created with 'kSetThreadIdOnFirstCheck'.
+  void Detach() {
+    // This is safe as when this function is called, it is expected that it
+    // won't be called on its current thread.
+    thread_id_ = kSbThreadInvalidId;
+  }
+
   bool CalledOnValidThread() const {
     SbThreadId current_thread_id = SbThreadGetId();
     SbThreadId stored_thread_id = SbAtomicNoBarrier_CompareAndSwap(
diff --git a/src/starboard/shared/uwp/application_uwp.cc b/src/starboard/shared/uwp/application_uwp.cc
index 718abd8..6ebcda9 100644
--- a/src/starboard/shared/uwp/application_uwp.cc
+++ b/src/starboard/shared/uwp/application_uwp.cc
@@ -109,23 +109,19 @@
 #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 std::string& uri_string) {
+  SB_LOG(INFO) << "Navigate to: [" << uri_string << "]";
   const size_t kMaxDeepLinkSize = 128 * 1024;
-  const std::size_t uri_size = uri_protocol_stripped.size();
+  const std::size_t uri_size = uri_string.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;
+  const int kBufferSize = static_cast<int>(uri_string.size()) + 1;
   char* deep_link = new char[kBufferSize];
   SB_DCHECK(deep_link);
-  SbStringCopy(deep_link, uri_protocol_stripped.c_str(), kBufferSize);
+  SbStringCopy(deep_link, uri_string.c_str(), kBufferSize);
 
   return std::unique_ptr<Application::Event>(
       new Application::Event(kSbEventTypeLink, deep_link,
@@ -300,6 +296,13 @@
       if (uri->SchemeName->Equals("youtube") ||
           uri->SchemeName->Equals("ms-xbl-07459769")) {
         std::string uri_string = sbwin32::platformStringToString(uri->RawUri);
+
+        // Strip the protocol from the uri.
+        size_t index = uri_string.find(':');
+        if (index != std::string::npos) {
+          uri_string = uri_string.substr(index + 1);
+        }
+
         ProcessDeepLinkUri(&uri_string);
       }
     } else if (args->Kind == ActivationKind::DialReceiver) {
@@ -312,11 +315,13 @@
           kDialParamPrefix + sbwin32::platformStringToString(arguments);
         ProcessDeepLinkUri(&uri_string);
       } else {
-        const char kYouTubeTVurl[] = "--url=https://www.youtube.com/tv/?";
+        const char kYouTubeTVurl[] = "--url=https://www.youtube.com/tv?";
         std::string activation_args =
             kYouTubeTVurl + sbwin32::platformStringToString(arguments);
         SB_DLOG(INFO) << "Dial Activation url: " << activation_args;
+        args_.push_back(GetArgvZero());
         args_.push_back(activation_args);
+        argv_.push_back(args_.front().c_str());
         argv_.push_back(args_.back().c_str());
         ApplicationUwp::Get()->SetCommandLine(static_cast<int>(argv_.size()),
           argv_.data());
@@ -469,11 +474,16 @@
   ThreadPoolTimer^ timer = ThreadPoolTimer::CreateTimer(
     ref new TimerElapsedHandler([this, timed_event](ThreadPoolTimer^ timer) {
       RunInMainThreadAsync([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);
+        // Even if the event is canceled, the callback can still fire.
+        // Thus, the existence of event in timer_event_map_ is used
+        // as a source of truth.
+        std::size_t number_erased = 0;
+        {
+          ScopedLock lock(mutex_);
+          number_erased = timer_event_map_.erase(timed_event->id);
+        }
+        if (number_erased > 0) {
+          timed_event->callback(timed_event->context);
         }
       });
     }), timespan);
diff --git a/src/starboard/shared/win32/audio_decoder.cc b/src/starboard/shared/win32/audio_decoder.cc
index 879202c..9a40207 100644
--- a/src/starboard/shared/win32/audio_decoder.cc
+++ b/src/starboard/shared/win32/audio_decoder.cc
@@ -117,14 +117,14 @@
   SB_DCHECK(thread_checker_.CalledOnValidThread());
 
   decoder_thread_.reset(nullptr);
-  decoder_impl_.reset(nullptr);
-  decoder_impl_ = AbstractWin32AudioDecoder::Create(
-      audio_codec_, GetStorageType(), GetSampleType(), audio_header_,
-      drm_system_);
-  decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
+  decoder_impl_->Reset();
+
   decoded_data_.Clear();
   stream_ended_ = false;
+  callback_scheduler_.reset(new CallbackScheduler());
   CancelPendingJobs();
+
+  decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
 }
 
 SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
diff --git a/src/starboard/shared/win32/audio_decoder.h b/src/starboard/shared/win32/audio_decoder.h
index 5bd335c..c0516d7 100644
--- a/src/starboard/shared/win32/audio_decoder.h
+++ b/src/starboard/shared/win32/audio_decoder.h
@@ -28,7 +28,6 @@
 #include "starboard/shared/win32/atomic_queue.h"
 #include "starboard/shared/win32/audio_decoder_thread.h"
 #include "starboard/shared/win32/media_common.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 
 namespace starboard {
 namespace shared {
@@ -63,10 +62,10 @@
 
   ::starboard::shared::starboard::ThreadChecker thread_checker_;
 
-  SbMediaAudioCodec audio_codec_;
-  SbMediaAudioHeader audio_header_;
-  SbDrmSystem drm_system_;
-  SbMediaAudioSampleType sample_type_;
+  const SbMediaAudioCodec audio_codec_;
+  const SbMediaAudioHeader audio_header_;
+  SbDrmSystem const drm_system_;
+  const SbMediaAudioSampleType sample_type_;
   bool stream_ended_;
 
   AtomicQueue<DecodedAudioPtr> decoded_data_;
diff --git a/src/starboard/shared/win32/audio_decoder_thread.cc b/src/starboard/shared/win32/audio_decoder_thread.cc
index 91d28fe..ce9bf33 100644
--- a/src/starboard/shared/win32/audio_decoder_thread.cc
+++ b/src/starboard/shared/win32/audio_decoder_thread.cc
@@ -34,9 +34,7 @@
     data_queue->pop_front();
 
     if (buff) {
-      const bool write_ok = audio_decoder->TryWrite(*buff);
-
-      if (!write_ok) {
+      if (!audio_decoder->TryWrite(buff)) {
         data_queue->push_front(buff);
         break;
       }
@@ -91,11 +89,16 @@
   std::deque<scoped_refptr<InputBuffer> > local_queue;
 
   while (!join_called()) {
-    TransferPendingInputTo(&local_queue);
-    bool work_done = !local_queue.empty();
+    if (local_queue.empty()) {
+      TransferPendingInputTo(&local_queue);
+    }
+    bool work_done = false;
     size_t number_written =
         WriteAsMuchAsPossible(&local_queue, win32_audio_decoder_);
-    processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
+    if (number_written > 0) {
+      processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
+      work_done = true;
+    }
 
     std::vector<DecodedAudioPtr> decoded_audio =
         ReadAllDecodedAudioSamples(win32_audio_decoder_);
diff --git a/src/starboard/shared/win32/audio_decoder_thread.h b/src/starboard/shared/win32/audio_decoder_thread.h
index f595ec4..131639b 100644
--- a/src/starboard/shared/win32/audio_decoder_thread.h
+++ b/src/starboard/shared/win32/audio_decoder_thread.h
@@ -27,7 +27,6 @@
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/simple_thread.h"
 #include "starboard/shared/win32/win32_audio_decoder.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 
 namespace starboard {
 namespace shared {
diff --git a/src/starboard/shared/win32/audio_sink.cc b/src/starboard/shared/win32/audio_sink.cc
index 7097582..1c735b2 100644
--- a/src/starboard/shared/win32/audio_sink.cc
+++ b/src/starboard/shared/win32/audio_sink.cc
@@ -12,259 +12,36 @@
 // 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 <algorithm>
 #include <limits>
-#include <sstream>
-#include <string>
+#include <vector>
 
+#include "starboard/condition_variable.h"
 #include "starboard/configuration.h"
 #include "starboard/log.h"
 #include "starboard/mutex.h"
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.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;
-}
-
-const int kMaxBuffersSubmittedPerLoop = 2;
-
-std::string GenerateThreadName() {
-  static int s_count = 0;
-  std::stringstream ss;
-  ss << "AudioOut_" << s_count++;
-  return ss.str();
-}
-}  // namespace.
-
 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;
-  }
-  void SetVolume(double volume) SB_OVERRIDE {
-    ScopedLock lock(mutex_);
-    volume_ = volume;
-  }
-
- 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_;
-  double volume_;
-};
-
-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),
-      volume_(1.0) {
-  // TODO: Check MaxFrequencyRatio
-  CHECK_HRESULT_OK(
-      type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
-                                          /*MaxFrequencyRatio = */ 1.0));
-
-  CHECK_HRESULT_OK(source_voice_->Stop(0));
-
-  std::string thread_name = GenerateThreadName();
-
-  audio_out_thread_ = SbThreadCreate(
-      0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
-      thread_name.c_str(), &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;
-  int queued_buffers = 0;
-  bool was_playing = false;  // The player starts out playing by default.
-  double current_volume = 1.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;
-    bool should_set_volume;
-    {
-      ScopedLock lock(mutex_);
-      is_playback_rate_zero = playback_rate_ == 0.0;
-      should_set_volume = current_volume != volume_;
-      current_volume = volume_;
-    }
-
-    if (should_set_volume) {
-      CHECK_HRESULT_OK(source_voice_->SetVolume(current_volume));
-    }
-
-    update_source_status_func_(&frames_in_buffer, &offset_in_frames,
-                               &is_playing, &is_eos_reached, context_);
-    if (is_playback_rate_zero) {
-      is_playing = false;
-    }
-
-    if (is_playing != was_playing) {
-      if (is_playing) {
-        CHECK_HRESULT_OK(source_voice_->Start(0));
-      } else {
-        CHECK_HRESULT_OK(source_voice_->Stop(0));
-      }
-    }
-    was_playing = is_playing;
-
-    // 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 ||
-        queued_buffers +
-            kMaxBuffersSubmittedPerLoop > XAUDIO2_MAX_QUEUED_BUFFERS) {
-      // 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;
-      // Note since we can submit up to two source buffers at a time,
-      // kMaxBuffersSubmittedPerLoop = 2.
-      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;
-    queued_buffers = voice_state.BuffersQueued;
-  }
-}
-
 namespace {
 
+using Microsoft::WRL::ComPtr;
+
+const int kMaxBuffersSubmittedPerLoop = 2;
+
+// Fails an SB_DCHECK if an HRESULT is not S_OK
+void CHECK_HRESULT_OK(HRESULT hr) {
+  SB_DCHECK(SUCCEEDED(hr)) << std::hex << hr;
+}
+
 WORD SampleTypeToFormatTag(SbMediaAudioSampleType type) {
   switch (type) {
     case kSbMediaAudioSampleTypeInt16:
@@ -289,10 +66,244 @@
   }
 }
 
-}  // namespace
+class XAudioAudioSinkType;
 
-XAudioAudioSinkType::XAudioAudioSinkType() {
+class XAudioAudioSink : public SbAudioSinkPrivate {
+ public:
+  XAudioAudioSink(XAudioAudioSinkType* type,
+                  IXAudio2SourceVoice* source_voice,
+                  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;
+  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;
+  }
+  void SetVolume(double volume) SB_OVERRIDE {
+    ScopedLock lock(mutex_);
+    volume_ = volume;
+  }
+  void Process();
+
+ private:
+  void SubmitSourceBuffer(int offset_in_frames, int count_frames);
+
+  XAudioAudioSinkType* const type_;
+  const SbAudioSinkUpdateSourceStatusFunc update_source_status_func_;
+  const SbAudioSinkConsumeFramesFunc consume_frame_func_;
+  void* const context_;
+
+  SbAudioSinkFrameBuffers frame_buffers_;
+  const int frame_buffers_size_in_frames_;
+  const 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_|.
+  Mutex mutex_;
+  double playback_rate_;
+  double volume_;
+  // The following variables are only used inside Process().  To keep it in the
+  // class simply to allow them to be kept between Process() calls.
+  int submited_frames_;
+  int samples_played_;
+  int queued_buffers_;
+  bool was_playing_;
+  double current_volume_;
+};
+
+class XAudioAudioSinkType : public SbAudioSinkPrivate::Type,
+                            private IXAudio2EngineCallback {
+ 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;
+
+ private:
+  // IXAudio2EngineCallback methods
+  // This function will be called periodically with an interval of ~10ms.
+  void OnProcessingPassStart() SB_OVERRIDE;
+  void OnProcessingPassEnd() SB_OVERRIDE {}
+  void OnCriticalError(HRESULT) SB_OVERRIDE {}
+
+  ComPtr<IXAudio2> x_audio2_;
+  IXAudio2MasteringVoice* mastering_voice_;
+
+  Mutex mutex_;
+  std::vector<XAudioAudioSink*> audio_sinks_to_add_;
+  std::vector<SbAudioSink> audio_sinks_to_delete_;
+  ConditionVariable sink_removed_signal_;
+  std::vector<XAudioAudioSink*> audio_sinks_on_xaudio_callbacks_;
+};
+
+XAudioAudioSink::XAudioAudioSink(
+    XAudioAudioSinkType* type,
+    IXAudio2SourceVoice* source_voice,
+    const WAVEFORMATEX& wfx,
+    SbAudioSinkFrameBuffers frame_buffers,
+    int frame_buffers_size_in_frames,
+    SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+    SbAudioSinkConsumeFramesFunc consume_frame_func,
+    void* context)
+    : type_(type),
+      source_voice_(source_voice),
+      update_source_status_func_(update_source_status_func),
+      consume_frame_func_(consume_frame_func),
+      context_(context),
+      frame_buffers_(frame_buffers),
+      frame_buffers_size_in_frames_(frame_buffers_size_in_frames),
+      wfx_(wfx),
+      playback_rate_(1.0),
+      volume_(1.0),
+      submited_frames_(0),
+      samples_played_(0),
+      queued_buffers_(0),
+      was_playing_(false),
+      current_volume_(1.0) {
+  CHECK_HRESULT_OK(source_voice_->Stop(0));
+}
+
+XAudioAudioSink::~XAudioAudioSink() {
+  source_voice_->DestroyVoice();
+}
+
+bool XAudioAudioSink::IsType(Type* type) {
+  return type_ == type;
+}
+
+void XAudioAudioSink::Process() {
+  int frames_in_buffer, offset_in_frames;
+  bool is_playing, is_eos_reached;
+  bool is_playback_rate_zero = false;
+  bool should_set_volume = false;
+
+  // This function is run on the XAudio thread and shouldn't be blocked.
+  if (mutex_.AcquireTry()) {
+    is_playback_rate_zero = playback_rate_ == 0.0;
+    should_set_volume = current_volume_ != volume_;
+    current_volume_ = volume_;
+    mutex_.Release();
+  }
+
+  if (should_set_volume) {
+    CHECK_HRESULT_OK(source_voice_->SetVolume(current_volume_));
+  }
+
+  update_source_status_func_(&frames_in_buffer, &offset_in_frames, &is_playing,
+                             &is_eos_reached, context_);
+  if (is_playback_rate_zero) {
+    is_playing = false;
+  }
+
+  if (is_playing != was_playing_) {
+    if (is_playing) {
+      CHECK_HRESULT_OK(source_voice_->Start(0));
+    } else {
+      CHECK_HRESULT_OK(source_voice_->Stop(0));
+    }
+  }
+  was_playing_ = is_playing;
+
+  // 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) {
+    return;
+  }
+  int unsubmitted_frames = frames_in_buffer - submited_frames_;
+  int unsubmitted_start =
+      (offset_in_frames + submited_frames_) % frame_buffers_size_in_frames_;
+  if (unsubmitted_frames == 0 || queued_buffers_ + kMaxBuffersSubmittedPerLoop >
+                                     XAUDIO2_MAX_QUEUED_BUFFERS) {
+    // 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;
+    // Note since we can submit up to two source buffers at a time,
+    // kMaxBuffersSubmittedPerLoop = 2.
+    SubmitSourceBuffer(unsubmitted_start, count_tail_frames);
+    SubmitSourceBuffer(0, unsubmitted_frames - count_tail_frames);
+  }
+  submited_frames_ = frames_in_buffer;
+
+  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_);
+  submited_frames_ -= consumed_frames_int;
+  samples_played_ = voice_state.SamplesPlayed;
+  queued_buffers_ = voice_state.BuffersQueued;
+}
+
+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));
+}
+
+XAudioAudioSinkType::XAudioAudioSinkType() : sink_removed_signal_(mutex_) {
   CHECK_HRESULT_OK(XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR));
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  XAUDIO2_DEBUG_CONFIGURATION debug_config = {};
+  debug_config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS |
+                           XAUDIO2_LOG_INFO | XAUDIO2_LOG_DETAIL |
+                           XAUDIO2_LOG_TIMING | XAUDIO2_LOG_LOCKS;
+  debug_config.LogThreadID = TRUE;
+  debug_config.LogFileline = TRUE;
+  debug_config.LogFunctionName = TRUE;
+  debug_config.LogTiming = TRUE;
+  x_audio2_->SetDebugConfiguration(&debug_config, NULL);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+  x_audio2_->RegisterForCallbacks(this);
   CHECK_HRESULT_OK(x_audio2_->CreateMasteringVoice(&mastering_voice_));
 }
 
@@ -321,11 +332,61 @@
   wfx.nBlockAlign = static_cast<WORD>((channels * wfx.wBitsPerSample) / 8);
   wfx.cbSize = 0;
 
-  return new XAudioAudioSink(
-      this, wfx, frame_buffers, frame_buffers_size_in_frames,
+  IXAudio2SourceVoice* source_voice;
+  CHECK_HRESULT_OK(x_audio2_->CreateSourceVoice(&source_voice, &wfx,
+                                                XAUDIO2_VOICE_NOPITCH, 1.f));
+
+  XAudioAudioSink* audio_sink = new XAudioAudioSink(
+      this, source_voice, wfx, frame_buffers, frame_buffers_size_in_frames,
       update_source_status_func, consume_frames_func, context);
+
+  ScopedLock lock(mutex_);
+  audio_sinks_to_add_.push_back(audio_sink);
+  return audio_sink;
 }
 
+void XAudioAudioSinkType::Destroy(SbAudioSink audio_sink) {
+  if (audio_sink == kSbAudioSinkInvalid) {
+    return;
+  }
+  if (!IsValid(audio_sink)) {
+    SB_LOG(WARNING) << "audio_sink is invalid.";
+    return;
+  }
+  ScopedLock lock(mutex_);
+  audio_sinks_to_delete_.push_back(audio_sink);
+  while (!audio_sinks_to_delete_.empty()) {
+    sink_removed_signal_.Wait();
+  }
+  delete audio_sink;
+}
+
+void XAudioAudioSinkType::OnProcessingPassStart() {
+  if (mutex_.AcquireTry()) {
+    if (!audio_sinks_to_add_.empty()) {
+      audio_sinks_on_xaudio_callbacks_.insert(
+          audio_sinks_on_xaudio_callbacks_.end(), audio_sinks_to_add_.begin(),
+          audio_sinks_to_add_.end());
+      audio_sinks_to_add_.clear();
+    }
+    if (!audio_sinks_to_delete_.empty()) {
+      for (auto sink : audio_sinks_to_delete_) {
+        audio_sinks_on_xaudio_callbacks_.erase(
+            std::find(audio_sinks_on_xaudio_callbacks_.begin(),
+                      audio_sinks_on_xaudio_callbacks_.end(), sink));
+      }
+      audio_sinks_to_delete_.clear();
+      sink_removed_signal_.Broadcast();
+    }
+    mutex_.Release();
+  }
+
+  for (auto sink : audio_sinks_on_xaudio_callbacks_) {
+    sink->Process();
+  }
+}
+
+}  // namespace
 }  // namespace win32
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/shared/win32/audio_sink.h b/src/starboard/shared/win32/audio_sink.h
deleted file mode 100644
index f8820f7..0000000
--- a/src/starboard/shared/win32/audio_sink.h
+++ /dev/null
@@ -1,67 +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 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/decode_target_internal.cc b/src/starboard/shared/win32/decode_target_internal.cc
index 8188621..125b186 100644
--- a/src/starboard/shared/win32/decode_target_internal.cc
+++ b/src/starboard/shared/win32/decode_target_internal.cc
@@ -16,6 +16,7 @@
 
 #include <D3D11.h>
 #include <Mfidl.h>
+#include <Mfobjects.h>
 #include <wrl\client.h>  // For ComPtr.
 
 #include "starboard/configuration.h"
@@ -48,11 +49,15 @@
 
 SbDecodeTargetPrivate::SbDecodeTargetPrivate(VideoFramePtr f) : frame(f) {
   SbMemorySet(&info, 0, sizeof(info));
-  ComPtr<IMFMediaBuffer> media_buffer =
-      static_cast<IMFMediaBuffer*>(frame->native_texture());
+
+  ComPtr<IMFSample> sample = static_cast<IMFSample*>(frame->native_texture());
+
+  ComPtr<IMFMediaBuffer> media_buffer;
+  HRESULT hr = sample->GetBufferByIndex(0, &media_buffer);
+  CheckResult(hr);
 
   ComPtr<IMFDXGIBuffer> dxgi_buffer;
-  HRESULT hr = media_buffer.As(&dxgi_buffer);
+  hr = media_buffer.As(&dxgi_buffer);
   CheckResult(hr);
   SB_DCHECK(dxgi_buffer.Get());
 
diff --git a/src/starboard/shared/win32/decrypting_decoder.cc b/src/starboard/shared/win32/decrypting_decoder.cc
new file mode 100644
index 0000000..0b25085
--- /dev/null
+++ b/src/starboard/shared/win32/decrypting_decoder.cc
@@ -0,0 +1,349 @@
+// 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/decrypting_decoder.h"
+
+#include <algorithm>
+#include <numeric>
+
+#include "starboard/byte_swap.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/media_foundation_utils.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+namespace {
+
+ComPtr<IMFSample> CreateSample(const void* data,
+                               int size,
+                               int64_t win32_timestamp) {
+  ComPtr<IMFMediaBuffer> buffer;
+  HRESULT hr = MFCreateMemoryBuffer(size, &buffer);
+  CheckResult(hr);
+
+  BYTE* buffer_ptr;
+  hr = buffer->Lock(&buffer_ptr, 0, 0);
+  CheckResult(hr);
+
+  SbMemoryCopy(buffer_ptr, data, size);
+
+  hr = buffer->Unlock();
+  CheckResult(hr);
+
+  hr = buffer->SetCurrentLength(size);
+  CheckResult(hr);
+
+  ComPtr<IMFSample> input;
+  hr = MFCreateSample(&input);
+  CheckResult(hr);
+
+  hr = input->AddBuffer(buffer.Get());
+  CheckResult(hr);
+
+  // sample time is in 100 nanoseconds.
+  input->SetSampleTime(win32_timestamp);
+  return input;
+}
+
+void AttachDrmDataToSample(ComPtr<IMFSample> sample,
+                           int sample_size,
+                           const uint8_t* key_id,
+                           int key_id_size,
+                           const uint8_t* iv,
+                           int iv_size,
+                           const SbDrmSubSampleMapping* subsample_mapping,
+                           int subsample_count) {
+  if (iv_size == 16 && SbMemoryIsZero(iv + 8, 8)) {
+    // For iv that is 16 bytes long but the the last 8 bytes is 0, we treat
+    // it as an 8 bytes iv.
+    iv_size = 8;
+  }
+  sample->SetBlob(MFSampleExtension_Encryption_SampleID,
+                  reinterpret_cast<const UINT8*>(iv),
+                  static_cast<UINT32>(iv_size));
+  SB_DCHECK(key_id_size == sizeof(GUID));
+  GUID guid = *reinterpret_cast<const GUID*>(key_id);
+
+  guid.Data1 = SbByteSwapU32(guid.Data1);
+  guid.Data2 = SbByteSwapU16(guid.Data2);
+  guid.Data3 = SbByteSwapU16(guid.Data3);
+
+  sample->SetGUID(MFSampleExtension_Content_KeyID, guid);
+
+  SB_DCHECK(sizeof(DWORD) * 2 == sizeof(SbDrmSubSampleMapping));
+
+  SbDrmSubSampleMapping default_subsample = {0, sample_size};
+  if (subsample_count == 0) {
+    subsample_mapping = &default_subsample;
+    subsample_count = 1;
+  }
+  sample->SetBlob(
+      MFSampleExtension_Encryption_SubSampleMappingSplit,
+      reinterpret_cast<const UINT8*>(subsample_mapping),
+      static_cast<UINT32>(subsample_count * sizeof(SbDrmSubSampleMapping)));
+}
+
+}  // namespace
+
+DecryptingDecoder::DecryptingDecoder(const std::string& type,
+                                     CLSID clsid,
+                                     SbDrmSystem drm_system)
+    : type_(type), decoder_(clsid) {
+  drm_system_ = static_cast<SbDrmSystemPlayready*>(drm_system);
+}
+
+DecryptingDecoder::~DecryptingDecoder() {
+  Reset();
+}
+
+bool DecryptingDecoder::TryWriteInputBuffer(
+    const scoped_refptr<InputBuffer>& input_buffer,
+    int bytes_to_skip_in_sample) {
+  SB_DCHECK(input_buffer);
+  SB_DCHECK(bytes_to_skip_in_sample > 0);
+
+  ComPtr<IMFSample> input_sample;
+
+  const SbDrmSampleInfo* drm_info = input_buffer->drm_info();
+  const uint8_t* key_id = NULL;
+  int key_id_size = 0;
+  bool encrypted = false;
+
+  if (drm_info != NULL && drm_info->identifier_size == 16 &&
+      (drm_info->initialization_vector_size == 8 ||
+       drm_info->initialization_vector_size == 16)) {
+    key_id = drm_info->identifier;
+    key_id_size = drm_info->identifier_size;
+    encrypted = true;
+  }
+
+  if (input_buffer == last_input_buffer_) {
+    SB_DCHECK(last_input_sample_);
+    input_sample = last_input_sample_;
+  } else {
+    if (input_buffer->size() < bytes_to_skip_in_sample) {
+      SB_NOTREACHED();
+      return false;
+    }
+
+    const void* data = input_buffer->data() + bytes_to_skip_in_sample;
+    int size = input_buffer->size() - bytes_to_skip_in_sample;
+
+    std::int64_t win32_timestamp = ConvertToWin32Time(input_buffer->pts());
+    const uint8_t* iv = NULL;
+    int iv_size = 0;
+    const SbDrmSubSampleMapping* subsample_mapping = NULL;
+    int subsample_count = 0;
+
+    if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
+      if (bytes_to_skip_in_sample != 0) {
+        if (drm_info->subsample_count != 0 && drm_info->subsample_count != 1) {
+          return false;
+        }
+        if (drm_info->subsample_count == 1) {
+          if (drm_info->subsample_mapping[0].clear_byte_count !=
+              bytes_to_skip_in_sample) {
+            return false;
+          }
+        }
+      } else {
+        subsample_mapping = drm_info->subsample_mapping;
+        subsample_count = drm_info->subsample_count;
+      }
+
+      iv = drm_info->initialization_vector;
+      iv_size = drm_info->initialization_vector_size;
+    }
+
+    // MFSampleExtension_CleanPoint is a key-frame for the video + audio. It is
+    // not set here because the win32 system is smart enough to figure this out.
+    // It will probably be totally ok to not set this at all. Resolution: If
+    // there are problems with win32 video decoding, come back to this and see
+    // if setting this will fix it. THis will be used if
+    // SbMediaVideoSampleInfo::is_key_frame is true inside of the this function
+    // (which will receive an InputBuffer).
+    input_sample = CreateSample(data, size, win32_timestamp);
+
+    if (encrypted) {
+      AttachDrmDataToSample(input_sample, size, key_id, key_id_size, iv,
+                            iv_size, subsample_mapping, subsample_count);
+    }
+    last_input_buffer_ = input_buffer;
+    last_input_sample_ = input_sample;
+  }
+
+  if (encrypted) {
+    if (!decryptor_) {
+      if (decoder_.draining()) {
+        return false;
+      }
+      if (!decoder_.drained()) {
+        decoder_.Drain();
+        return false;
+      }
+      decoder_.ResetFromDrained();
+      scoped_refptr<SbDrmSystemPlayready::License> license =
+          drm_system_->GetLicense(key_id, key_id_size);
+      if (license && license->usable()) {
+        decryptor_.reset(new MediaTransform(license->decryptor()));
+        ActivateDecryptor();
+      }
+    }
+    if (!decryptor_) {
+      SB_NOTREACHED();
+      return false;
+    }
+  }
+
+  if (encrypted) {
+    return decryptor_->TryWrite(input_sample);
+  }
+  return decoder_.TryWrite(input_sample);
+}
+
+bool DecryptingDecoder::ProcessAndRead(ComPtr<IMFSample>* output,
+                                       ComPtr<IMFMediaType>* new_type) {
+  bool did_something = false;
+
+  *output = decoder_.TryRead(new_type);
+  did_something = *output != NULL;
+
+  if (decryptor_) {
+    if (!pending_decryptor_output_) {
+      ComPtr<IMFMediaType> ignored_type;
+      pending_decryptor_output_ = decryptor_->TryRead(&ignored_type);
+      did_something = pending_decryptor_output_ != NULL;
+    }
+
+    if (pending_decryptor_output_) {
+      if (decoder_.TryWrite(pending_decryptor_output_)) {
+        pending_decryptor_output_.Reset();
+        did_something = true;
+      }
+    }
+
+    if (decryptor_->drained() && !decoder_.draining() && !decoder_.drained()) {
+      decoder_.Drain();
+      did_something = true;
+    }
+  }
+
+  return did_something;
+}
+
+void DecryptingDecoder::Drain() {
+  if (decryptor_) {
+    decryptor_->Drain();
+  } else {
+    decoder_.Drain();
+  }
+}
+
+void DecryptingDecoder::ActivateDecryptor() {
+  SB_DCHECK(decryptor_);
+
+  ComPtr<IMFMediaType> decoder_output_type = decoder_.GetCurrentOutputType();
+  decryptor_->SetInputType(decoder_.GetCurrentInputType());
+
+  GUID original_sub_type;
+  decoder_output_type->GetGUID(MF_MT_SUBTYPE, &original_sub_type);
+
+  // Ensure that the decryptor and the decoder agrees on the protection of
+  // samples transferred between them.
+  ComPtr<IMFSampleProtection> decryption_sample_protection =
+      decryptor_->GetSampleProtection();
+  SB_DCHECK(decryption_sample_protection);
+
+  DWORD decryption_protection_version;
+  HRESULT hr = decryption_sample_protection->GetOutputProtectionVersion(
+      &decryption_protection_version);
+  CheckResult(hr);
+
+  ComPtr<IMFSampleProtection> decoder_sample_protection =
+      decoder_.GetSampleProtection();
+  SB_DCHECK(decoder_sample_protection);
+
+  DWORD decoder_protection_version;
+  hr = decoder_sample_protection->GetInputProtectionVersion(
+      &decoder_protection_version);
+  CheckResult(hr);
+
+  DWORD protection_version =
+      std::min(decoder_protection_version, decryption_protection_version);
+  if (protection_version < SAMPLE_PROTECTION_VERSION_RC4) {
+    SB_NOTREACHED();
+    return;
+  }
+
+  BYTE* cert_data = NULL;
+  DWORD cert_data_size = 0;
+
+  hr = decoder_sample_protection->GetProtectionCertificate(
+      protection_version, &cert_data, &cert_data_size);
+  CheckResult(hr);
+
+  BYTE* crypt_seed = NULL;
+  DWORD crypt_seed_size = 0;
+  hr = decryption_sample_protection->InitOutputProtection(
+      protection_version, 0, cert_data, cert_data_size, &crypt_seed,
+      &crypt_seed_size);
+  CheckResult(hr);
+
+  hr = decoder_sample_protection->InitInputProtection(
+      protection_version, 0, crypt_seed, crypt_seed_size);
+  CheckResult(hr);
+
+  CoTaskMemFree(cert_data);
+  CoTaskMemFree(crypt_seed);
+
+  // Ensure that the input type of the decoder is the output type of the
+  // decryptor.
+  ComPtr<IMFMediaType> decoder_input_type;
+  std::vector<ComPtr<IMFMediaType>> decryptor_output_types =
+      decryptor_->GetAvailableOutputTypes();
+  SB_DCHECK(!decryptor_output_types.empty());
+
+  decryptor_->SetOutputType(decryptor_output_types[0]);
+  decoder_.SetInputType(decryptor_output_types[0]);
+
+  std::vector<ComPtr<IMFMediaType>> decoder_output_types =
+      decoder_.GetAvailableOutputTypes();
+  for (auto output_type : decoder_output_types) {
+    GUID sub_type;
+    output_type->GetGUID(MF_MT_SUBTYPE, &sub_type);
+    if (IsEqualGUID(sub_type, original_sub_type)) {
+      decoder_.SetOutputType(output_type);
+      return;
+    }
+  }
+}
+
+void DecryptingDecoder::Reset() {
+  if (decryptor_) {
+    decryptor_->Reset();
+  }
+  decoder_.Reset();
+
+  last_input_buffer_ = nullptr;
+  last_input_sample_ = nullptr;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/decrypting_decoder.h b/src/starboard/shared/win32/decrypting_decoder.h
new file mode 100644
index 0000000..99555fe
--- /dev/null
+++ b/src/starboard/shared/win32/decrypting_decoder.h
@@ -0,0 +1,85 @@
+// 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_DECRYPTING_DECODER_H_
+#define STARBOARD_SHARED_WIN32_DECRYPTING_DECODER_H_
+
+#include <D3D11.h>
+#include <mfapi.h>
+#include <mferror.h>
+#include <mfidl.h>
+#include <wrl\client.h>
+
+#include <string>
+#include <vector>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
+#include "starboard/media.h"
+#include "starboard/shared/win32/drm_system_playready.h"
+#include "starboard/shared/win32/media_common.h"
+#include "starboard/shared/win32/media_transform.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// This class maintains a MediaTransform based decoding stream.  When the
+// stream is encrypted, it also contains a MediaTransform based decryptor and
+// manages the interaction between the decryptor and the decoder.
+class DecryptingDecoder {
+ public:
+  DecryptingDecoder(const std::string& type,
+                    CLSID clsid,
+                    SbDrmSystem drm_system);
+  ~DecryptingDecoder();
+
+  MediaTransform& GetDecoder() { return decoder_; }
+
+  bool TryWriteInputBuffer(const scoped_refptr<InputBuffer>& input_buffer,
+                           int bytes_to_skip_in_sample);
+
+  // Return true if there is any internal actions succeeded, this implies that
+  // the caller can call this function again to process further.
+  // |output| contains the decrypted and decoded output if there is any.
+  // |new_type| contains the new output type in case the output type of the
+  // decoding stream is changed.
+  bool ProcessAndRead(ComPtr<IMFSample>* output,
+                      ComPtr<IMFMediaType>* new_type);
+  void Drain();
+  void Reset();
+
+ private:
+  void ActivateDecryptor();
+
+  // TODO: Clarify the thread pattern of this class.
+  const std::string type_;  // For debugging purpose.
+  SbDrmSystemPlayready* drm_system_;
+
+  scoped_ptr<MediaTransform> decryptor_;
+  MediaTransform decoder_;
+
+  scoped_refptr<InputBuffer> last_input_buffer_;
+  ComPtr<IMFSample> last_input_sample_;
+
+  ComPtr<IMFSample> pending_decryptor_output_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_DECRYPTING_DECODER_H_
diff --git a/src/starboard/shared/win32/drm_create_system.cc b/src/starboard/shared/win32/drm_create_system.cc
new file mode 100644
index 0000000..332a7e2
--- /dev/null
+++ b/src/starboard/shared/win32/drm_create_system.cc
@@ -0,0 +1,37 @@
+// 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/drm.h"
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/drm_system_playready.h"
+#include "starboard/string.h"
+
+SbDrmSystem SbDrmCreateSystem(
+    const char* key_system,
+    void* context,
+    SbDrmSessionUpdateRequestFunc update_request_callback,
+    SbDrmSessionUpdatedFunc session_updated_callback,
+    SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback) {
+  using ::starboard::shared::win32::SbDrmSystemPlayready;
+
+  if (SbStringCompareAll(key_system, "com.youtube.playready") != 0) {
+    SB_DLOG(WARNING) << "Invalid key system " << key_system;
+    return kSbDrmSystemInvalid;
+  }
+
+  return new SbDrmSystemPlayready(context, update_request_callback,
+                                  session_updated_callback,
+                                  key_statuses_changed_callback);
+}
diff --git a/src/starboard/shared/win32/drm_system_playready.cc b/src/starboard/shared/win32/drm_system_playready.cc
new file mode 100644
index 0000000..8856937
--- /dev/null
+++ b/src/starboard/shared/win32/drm_system_playready.cc
@@ -0,0 +1,221 @@
+// 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/drm_system_playready.h"
+
+#include <sstream>
+
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+namespace {
+
+#if defined(COBALT_BUILD_TYPE_GOLD)
+const bool kEnablePlayreadyLog = false;
+#else  // defined(COBALT_BUILD_TYPE_GOLD)
+const bool kEnablePlayreadyLog = true;
+#endif  // defined(COBALT_BUILD_TYPE_GOLD)
+
+std::string GetHexRepresentation(const GUID& guid) {
+  const char kHex[] = "0123456789ABCDEF";
+
+  std::stringstream ss;
+  const uint8_t* binary = reinterpret_cast<const uint8_t*>(&guid);
+  for (size_t i = 0; i < sizeof(guid); ++i) {
+    if (i != 0) {
+      ss << ' ';
+    }
+    ss << kHex[binary[i] / 16] << kHex[binary[i] % 16];
+  }
+
+  return ss.str();
+}
+
+}  // namespace
+
+SbDrmSystemPlayready::SbDrmSystemPlayready(
+    void* context,
+    SbDrmSessionUpdateRequestFunc session_update_request_callback,
+    SbDrmSessionUpdatedFunc session_updated_callback,
+    SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback)
+    : context_(context),
+      session_update_request_callback_(session_update_request_callback),
+      session_updated_callback_(session_updated_callback),
+      key_statuses_changed_callback_(key_statuses_changed_callback),
+      current_session_id_(1) {
+  SB_DCHECK(session_update_request_callback);
+  SB_DCHECK(session_updated_callback);
+  SB_DCHECK(key_statuses_changed_callback);
+}
+
+SbDrmSystemPlayready::~SbDrmSystemPlayready() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void SbDrmSystemPlayready::GenerateSessionUpdateRequest(
+    int ticket,
+    const char* type,
+    const void* initialization_data,
+    int initialization_data_size) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (SbStringCompareAll("cenc", type) != 0) {
+    SB_NOTREACHED() << "Invalid initialization data type " << type;
+    return;
+  }
+
+  std::string session_id = GenerateAndAdvanceSessionId();
+  scoped_refptr<License> license =
+      License::Create(initialization_data, initialization_data_size);
+  const std::string& challenge = license->license_challenge();
+  if (challenge.empty()) {
+    SB_NOTREACHED();
+    return;
+  }
+
+  SB_LOG_IF(INFO, kEnablePlayreadyLog)
+      << "Send challenge for key id " << GetHexRepresentation(license->key_id())
+      << " in session " << session_id;
+
+  session_update_request_callback_(this, context_, ticket, session_id.c_str(),
+                                   static_cast<int>(session_id.size()),
+                                   challenge.c_str(),
+                                   static_cast<int>(challenge.size()), NULL);
+  pending_requests_[session_id] = license;
+}
+
+void SbDrmSystemPlayready::UpdateSession(int ticket,
+                                         const void* key,
+                                         int key_size,
+                                         const void* session_id,
+                                         int session_id_size) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  std::string session_id_copy(static_cast<const char*>(session_id),
+                              session_id_size);
+  auto iter = pending_requests_.find(session_id_copy);
+  SB_DCHECK(iter != pending_requests_.end());
+  if (iter == pending_requests_.end()) {
+    SB_NOTREACHED() << "Invalid session id " << session_id_copy;
+    return;
+  }
+  iter->second->UpdateLicense(key, key_size);
+
+  SB_DCHECK(iter->second->usable());
+  if (iter->second->usable()) {
+    SB_LOG_IF(INFO, kEnablePlayreadyLog)
+        << "Successfully add key for key id "
+        << GetHexRepresentation(iter->second->key_id()) << " in session "
+        << session_id_copy;
+    {
+      ScopedLock lock(mutex_);
+      successful_requests_[iter->first] = iter->second;
+    }
+    session_updated_callback_(this, context_, ticket, session_id,
+                              session_id_size, true);
+
+    GUID key_id = iter->second->key_id();
+    SbDrmKeyId drm_key_id;
+    SB_DCHECK(sizeof(drm_key_id.identifier) >= sizeof(key_id));
+    SbMemoryCopy(&drm_key_id, &key_id, sizeof(key_id));
+    drm_key_id.identifier_size = sizeof(key_id);
+    SbDrmKeyStatus key_status = kSbDrmKeyStatusUsable;
+    key_statuses_changed_callback_(this, context_, session_id, session_id_size,
+                                   1, &drm_key_id, &key_status);
+  } else {
+    SB_LOG_IF(INFO, kEnablePlayreadyLog)
+        << "Failed to add key for session " << session_id_copy;
+    session_updated_callback_(this, context_, ticket, session_id,
+                              session_id_size, false);
+  }
+  pending_requests_.erase(iter);
+}
+
+void SbDrmSystemPlayready::CloseSession(const void* session_id,
+                                        int session_id_size) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  key_statuses_changed_callback_(this, context_, session_id, session_id_size, 0,
+                                 nullptr, nullptr);
+
+  std::string session_id_copy(static_cast<const char*>(session_id),
+                              session_id_size);
+  pending_requests_.erase(session_id_copy);
+
+  ScopedLock lock(mutex_);
+  successful_requests_.erase(session_id_copy);
+}
+
+SbDrmSystemPrivate::DecryptStatus SbDrmSystemPlayready::Decrypt(
+    InputBuffer* buffer) {
+  const SbDrmSampleInfo* drm_info = buffer->drm_info();
+
+  if (drm_info == NULL || drm_info->initialization_vector_size == 0) {
+    return kSuccess;
+  }
+
+  GUID key_id;
+  if (drm_info->identifier_size != sizeof(key_id)) {
+    return kRetry;
+  }
+  key_id = *reinterpret_cast<const GUID*>(drm_info->identifier);
+
+  ScopedLock lock(mutex_);
+  for (auto& item : successful_requests_) {
+    if (item.second->key_id() == key_id) {
+      return kSuccess;
+    }
+  }
+
+  return kRetry;
+}
+
+scoped_refptr<SbDrmSystemPlayready::License> SbDrmSystemPlayready::GetLicense(
+    const uint8_t* key_id,
+    int key_id_size) {
+  GUID key_id_copy;
+  if (key_id_size != sizeof(key_id_copy)) {
+    return NULL;
+  }
+  key_id_copy = *reinterpret_cast<const GUID*>(key_id);
+
+  ScopedLock lock(mutex_);
+
+  for (auto& item : successful_requests_) {
+    if (item.second->key_id() == key_id_copy) {
+      return item.second;
+    }
+  }
+
+  return NULL;
+}
+
+std::string SbDrmSystemPlayready::GenerateAndAdvanceSessionId() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  std::stringstream ss;
+  ss << current_session_id_;
+  ++current_session_id_;
+  return ss.str();
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/drm_system_playready.h b/src/starboard/shared/win32/drm_system_playready.h
new file mode 100644
index 0000000..48d608e
--- /dev/null
+++ b/src/starboard/shared/win32/drm_system_playready.h
@@ -0,0 +1,102 @@
+// 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_DRM_SYSTEM_PLAYREADY_H_
+#define STARBOARD_SHARED_WIN32_DRM_SYSTEM_PLAYREADY_H_
+
+#include <mfapi.h>
+#include <mfidl.h>
+#include <wrl.h>
+#include <wrl/client.h>
+
+#include <map>
+#include <string>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/drm/drm_system_internal.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// Adapts PlayReady decryption module to Starboard's |SbDrmSystem|.
+class SbDrmSystemPlayready : public SbDrmSystemPrivate {
+ public:
+  class License : public RefCountedThreadSafe<License> {
+   public:
+    static scoped_refptr<License> Create(const void* initialization_data,
+                                         int initialization_data_size);
+
+    virtual ~License() {}
+
+    virtual GUID key_id() const = 0;
+    virtual bool usable() const = 0;
+    virtual std::string license_challenge() const = 0;
+    virtual Microsoft::WRL::ComPtr<IMFTransform> decryptor() = 0;
+    virtual void UpdateLicense(const void* license, int license_size) = 0;
+  };
+
+  SbDrmSystemPlayready(
+      void* context,
+      SbDrmSessionUpdateRequestFunc session_update_request_callback,
+      SbDrmSessionUpdatedFunc session_updated_callback,
+      SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback);
+
+  ~SbDrmSystemPlayready() SB_OVERRIDE;
+
+  // From |SbDrmSystemPrivate|.
+  void GenerateSessionUpdateRequest(int ticket,
+                                    const char* type,
+                                    const void* initialization_data,
+                                    int initialization_data_size) SB_OVERRIDE;
+
+  void UpdateSession(int ticket,
+                     const void* key,
+                     int key_size,
+                     const void* session_id,
+                     int session_id_size) SB_OVERRIDE;
+
+  void CloseSession(const void* session_id, int session_id_size) SB_OVERRIDE;
+
+  DecryptStatus Decrypt(InputBuffer* buffer) SB_OVERRIDE;
+
+  // Used by audio and video decoders to retrieve the decryptors.
+  scoped_refptr<License> GetLicense(const uint8_t* key_id, int key_id_size);
+
+ private:
+  std::string GenerateAndAdvanceSessionId();
+
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
+  void* context_;
+  SbDrmSessionUpdateRequestFunc session_update_request_callback_;
+  SbDrmSessionUpdatedFunc session_updated_callback_;
+  SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback_;
+  int current_session_id_;
+
+  std::map<std::string, scoped_refptr<License> > pending_requests_;
+
+  // |successful_requests_| can be accessed from more than one thread.  Guard
+  // it by a mutex.
+  Mutex mutex_;
+  std::map<std::string, scoped_refptr<License> > successful_requests_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_DRM_SYSTEM_PLAYREADY_H_
diff --git a/src/starboard/shared/win32/error_utils.h b/src/starboard/shared/win32/error_utils.h
index 173dd53..b3761d9 100644
--- a/src/starboard/shared/win32/error_utils.h
+++ b/src/starboard/shared/win32/error_utils.h
@@ -1,59 +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.

-

-// Not breaking these functions up because however one is implemented, the

-// others should be implemented similarly.

-

-#ifndef STARBOARD_SHARED_WIN32_ERROR_UTILS_H_

-#define STARBOARD_SHARED_WIN32_ERROR_UTILS_H_

-

-#include <windows.h>

-

-#include <iostream>

-#include <string>

-

-#include "starboard/log.h"

-

-namespace starboard {

-namespace shared {

-namespace win32 {

-

-class Win32ErrorCode {

- public:

-  explicit Win32ErrorCode(DWORD error_code) : error_code_(error_code) {}

-

-  HRESULT GetHRESULT() const { return HRESULT_FROM_WIN32(error_code_); }

-

- private:

-  DWORD error_code_;

-};

-

-std::ostream& operator<<(std::ostream& os, const Win32ErrorCode& error_code);

-

-// Checks for system errors and logs a human-readable error if GetLastError()

-// returns an error code. Noops on non-debug builds.

-void DebugLogWinError();

-

-std::string HResultToString(HRESULT hr);

-

+// 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.
+
+// Not breaking these functions up because however one is implemented, the
+// others should be implemented similarly.
+
+#ifndef STARBOARD_SHARED_WIN32_ERROR_UTILS_H_
+#define STARBOARD_SHARED_WIN32_ERROR_UTILS_H_
+
+#include <windows.h>
+
+#include <iostream>
+#include <string>
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class Win32ErrorCode {
+ public:
+  explicit Win32ErrorCode(DWORD error_code) : error_code_(error_code) {}
+
+  HRESULT GetHRESULT() const { return HRESULT_FROM_WIN32(error_code_); }
+
+ private:
+  DWORD error_code_;
+};
+
+std::ostream& operator<<(std::ostream& os, const Win32ErrorCode& error_code);
+
+// Checks for system errors and logs a human-readable error if GetLastError()
+// returns an error code. Noops on non-debug builds.
+void DebugLogWinError();
+
+std::string HResultToString(HRESULT hr);
+
 inline void CheckResult(HRESULT hr) {
   SB_DCHECK(SUCCEEDED(hr)) << "HRESULT was " << std::hex << hr
       << " which translates to\n---> \"" << HResultToString(hr) << "\"";
-}

-

-}  // namespace win32

-}  // namespace shared

-}  // namespace starboard

-

-#endif  // STARBOARD_SHARED_WIN32_ERROR_UTILS_H_

+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_ERROR_UTILS_H_
diff --git a/src/starboard/shared/win32/media_common.h b/src/starboard/shared/win32/media_common.h
index 59212ee..ac2e715 100644
--- a/src/starboard/shared/win32/media_common.h
+++ b/src/starboard/shared/win32/media_common.h
@@ -59,6 +59,8 @@
 // Convert the other way around.
 SbMediaTime ConvertToMediaTime(int64_t input);
 
+// TODO: Remove the following functions once all of them are moved into
+//       MediaTransform.
 std::vector<ComPtr<IMFMediaType>> GetAllOutputMediaTypes(IMFTransform* decoder);
 
 std::vector<ComPtr<IMFMediaType>> FilterMediaBySubType(
diff --git a/src/starboard/shared/win32/media_is_output_protected.cc b/src/starboard/shared/win32/media_is_output_protected.cc
index 73d2f07..213e5f5 100644
--- a/src/starboard/shared/win32/media_is_output_protected.cc
+++ b/src/starboard/shared/win32/media_is_output_protected.cc
@@ -1,21 +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/media.h"

-

-bool SbMediaIsOutputProtected() {

-  // Pretend that HDCP is always on.

-  // TODO: Fix this with proper HDCP query.

-  return true;

-}

+// 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/media.h"
+
+bool SbMediaIsOutputProtected() {
+  // Pretend that HDCP is always on.
+  // TODO: Fix this with proper HDCP query.
+  return true;
+}
diff --git a/src/starboard/shared/win32/media_is_video_supported.cc b/src/starboard/shared/win32/media_is_video_supported.cc
index 8236694..0bba16a 100644
--- a/src/starboard/shared/win32/media_is_video_supported.cc
+++ b/src/starboard/shared/win32/media_is_video_supported.cc
@@ -36,6 +36,6 @@
     return false;
   }
 
-  // If this is changed then so should win32_decoder_impl.cc
+  // TODO: Re-implement this based on new static function of the video decoder.
   return (video_codec == kSbMediaVideoCodecH264);
 }
diff --git a/src/starboard/shared/win32/media_transform.cc b/src/starboard/shared/win32/media_transform.cc
new file mode 100644
index 0000000..d108a1e
--- /dev/null
+++ b/src/starboard/shared/win32/media_transform.cc
@@ -0,0 +1,356 @@
+// 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/media_transform.h"
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/error_utils.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+using Microsoft::WRL::ComPtr;
+using ::starboard::shared::starboard::ThreadChecker;
+
+namespace {
+
+template <typename T>
+void ReleaseIfNotNull(T** ptr) {
+  if (*ptr) {
+    (*ptr)->Release();
+    *ptr = NULL;
+  }
+}
+
+const int kStreamId = 0;
+
+}  // namespace
+
+MediaTransform::MediaTransform(CLSID clsid)
+    : thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
+      state_(kCanAcceptInput),
+      stream_begun_(false),
+      discontinuity_(true) {
+  LPVOID* ptr_address = reinterpret_cast<LPVOID*>(transform_.GetAddressOf());
+  HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
+                                IID_IMFTransform, ptr_address);
+  CheckResult(hr);
+}
+
+MediaTransform::MediaTransform(
+    const Microsoft::WRL::ComPtr<IMFTransform>& transform)
+    : transform_(transform),
+      thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
+      state_(kCanAcceptInput),
+      stream_begun_(false),
+      discontinuity_(true) {
+  SB_DCHECK(transform_);
+}
+
+bool MediaTransform::TryWrite(const ComPtr<IMFSample>& input) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (state_ != kCanAcceptInput) {
+    return false;
+  }
+
+  if (!stream_begun_) {
+    SendMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING);
+    SendMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM);
+    stream_begun_ = true;
+  }
+
+  HRESULT hr = transform_->ProcessInput(kStreamId, input.Get(), 0);
+
+  if (SUCCEEDED(hr)) {
+    return true;
+  }
+  if (hr == MF_E_NOTACCEPTING) {
+    state_ = kCanProvideOutput;
+    return false;
+  }
+  SB_NOTREACHED() << "Unexpected return value " << hr;
+  return false;
+}
+
+ComPtr<IMFSample> MediaTransform::TryRead(ComPtr<IMFMediaType>* new_type) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(new_type);
+
+  if (state_ != kCanProvideOutput && state_ != kDraining) {
+    return NULL;
+  }
+
+  ComPtr<IMFSample> sample;
+  HRESULT hr = ProcessOutput(&sample);
+
+  if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+    hr = transform_->GetOutputAvailableType(kStreamId,
+                                            0,  // TypeIndex
+                                            new_type->GetAddressOf());
+    CheckResult(hr);
+    SetOutputType(*new_type);
+
+    hr = ProcessOutput(&sample);
+  }
+
+  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+    if (state_ == kCanProvideOutput) {
+      state_ = kCanAcceptInput;
+    }
+    if (state_ == kDraining) {
+      state_ = kDrained;
+    }
+    return NULL;
+  }
+
+  if (FAILED(hr)) {
+    // Sometimes the decryptor refuse to emit output after shutting down.
+    SB_DCHECK(hr == MF_E_INVALIDREQUEST);
+    if (state_ == kDraining) {
+      state_ = kDrained;
+    }
+    return NULL;
+  }
+
+  SB_DCHECK(sample);
+
+  if (discontinuity_) {
+    sample->SetUINT32(MFSampleExtension_Discontinuity, TRUE);
+    discontinuity_ = false;
+  }
+
+  return sample;
+}
+
+void MediaTransform::Drain() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(state_ != kDraining && state_ != kDrained);
+
+  if (state_ == kDraining || state_ == kDrained) {
+    return;
+  }
+
+  if (!stream_begun_) {
+    state_ = kDrained;
+    return;
+  }
+
+  SendMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM);
+  SendMessage(MFT_MESSAGE_COMMAND_DRAIN);
+  state_ = kDraining;
+}
+
+bool MediaTransform::draining() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  return state_ == kDraining;
+}
+
+bool MediaTransform::drained() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  return state_ == kDrained;
+}
+
+void MediaTransform::ResetFromDrained() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(drained());
+
+  state_ = kCanAcceptInput;
+  stream_begun_ = false;
+  discontinuity_ = true;
+}
+
+ComPtr<IMFMediaType> MediaTransform::GetCurrentInputType() {
+  ComPtr<IMFMediaType> type;
+  HRESULT hr = transform_->GetInputCurrentType(kStreamId, type.GetAddressOf());
+  CheckResult(hr);
+  return type;
+}
+
+void MediaTransform::SetInputType(const ComPtr<IMFMediaType>& input_type) {
+  HRESULT hr = transform_->SetInputType(0, input_type.Get(), 0);
+  CheckResult(hr);
+}
+
+std::vector<ComPtr<IMFMediaType>> MediaTransform::GetAvailableInputTypes() {
+  std::vector<ComPtr<IMFMediaType>> input_types;
+
+  for (DWORD i = 0;; ++i) {
+    ComPtr<IMFMediaType> curr_type;
+    HRESULT hr = transform_->GetInputAvailableType(kStreamId, i,
+                                                   curr_type.GetAddressOf());
+    if (FAILED(hr)) {
+      break;
+    }
+    input_types.push_back(curr_type);
+  }
+
+  return input_types;
+}
+
+ComPtr<IMFMediaType> MediaTransform::GetCurrentOutputType() {
+  ComPtr<IMFMediaType> type;
+  HRESULT hr = transform_->GetOutputCurrentType(kStreamId, type.GetAddressOf());
+  CheckResult(hr);
+  return type;
+}
+
+void MediaTransform::SetOutputType(const ComPtr<IMFMediaType>& output_type) {
+  HRESULT hr = transform_->SetOutputType(0, output_type.Get(), 0);
+  CheckResult(hr);
+}
+
+std::vector<ComPtr<IMFMediaType>> MediaTransform::GetAvailableOutputTypes() {
+  std::vector<ComPtr<IMFMediaType>> output_types;
+
+  for (DWORD i = 0;; ++i) {
+    ComPtr<IMFMediaType> curr_type;
+    HRESULT hr = transform_->GetOutputAvailableType(kStreamId, i,
+                                                    curr_type.GetAddressOf());
+    if (FAILED(hr)) {
+      break;
+    }
+    output_types.push_back(curr_type);
+  }
+
+  return output_types;
+}
+
+void MediaTransform::SetOutputTypeBySubType(GUID subtype) {
+  for (int index = 0;; ++index) {
+    ComPtr<IMFMediaType> media_type;
+    HRESULT hr =
+        transform_->GetOutputAvailableType(kStreamId, index, &media_type);
+    if (SUCCEEDED(hr)) {
+      GUID media_sub_type = {};
+      media_type->GetGUID(MF_MT_SUBTYPE, &media_sub_type);
+      if (media_sub_type == subtype) {
+        SetOutputType(media_type);
+        return;
+      }
+    } else {
+      SB_DCHECK(hr == MF_E_NO_MORE_TYPES);
+      break;
+    }
+  }
+  SB_NOTREACHED();
+}
+
+ComPtr<IMFAttributes> MediaTransform::GetAttributes() {
+  ComPtr<IMFAttributes> attributes;
+  HRESULT hr = transform_->GetAttributes(attributes.GetAddressOf());
+  CheckResult(hr);
+  return attributes;
+}
+
+ComPtr<IMFAttributes> MediaTransform::GetOutputStreamAttributes() {
+  ComPtr<IMFAttributes> attributes;
+  HRESULT hr =
+      transform_->GetOutputStreamAttributes(0, attributes.GetAddressOf());
+  CheckResult(hr);
+  return attributes;
+}
+
+ComPtr<IMFSampleProtection> MediaTransform::GetSampleProtection() {
+  ComPtr<IMFSampleProtection> sample_protection;
+  HRESULT hr = transform_.As(&sample_protection);
+  CheckResult(hr);
+  return sample_protection;
+}
+
+void MediaTransform::GetStreamCount(DWORD* input_stream_count,
+                                    DWORD* output_stream_count) {
+  SB_DCHECK(input_stream_count);
+  SB_DCHECK(output_stream_count);
+  HRESULT hr =
+      transform_->GetStreamCount(input_stream_count, output_stream_count);
+  CheckResult(hr);
+}
+
+void MediaTransform::SendMessage(MFT_MESSAGE_TYPE msg, ULONG_PTR data /*= 0*/) {
+  HRESULT hr = transform_->ProcessMessage(msg, data);
+  CheckResult(hr);
+}
+
+void MediaTransform::Reset() {
+  SendMessage(MFT_MESSAGE_COMMAND_FLUSH);
+  thread_checker_.Detach();
+  state_ = kCanAcceptInput;
+  stream_begun_ = false;
+  discontinuity_ = true;
+}
+
+void MediaTransform::PrepareOutputDataBuffer(
+    MFT_OUTPUT_DATA_BUFFER* output_data_buffer) {
+  output_data_buffer->dwStreamID = kStreamId;
+  output_data_buffer->pSample = NULL;
+  output_data_buffer->dwStatus = 0;
+  output_data_buffer->pEvents = NULL;
+
+  MFT_OUTPUT_STREAM_INFO output_stream_info;
+  HRESULT hr = transform_->GetOutputStreamInfo(kStreamId, &output_stream_info);
+  CheckResult(hr);
+
+  static const DWORD kFlagAutoAllocateMemory =
+      MFT_OUTPUT_STREAM_PROVIDES_SAMPLES |
+      MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES;
+  if ((output_stream_info.dwFlags & kFlagAutoAllocateMemory) != 0) {
+    // Try to let the IMFTransform allocate the memory if possible.
+    return;
+  }
+
+  ComPtr<IMFSample> sample;
+  hr = MFCreateSample(&sample);
+  CheckResult(hr);
+
+  ComPtr<IMFMediaBuffer> buffer;
+  hr = MFCreateAlignedMemoryBuffer(output_stream_info.cbSize,
+                                   output_stream_info.cbAlignment, &buffer);
+  CheckResult(hr);
+
+  hr = sample->AddBuffer(buffer.Get());
+  CheckResult(hr);
+
+  output_data_buffer->pSample = sample.Detach();
+}
+
+HRESULT MediaTransform::ProcessOutput(ComPtr<IMFSample>* sample) {
+  SB_DCHECK(sample);
+
+  MFT_OUTPUT_DATA_BUFFER output_data_buffer;
+  PrepareOutputDataBuffer(&output_data_buffer);
+
+  const DWORD kFlags = 0;
+  const DWORD kNumberOfBuffers = 1;
+  DWORD status = 0;
+  HRESULT hr = transform_->ProcessOutput(kFlags, kNumberOfBuffers,
+                                         &output_data_buffer, &status);
+
+  SB_DCHECK(!output_data_buffer.pEvents);
+
+  *sample = output_data_buffer.pSample;
+  ReleaseIfNotNull(&output_data_buffer.pEvents);
+  ReleaseIfNotNull(&output_data_buffer.pSample);
+
+  if (output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
+    hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
+  }
+
+  return hr;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/media_transform.h b/src/starboard/shared/win32/media_transform.h
new file mode 100644
index 0000000..123f129
--- /dev/null
+++ b/src/starboard/shared/win32/media_transform.h
@@ -0,0 +1,96 @@
+// 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_MEDIA_TRANSFORM_H_
+#define STARBOARD_SHARED_WIN32_MEDIA_TRANSFORM_H_
+
+#include <mfapi.h>
+#include <mferror.h>
+#include <mfidl.h>
+#include <wrl\client.h>
+
+#include <vector>
+
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// Wrapper class for IMFTransform with the following functionalities:
+// 1. State management:
+//    It supports a one way life cycle from "kCanAcceptInput/kCanProvideOutput"
+//    to "kDraining" then to "kDrained".
+// 2. Manages states like input/output types, various attributes, and sample
+//    protection, etc.
+// 3. Send message to the underlying transform.
+// This simplifies the implementation of higher level decoder mechanism that
+// may deal with two IMFTransforms: one decoder and one decryptor.
+class MediaTransform {
+ public:
+  explicit MediaTransform(CLSID clsid);
+  explicit MediaTransform(
+      const Microsoft::WRL::ComPtr<IMFTransform>& transform);
+
+  bool TryWrite(const Microsoft::WRL::ComPtr<IMFSample>& input);
+  Microsoft::WRL::ComPtr<IMFSample> TryRead(
+      Microsoft::WRL::ComPtr<IMFMediaType>* new_type);
+  void Drain();
+  bool draining() const;
+  bool drained() const;
+  // Once the transform is drained, this function can be called to allow the
+  // transform to accept new input as a newly created transform.  This allows
+  // the reuse of existing transform without re-negotiating all types and
+  // attributes.
+  void ResetFromDrained();
+
+  Microsoft::WRL::ComPtr<IMFMediaType> GetCurrentInputType();
+  void SetInputType(const Microsoft::WRL::ComPtr<IMFMediaType>& input_type);
+  std::vector<Microsoft::WRL::ComPtr<IMFMediaType>> GetAvailableInputTypes();
+
+  Microsoft::WRL::ComPtr<IMFMediaType> GetCurrentOutputType();
+  void SetOutputType(const Microsoft::WRL::ComPtr<IMFMediaType>& input_type);
+  std::vector<Microsoft::WRL::ComPtr<IMFMediaType>> GetAvailableOutputTypes();
+  void SetOutputTypeBySubType(GUID subtype);
+
+  Microsoft::WRL::ComPtr<IMFAttributes> GetAttributes();
+  Microsoft::WRL::ComPtr<IMFAttributes> GetOutputStreamAttributes();
+
+  Microsoft::WRL::ComPtr<IMFSampleProtection> GetSampleProtection();
+  void GetStreamCount(DWORD* input_streamcount, DWORD* output_stream_count);
+
+  void SendMessage(MFT_MESSAGE_TYPE msg, ULONG_PTR data = 0);
+
+  // Reset the media transform to its original state.
+  void Reset();
+
+ private:
+  enum State { kCanAcceptInput, kCanProvideOutput, kDraining, kDrained };
+
+  void PrepareOutputDataBuffer(MFT_OUTPUT_DATA_BUFFER* output_data_buffer);
+  HRESULT ProcessOutput(Microsoft::WRL::ComPtr<IMFSample>* sample);
+
+  Microsoft::WRL::ComPtr<IMFTransform> transform_;
+
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+  State state_;
+  bool stream_begun_;
+  bool discontinuity_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_MEDIA_TRANSFORM_H_
diff --git a/src/starboard/shared/win32/playready_license.cc b/src/starboard/shared/win32/playready_license.cc
new file mode 100644
index 0000000..5b9b11e
--- /dev/null
+++ b/src/starboard/shared/win32/playready_license.cc
@@ -0,0 +1,33 @@
+// 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/drm_system_playready.h"
+
+#include "starboard/configuration.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+scoped_refptr<SbDrmSystemPlayready::License>
+SbDrmSystemPlayready::License::Create(const void* initialization_data,
+                                      int initialization_data_size) {
+  SB_UNREFERENCED_PARAMETER(initialization_data);
+  SB_UNREFERENCED_PARAMETER(initialization_data_size);
+  return NULL;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/system_is_debugger_attached.cc b/src/starboard/shared/win32/system_is_debugger_attached.cc
new file mode 100644
index 0000000..7fec162
--- /dev/null
+++ b/src/starboard/shared/win32/system_is_debugger_attached.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>
+
+bool SbSystemIsDebuggerAttached() {
+  return IsDebuggerPresent();
+}
diff --git a/src/starboard/shared/win32/thread_create.cc b/src/starboard/shared/win32/thread_create.cc
index 2b14f92..88a606d 100644
--- a/src/starboard/shared/win32/thread_create.cc
+++ b/src/starboard/shared/win32/thread_create.cc
@@ -105,8 +105,9 @@
     case kSbThreadPriorityHigh:
       return THREAD_PRIORITY_ABOVE_NORMAL;
     case kSbThreadPriorityHighest:
-    case kSbThreadPriorityRealTime:
       return THREAD_PRIORITY_HIGHEST;
+    case kSbThreadPriorityRealTime:
+      return THREAD_PRIORITY_TIME_CRITICAL;
   }
   SB_NOTREACHED() << "Invalid priority " << priority;
   return 0;
diff --git a/src/starboard/shared/win32/video_decoder.cc b/src/starboard/shared/win32/video_decoder.cc
index 0a08c9a..e13877d 100644
--- a/src/starboard/shared/win32/video_decoder.cc
+++ b/src/starboard/shared/win32/video_decoder.cc
@@ -77,8 +77,8 @@
   SB_DCHECK(thread_checker_.CalledOnValidThread());
   SB_DCHECK(host_);
   video_decoder_thread_.reset(nullptr);
-  impl_.reset(nullptr);
-  impl_ = AbstractWin32VideoDecoder::Create(video_codec_, drm_system_);
+  impl_->Reset();
+  decoded_data_.Clear();
   video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
 }
 
diff --git a/src/starboard/shared/win32/video_decoder.h b/src/starboard/shared/win32/video_decoder.h
index 5d3734b..f973211 100644
--- a/src/starboard/shared/win32/video_decoder.h
+++ b/src/starboard/shared/win32/video_decoder.h
@@ -28,7 +28,6 @@
 #include "starboard/shared/win32/atomic_queue.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/video_decoder_thread.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 
 namespace starboard {
 namespace shared {
@@ -63,7 +62,7 @@
   // These variables will be initialized inside ctor or SetHost() and will not
   // be changed during the life time of this class.
   const SbMediaVideoCodec video_codec_;
-  SbDrmSystem drm_system_;
+  SbDrmSystem const drm_system_;
   Host* host_;
 
   // Decode-to-texture related state.
diff --git a/src/starboard/shared/win32/video_decoder_thread.cc b/src/starboard/shared/win32/video_decoder_thread.cc
index d2f8ba2..fca08be 100644
--- a/src/starboard/shared/win32/video_decoder_thread.cc
+++ b/src/starboard/shared/win32/video_decoder_thread.cc
@@ -92,9 +92,10 @@
 void VideoDecoderThread::Run() {
   std::deque<scoped_refptr<InputBuffer> > local_queue;
   while (!join_called()) {
-    // Transfer input buffer to local thread.
-    TransferPendingInputTo(&local_queue);
-    bool work_done = !local_queue.empty();
+    if (local_queue.empty()) {
+      TransferPendingInputTo(&local_queue);
+    }
+    bool work_done = false;
     bool is_end_of_stream = false;
     const size_t number_written =
         WriteAsMuchAsPossible(&local_queue, win32_video_decoder_,
@@ -105,8 +106,9 @@
       callback_->OnVideoDecoded(NULL);
     }
 
-    while (VideoFramePtr decoded_datum =
-             win32_video_decoder_->ProcessAndRead()) {
+    bool too_many_outstanding_frames;
+    while (VideoFramePtr decoded_datum = win32_video_decoder_->ProcessAndRead(
+               &too_many_outstanding_frames)) {
       if (decoded_datum.get()) {
         callback_->OnVideoDecoded(decoded_datum);
       }
@@ -118,6 +120,10 @@
       work_done = true;
     }
 
+    if (too_many_outstanding_frames) {
+      SbThreadSleep(10 * kSbTimeMillisecond);
+    }
+
     if (!work_done) {
       semaphore_.TakeWait(kSbTimeMillisecond);
     }
diff --git a/src/starboard/shared/win32/video_decoder_thread.h b/src/starboard/shared/win32/video_decoder_thread.h
index 0077756..a53ce5e 100644
--- a/src/starboard/shared/win32/video_decoder_thread.h
+++ b/src/starboard/shared/win32/video_decoder_thread.h
@@ -28,7 +28,6 @@
 #include "starboard/shared/starboard/player/job_queue.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/simple_thread.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 #include "starboard/shared/win32/win32_video_decoder.h"
 
 namespace starboard {
diff --git a/src/starboard/shared/win32/win32_audio_decoder.cc b/src/starboard/shared/win32/win32_audio_decoder.cc
index 4142764..01ff7ac 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.cc
+++ b/src/starboard/shared/win32/win32_audio_decoder.cc
@@ -15,21 +15,22 @@
 #include "starboard/shared/win32/win32_audio_decoder.h"
 
 #include <algorithm>
+#include <queue>
 
+#include "starboard/shared/starboard/thread_checker.h"
 #include "starboard/shared/win32/atomic_queue.h"
+#include "starboard/shared/win32/decrypting_decoder.h"
 #include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/media_foundation_utils.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 
 namespace starboard {
 namespace shared {
 namespace win32 {
 
 namespace {
-using Microsoft::WRL::ComPtr;
-using ::starboard::shared::win32::CheckResult;
 
-const int kStreamId = 0;
+using Microsoft::WRL::ComPtr;
+using ::starboard::shared::starboard::ThreadChecker;
 
 std::vector<ComPtr<IMFMediaType>> Filter(
     GUID subtype_guid, const std::vector<ComPtr<IMFMediaType>>& input) {
@@ -47,23 +48,6 @@
   return output;
 }
 
-std::vector<ComPtr<IMFMediaType>> GetAvailableTypes(IMFTransform* decoder) {
-  std::vector<ComPtr<IMFMediaType>> output;
-  for (DWORD i = 0; ; ++i) {
-    ComPtr<IMFMediaType> curr_type;
-    HRESULT input_hr_success = decoder->GetInputAvailableType(
-        kStreamId,
-        i,
-        curr_type.GetAddressOf());
-    if (!SUCCEEDED(input_hr_success)) {
-      break;
-    }
-    output.push_back(curr_type);
-  }
-
-  return output;
-}
-
 class WinAudioFormat {
  public:
   explicit WinAudioFormat(const SbMediaAudioHeader& audio_header) {
@@ -102,8 +86,7 @@
   uint8_t full_structure[sizeof(WAVEFORMATEX) + kAudioExtraFormatBytes];
 };
 
-class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder,
-                                      public MediaBufferConsumerInterface {
+class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder {
  public:
   AbstractWin32AudioDecoderImpl(SbMediaAudioCodec codec,
                                 SbMediaAudioFrameStorageType audio_frame_fmt,
@@ -113,10 +96,9 @@
       : codec_(codec),
         audio_frame_fmt_(audio_frame_fmt),
         sample_type_(sample_type),
-        audio_header_(audio_header) {
-    MediaBufferConsumerInterface* media_cb = this;
-    impl_.reset(new DecoderImpl("audio", media_cb, drm_system));
-    EnsureAudioDecoderCreated();
+        audio_header_(audio_header),
+        impl_("audio", CLSID_MSAACDecMFT, drm_system) {
+    Configure();
   }
 
   static GUID ConvertToWin32AudioCodec(SbMediaAudioCodec codec) {
@@ -131,11 +113,25 @@
     return MFAudioFormat_PCM;
   }
 
-  virtual void Consume(ComPtr<IMFMediaBuffer> media_buffer,
-                       int64_t win32_timestamp) {
+  void Consume(ComPtr<IMFSample> sample) {
+    DWORD buff_count = 0;
+    HRESULT hr = sample->GetBufferCount(&buff_count);
+    CheckResult(hr);
+    SB_DCHECK(buff_count == 1);
+
+    ComPtr<IMFMediaBuffer> media_buffer;
+    hr = sample->GetBufferByIndex(0, &media_buffer);
+    if (FAILED(hr)) {
+      return;
+    }
+
+    LONGLONG win32_timestamp = 0;
+    hr = sample->GetSampleTime(&win32_timestamp);
+    CheckResult(hr);
+
     BYTE* buffer;
     DWORD length;
-    HRESULT hr = media_buffer->Lock(&buffer, NULL, &length);
+    hr = media_buffer->Lock(&buffer, NULL, &length);
     CheckResult(hr);
     SB_DCHECK(length);
 
@@ -148,13 +144,11 @@
 
     std::copy(data, data + data_size, data_ptr->buffer());
 
-    output_queue_.PushBack(data_ptr);
+    output_queue_.push(data_ptr);
     media_buffer->Unlock();
   }
 
-  void OnNewOutputType(const ComPtr<IMFMediaType>& /*type*/) override {}
-
-  ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
+  void Configure() {
     ComPtr<IMFMediaType> input_type;
     HRESULT hr = MFCreateMediaType(&input_type);
     CheckResult(hr);
@@ -174,11 +168,11 @@
     hr = input_type->SetGUID(MF_MT_SUBTYPE, subtype);
     CheckResult(hr);
 
+    MediaTransform& decoder = impl_.GetDecoder();
     std::vector<ComPtr<IMFMediaType>> available_types =
-        GetAvailableTypes(decoder);
+        decoder.GetAvailableInputTypes();
 
-    GUID audio_fmt_guid = ConvertToWin32AudioCodec(codec_);
-    available_types = Filter(audio_fmt_guid, available_types);
+    available_types = Filter(subtype, available_types);
     SB_DCHECK(available_types.size());
     ComPtr<IMFMediaType> selected = available_types[0];
 
@@ -193,114 +187,69 @@
       CopyUint32Property(*it, input_type.Get(), selected.Get());
     }
 
-    hr = decoder->SetInputType(0, selected.Get(), 0);
-
-    CheckResult(hr);
-    return selected;
+    decoder.SetInputType(selected);
+    decoder.SetOutputTypeBySubType(MFAudioFormat_Float);
   }
 
-  void EnsureAudioDecoderCreated() SB_OVERRIDE {
-    if (impl_->has_decoder()) {
-      return;
-    }
+  bool TryWrite(const scoped_refptr<InputBuffer>& buff) SB_OVERRIDE {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
 
-    ComPtr<IMFTransform> decoder =
-        DecoderImpl::CreateDecoder(CLSID_MSAACDecMFT);
-
-    ComPtr<IMFMediaType> media_type = Configure(decoder.Get());
-
-    SB_DCHECK(decoder);
-
-    impl_->set_decoder(decoder);
-
-    // TODO: MFWinAudioFormat_PCM?
-    ComPtr<IMFMediaType> output_type =
-        FindMediaType(MFAudioFormat_Float, decoder.Get());
-
-    SB_DCHECK(output_type);
-
-    HRESULT hr = decoder->SetOutputType(0, output_type.Get(), 0);
-    CheckResult(hr);
-
-    decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
-    decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
-  }
-
-  bool TryWrite(const InputBuffer& buff) SB_OVERRIDE {
-    EnsureAudioDecoderCreated();
-    if (!impl_->has_decoder()) {
-      return false;  // TODO: Signal an error.
-    }
-
-    const void* data = buff.data();
-    const int size = buff.size();
-    const int64_t media_timestamp = buff.pts();
-
-    std::vector<uint8_t> key_id;
-    std::vector<uint8_t> iv;
-    std::vector<Subsample> subsamples;
-
-    const SbDrmSampleInfo* drm_info = buff.drm_info();
-
-    if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
-      key_id.assign(drm_info->identifier,
-                    drm_info->identifier + drm_info->identifier_size);
-      iv.assign(drm_info->initialization_vector,
-                drm_info->initialization_vector +
-                    drm_info->initialization_vector_size);
-      subsamples.reserve(drm_info->subsample_count);
-      for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
-        Subsample subsample = {
-            static_cast<uint32_t>(
-                drm_info->subsample_mapping[i].clear_byte_count),
-            static_cast<uint32_t>(
-                drm_info->subsample_mapping[i].encrypted_byte_count)};
-        subsamples.push_back(subsample);
-      }
-    }
-
-    const std::int64_t win32_time_stamp = ConvertToWin32Time(media_timestamp);
-
-    // Adjust the offset for 7 bytes to remove the ADTS header.
+    // The incoming audio is in ADTS format which has a 7 bytes header.  But
+    // the audio decoder is configured to accept raw AAC.  So we have to adjust
+    // the data, size, and subsample mapping to skip the ADTS header.
     const int kADTSHeaderSize = 7;
-    const uint8_t* audio_start =
-        static_cast<const uint8_t*>(data) + kADTSHeaderSize;
-    const int audio_size = size - kADTSHeaderSize;
-    if (!subsamples.empty()) {
-      SB_DCHECK(subsamples[0].clear_bytes == kADTSHeaderSize);
-      subsamples[0].clear_bytes = 0;
-    }
-
-    const bool write_ok = impl_->TryWriteInputBuffer(
-        audio_start, audio_size, win32_time_stamp, key_id, iv, subsamples);
-    impl_->DeliverOutputOnAllTransforms();
+    const bool write_ok = impl_.TryWriteInputBuffer(buff, kADTSHeaderSize);
     return write_ok;
   }
 
   void WriteEndOfStream() SB_OVERRIDE {
-    if (impl_->has_decoder()) {
-      impl_->DrainDecoder();
-      impl_->DeliverOutputOnAllTransforms();
-      output_queue_.PushBack(new DecodedAudio);
-    } else {
-      // Don't call DrainDecoder() if input data is never queued.
-      // TODO: Send EOS.
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    impl_.Drain();
+    ComPtr<IMFSample> sample;
+    ComPtr<IMFMediaType> media_type;
+    while (impl_.ProcessAndRead(&sample, &media_type)) {
+      if (sample) {
+        Consume(sample);
+      }
     }
+    output_queue_.push(new DecodedAudio);
   }
 
   scoped_refptr<DecodedAudio> ProcessAndRead() SB_OVERRIDE {
-    impl_->DeliverOutputOnAllTransforms();
-    scoped_refptr<DecodedAudio> output = output_queue_.PopFront();
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    ComPtr<IMFSample> sample;
+    ComPtr<IMFMediaType> media_type;
+    while (impl_.ProcessAndRead(&sample, &media_type)) {
+      if (sample) {
+        Consume(sample);
+      }
+    }
+    if (output_queue_.empty()) {
+      return NULL;
+    }
+    scoped_refptr<DecodedAudio> output = output_queue_.front();
+    output_queue_.pop();
     return output;
   }
 
-  SbMediaAudioCodec codec_;
-  SbMediaAudioHeader audio_header_;
-  SbMediaAudioSampleType sample_type_;
-  SbMediaAudioFrameStorageType audio_frame_fmt_;
-  scoped_ptr<DecoderImpl> impl_;
-  AtomicQueue<DecodedAudioPtr> output_queue_;
+  void Reset() SB_OVERRIDE {
+    impl_.Reset();
+    std::queue<DecodedAudioPtr> empty;
+    output_queue_.swap(empty);
+    thread_checker_.Detach();
+  }
+
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+  const SbMediaAudioCodec codec_;
+  const SbMediaAudioHeader audio_header_;
+  const SbMediaAudioSampleType sample_type_;
+  const SbMediaAudioFrameStorageType audio_frame_fmt_;
+  DecryptingDecoder impl_;
+  std::queue<DecodedAudioPtr> output_queue_;
 };
+
 }  // anonymous namespace.
 
 scoped_ptr<AbstractWin32AudioDecoder> AbstractWin32AudioDecoder::Create(
diff --git a/src/starboard/shared/win32/win32_audio_decoder.h b/src/starboard/shared/win32/win32_audio_decoder.h
index 3bfe78c..91293d6 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.h
+++ b/src/starboard/shared/win32/win32_audio_decoder.h
@@ -17,6 +17,7 @@
 
 #include <vector>
 
+#include "starboard/common/ref_counted.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/drm.h"
 #include "starboard/media.h"
@@ -42,16 +43,13 @@
 
   // INPUT:
   //
-  // ZACH: Note that this deviates from Xiaoming's AudioDecoder as this does
-  // not have the encrypted parameters. This will be added later.
-  virtual bool TryWrite(const InputBuffer& buff) = 0;
+  virtual bool TryWrite(const scoped_refptr<InputBuffer>& buff) = 0;
   virtual void WriteEndOfStream() = 0;
   // OUTPUT
   //
   virtual DecodedAudioPtr ProcessAndRead() = 0;
-
- private:
-  virtual void EnsureAudioDecoderCreated() = 0;
+  // Reset
+  virtual void Reset() = 0;
 };
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/win32_decoder_impl.cc b/src/starboard/shared/win32/win32_decoder_impl.cc
deleted file mode 100644
index b41b418..0000000
--- a/src/starboard/shared/win32/win32_decoder_impl.cc
+++ /dev/null
@@ -1,522 +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 "starboard/shared/win32/win32_decoder_impl.h"
-
-#include <D3D11.h>
-#include <mfapi.h>
-#include <mferror.h>
-#include <mfidl.h>
-#include <wrl/client.h>
-
-#include <algorithm>
-#include <numeric>
-
-#include "starboard/byte_swap.h"
-#include "starboard/common/ref_counted.h"
-#include "starboard/log.h"
-#include "starboard/shared/win32/error_utils.h"
-#include "starboard/shared/win32/media_common.h"
-#include "starboard/shared/win32/media_foundation_utils.h"
-
-namespace starboard {
-namespace shared {
-namespace win32 {
-
-using ::starboard::shared::win32::CheckResult;
-
-namespace {
-
-// TODO: merge all kStreamId's.
-const int kStreamId = 0;
-
-ComPtr<IMFSample> CreateSample(const void* data,
-                               int size,
-                               int64_t win32_timestamp) {
-  ComPtr<IMFMediaBuffer> buffer;
-  HRESULT hr = MFCreateMemoryBuffer(size, &buffer);
-  CheckResult(hr);
-
-  BYTE* buffer_ptr;
-  hr = buffer->Lock(&buffer_ptr, 0, 0);
-  CheckResult(hr);
-
-  SbMemoryCopy(buffer_ptr, data, size);
-
-  hr = buffer->Unlock();
-  CheckResult(hr);
-
-  hr = buffer->SetCurrentLength(size);
-  CheckResult(hr);
-
-  ComPtr<IMFSample> input;
-  hr = MFCreateSample(&input);
-  CheckResult(hr);
-
-  hr = input->AddBuffer(buffer.Get());
-  CheckResult(hr);
-
-  // sample time is in 100 nanoseconds.
-  input->SetSampleTime(win32_timestamp);
-  return input;
-}
-
-bool TryWriteToMediaProcessor(ComPtr<IMFTransform> media_processor,
-                              ComPtr<IMFSample> input,
-                              int stream_id) {
-  DWORD flag;
-  HRESULT hr = media_processor->GetInputStatus(stream_id, &flag);
-  CheckResult(hr);
-
-  SB_DCHECK(MFT_INPUT_STATUS_ACCEPT_DATA == flag);
-  hr = media_processor->ProcessInput(kStreamId, input.Get(), 0);
-
-  switch (hr) {
-    case S_OK: {  // Data was sent to the media processor.
-      return true;
-    }
-    case MF_E_NOTACCEPTING: {
-      return false;
-    }
-    default: {
-      SB_NOTREACHED() << "Unexpected error.";
-    }
-  }
-  return false;
-}
-
-bool StreamAllocatesMemory(DWORD out_stream_flags) {
-  static const DWORD kFlagAutoAllocateMemory =
-      MFT_OUTPUT_STREAM_PROVIDES_SAMPLES |
-      MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES;
-
-  const bool output_stream_allocates_memory =
-      (out_stream_flags & kFlagAutoAllocateMemory) != 0;
-  return output_stream_allocates_memory;
-}
-
-template <typename T>
-void ReleaseIfNotNull(T** ptr) {
-  if (*ptr) {
-    (*ptr)->Release();
-    *ptr = NULL;
-  }
-}
-
-}  // namespace
-
-DecoderImpl::DecoderImpl(const std::string& type,
-                         MediaBufferConsumerInterface* media_buffer_consumer,
-                         SbDrmSystem drm_system)
-    : type_(type),
-      media_buffer_consumer_(media_buffer_consumer),
-      discontinuity_(false) {
-  SB_DCHECK(media_buffer_consumer_);
-  drm_system_ =
-      static_cast<::starboard::xb1::shared::playready::SbDrmSystemPlayready*>(
-          drm_system);
-}
-
-DecoderImpl::~DecoderImpl() {
-}
-
-// static
-ComPtr<IMFTransform> DecoderImpl::CreateDecoder(CLSID clsid) {
-  ComPtr<IMFTransform> decoder;
-  LPVOID* ptr_address = reinterpret_cast<LPVOID*>(decoder.GetAddressOf());
-  HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
-                                IID_IMFTransform, ptr_address);
-  CheckResult(hr);
-  return decoder;
-}
-
-void DecoderImpl::ActivateDecryptor() {
-  SB_DCHECK(decoder_);
-
-  if (!decryptor_) {
-    return;
-  }
-
-  ComPtr<IMFMediaType> decryptor_input_type;
-
-  HRESULT hr = decoder_->GetInputCurrentType(
-      kStreamId, decryptor_input_type.GetAddressOf());
-  CheckResult(hr);
-
-  GUID original_sub_type;
-  {
-    ComPtr<IMFMediaType> output_type;
-    hr = decoder_->GetOutputCurrentType(kStreamId, output_type.GetAddressOf());
-    CheckResult(hr);
-    output_type->GetGUID(MF_MT_SUBTYPE, &original_sub_type);
-  }
-
-  const DWORD kFlags = 0;
-  hr = decryptor_->SetInputType(kStreamId, decryptor_input_type.Get(), kFlags);
-  CheckResult(hr);
-
-  // Ensure that the decryptor and the decoder agrees on the protection of
-  // samples transferred between them.
-  ComPtr<IMFSampleProtection> decryption_sample_protection;
-  ComPtr<IMFSampleProtection> decoder_sample_protection;
-  DWORD decryption_protection_version;
-  DWORD decoder_protection_version;
-  DWORD protection_version;
-  BYTE* cert_data = NULL;
-  DWORD cert_data_size = 0;
-  BYTE* crypt_seed = NULL;
-  DWORD crypt_seed_size = 0;
-  ComPtr<IMFMediaType> decoder_input_type;
-
-  hr = decryptor_.As(&decryption_sample_protection);
-  CheckResult(hr);
-  hr = decryption_sample_protection->GetOutputProtectionVersion(
-      &decryption_protection_version);
-  CheckResult(hr);
-  hr = decoder_.As(&decoder_sample_protection);
-  CheckResult(hr);
-  hr = decoder_sample_protection->GetInputProtectionVersion(
-      &decoder_protection_version);
-  CheckResult(hr);
-  protection_version =
-      std::min(decoder_protection_version, decryption_protection_version);
-  if (protection_version < SAMPLE_PROTECTION_VERSION_RC4) {
-    SB_NOTREACHED();
-  }
-
-  hr = decoder_sample_protection->GetProtectionCertificate(
-      protection_version,
-      &cert_data, &cert_data_size);
-  CheckResult(hr);
-
-  hr = decryption_sample_protection->InitOutputProtection(
-      protection_version, 0, cert_data, cert_data_size,
-      &crypt_seed, &crypt_seed_size);
-  CheckResult(hr);
-
-  hr = decoder_sample_protection->InitInputProtection(
-      protection_version, 0,
-      crypt_seed, crypt_seed_size);
-  CheckResult(hr);
-
-  CoTaskMemFree(cert_data);
-  CoTaskMemFree(crypt_seed);
-
-  // Ensure that the input type of the decoder is the output type of the
-  // decryptor.
-  hr = decryptor_->GetOutputAvailableType(
-      kStreamId,
-      0,  // Type Index
-      decoder_input_type.ReleaseAndGetAddressOf());
-  CheckResult(hr);
-  hr = decryptor_->SetOutputType(kStreamId, decoder_input_type.Get(),
-                                 0);  //  _MFT_SET_TYPE_FLAGS
-  CheckResult(hr);
-  hr = decoder_->SetInputType(kStreamId, decoder_input_type.Get(),
-                              0);  //  _MFT_SET_TYPE_FLAGS
-  CheckResult(hr);
-
-  // Start the decryptor, note that this should be better abstracted.
-  SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_BEGIN_STREAMING);
-  SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_START_OF_STREAM);
-
-  for (int index = 0;; ++index) {
-    ComPtr<IMFMediaType> output_type;
-    hr = decoder_->GetOutputAvailableType(0, index, &output_type);
-    if (SUCCEEDED(hr)) {
-      GUID sub_type;
-      output_type->GetGUID(MF_MT_SUBTYPE, &sub_type);
-      if (IsEqualGUID(sub_type, original_sub_type)) {
-        hr = decoder_->SetOutputType(0, output_type.Get(), 0);
-        CheckResult(hr);
-        break;
-      }
-    }
-    output_type.Reset();
-  }
-}
-
-bool DecoderImpl::TryWriteInputBuffer(
-    const void* data,
-    int size,
-    std::int64_t win32_timestamp,
-    const std::vector<std::uint8_t>& key_id,
-    const std::vector<std::uint8_t>& iv,
-    const std::vector<Subsample>& subsamples) {
-  // MFSampleExtension_CleanPoint is a key-frame for the video + audio. It is
-  // not set here because the win32 system is smart enough to figure this out.
-  // It will probably be totally ok to not set this at all. Resolution: If
-  // there are problems with win32 video decoding, come back to this and see
-  // if setting this will fix it. THis will be used if
-  // SbMediaVideoSampleInfo::is_key_frame is true inside of the this function
-  // (which will receive an InputBuffer).
-  // Set MFSampleExtension_CleanPoint attributes.
-  SB_DCHECK(decoder_);
-  ComPtr<IMFSample> input = CreateSample(data, size, win32_timestamp);
-  SB_DCHECK(decoder_);
-
-  // Has to check both as sometimes the sample can contain an invalid key id.
-  // Better check the key id size is 16 and iv size is 8 or 16.
-  bool encrypted = !key_id.empty() && !iv.empty();
-  if (encrypted) {
-    if (!decryptor_) {
-      scoped_refptr<::starboard::xb1::shared::playready::PlayreadyLicense>
-          license = drm_system_->GetLicense(key_id.data(),
-                                            static_cast<int>(key_id.size()));
-      if (license && license->usable()) {
-        decryptor_ = license->decryptor();
-        ActivateDecryptor();
-      }
-    }
-    if (!decryptor_) {
-      SB_NOTREACHED();
-      return false;
-    }
-    size_t iv_size = iv.size();
-    const char kEightZeros[8] = {0};
-    if (iv_size == 16 && SbMemoryCompare(iv.data() + 8, kEightZeros, 8) == 0) {
-      // For iv that is 16 bytes long but the the last 8 bytes is 0, we treat
-      // it as an 8 bytes iv.
-      iv_size = 8;
-    }
-    input->SetBlob(MFSampleExtension_Encryption_SampleID,
-                   reinterpret_cast<const UINT8*>(iv.data()),
-                   static_cast<UINT32>(iv_size));
-    SB_DCHECK(key_id.size() == sizeof(GUID));
-    GUID guid = *reinterpret_cast<const GUID*>(key_id.data());
-
-    guid.Data1 = SbByteSwapU32(guid.Data1);
-    guid.Data2 = SbByteSwapU16(guid.Data2);
-    guid.Data3 = SbByteSwapU16(guid.Data3);
-
-    input->SetGUID(MFSampleExtension_Content_KeyID, guid);
-
-    std::vector<DWORD> subsamples_data;
-    if (!subsamples.empty()) {
-      for (auto& subsample : subsamples) {
-        subsamples_data.push_back(subsample.clear_bytes);
-        subsamples_data.push_back(subsample.encrypted_bytes);
-      }
-      SB_DCHECK(std::accumulate(subsamples_data.begin(), subsamples_data.end(),
-                                0) == size);
-    } else {
-      subsamples_data.push_back(0);
-      subsamples_data.push_back(size);
-    }
-    input->SetBlob(MFSampleExtension_Encryption_SubSampleMappingSplit,
-                   reinterpret_cast<UINT8*>(&subsamples_data[0]),
-                   static_cast<UINT32>(subsamples_data.size() * sizeof(DWORD)));
-  }
-
-  ComPtr<IMFTransform> media_processor = encrypted ? decryptor_ : decoder_;
-  const bool write_ok =
-      TryWriteToMediaProcessor(media_processor, input, kStreamId);
-  return write_ok;
-}
-
-void DecoderImpl::DrainDecoder() {
-  // This is used during EOS to get the last few frames.
-  SB_DCHECK(decoder_);
-  SendMFTMessage(decoder_.Get(), MFT_MESSAGE_NOTIFY_END_OF_STREAM);
-  SendMFTMessage(decoder_.Get(), MFT_MESSAGE_COMMAND_DRAIN);
-}
-
-bool DecoderImpl::DeliverOutputOnAllTransforms() {
-  SB_DCHECK(decoder_);
-  bool delivered = false;
-  if (decryptor_) {
-    while (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decryptor_)) {
-      HRESULT hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
-      if (hr == MF_E_NOTACCEPTING) {
-        // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
-        // there must be some output available. Retrieve the output and the next
-        // ProcessInput() should succeed.
-        while (ComPtr<IMFSample> sample_inner =
-                   DeliverOutputOnTransform(decoder_)) {
-          DeliverDecodedSample(sample_inner);
-          delivered = true;
-        }
-        hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
-      }
-      CheckResult(hr);
-      delivered = true;
-    }
-  }
-  while (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) {
-    DeliverDecodedSample(sample);
-    delivered = true;
-  }
-  return delivered;
-}
-
-bool DecoderImpl::DeliverOneOutputOnAllTransforms() {
-  SB_DCHECK(decoder_);
-  if (decryptor_) {
-    if (!cached_decryptor_output_) {
-      cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
-    }
-    while (cached_decryptor_output_) {
-      HRESULT hr =
-          decoder_->ProcessInput(kStreamId, cached_decryptor_output_.Get(), 0);
-      if (hr == MF_E_NOTACCEPTING) {
-        break;
-      } else {
-        CheckResult(hr);
-        cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
-      }
-    }
-    if (cached_decryptor_output_) {
-      // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
-      // there must be some output available. Retrieve the output and the next
-      // ProcessInput() should succeed.
-      ComPtr<IMFSample> decoder_output = DeliverOutputOnTransform(decoder_);
-      if (decoder_output) {
-        DeliverDecodedSample(decoder_output);
-      }
-      return decoder_output != NULL;
-    }
-  }
-
-  bool delivered = false;
-  if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) {
-    DeliverDecodedSample(sample);
-    delivered = true;
-  }
-  return delivered;
-}
-
-void DecoderImpl::set_decoder(Microsoft::WRL::ComPtr<IMFTransform> decoder) {
-  // Either new object is null or the old one is.
-  SB_DCHECK(!decoder || !decoder_);
-  decoder_ = decoder;
-}
-
-void DecoderImpl::SendMFTMessage(IMFTransform* transform,
-                                 MFT_MESSAGE_TYPE msg) {
-  if (!transform) {
-    return;
-  }
-  ULONG_PTR data = 0;
-  HRESULT hr = transform->ProcessMessage(msg, data);
-  CheckResult(hr);
-}
-
-void DecoderImpl::PrepareOutputDataBuffer(
-    ComPtr<IMFTransform> transform,
-    MFT_OUTPUT_DATA_BUFFER* output_data_buffer) {
-  output_data_buffer->dwStreamID = kStreamId;
-  output_data_buffer->pSample = NULL;
-  output_data_buffer->dwStatus = 0;
-  output_data_buffer->pEvents = NULL;
-
-  MFT_OUTPUT_STREAM_INFO output_stream_info;
-  HRESULT hr = transform->GetOutputStreamInfo(kStreamId, &output_stream_info);
-  CheckResult(hr);
-
-  if (StreamAllocatesMemory(output_stream_info.dwFlags)) {
-    // Try to let the IMFTransform allocate the memory if possible.
-    return;
-  }
-
-  ComPtr<IMFSample> sample;
-  hr = MFCreateSample(&sample);
-  CheckResult(hr);
-
-  ComPtr<IMFMediaBuffer> buffer;
-  hr = MFCreateAlignedMemoryBuffer(output_stream_info.cbSize,
-                                   output_stream_info.cbAlignment, &buffer);
-
-  CheckResult(hr);
-
-  hr = sample->AddBuffer(buffer.Get());
-  CheckResult(hr);
-
-  output_data_buffer->pSample = sample.Detach();
-}
-
-ComPtr<IMFSample> DecoderImpl::DeliverOutputOnTransform(
-    ComPtr<IMFTransform> transform) {
-  MFT_OUTPUT_DATA_BUFFER output_data_buffer;
-  PrepareOutputDataBuffer(transform, &output_data_buffer);
-
-  const DWORD kFlags = 0;
-  const DWORD kNumberOfBuffers = 1;
-  DWORD status = 0;
-  HRESULT hr = transform->ProcessOutput(kFlags, kNumberOfBuffers,
-                                        &output_data_buffer, &status);
-
-  SB_DCHECK(!output_data_buffer.pEvents);
-
-  ComPtr<IMFSample> output = output_data_buffer.pSample;
-  ReleaseIfNotNull(&output_data_buffer.pEvents);
-  ReleaseIfNotNull(&output_data_buffer.pSample);
-
-  if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
-    ComPtr<IMFMediaType> media_type;
-
-    hr = transform->GetOutputAvailableType(kStreamId,
-                                           0,  // TypeIndex
-                                           &media_type);
-    CheckResult(hr);
-
-    hr = transform->SetOutputType(kStreamId, media_type.Get(), 0);
-    CheckResult(hr);
-
-    media_buffer_consumer_->OnNewOutputType(media_type);
-
-    hr = transform->ProcessOutput(kFlags, kNumberOfBuffers, &output_data_buffer,
-                                  &status);
-
-    SB_DCHECK(!output_data_buffer.pEvents);
-
-    output = output_data_buffer.pSample;
-    ReleaseIfNotNull(&output_data_buffer.pEvents);
-    ReleaseIfNotNull(&output_data_buffer.pSample);
-  }
-
-  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ||
-      output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
-    return NULL;
-  }
-
-  SB_DCHECK(output);
-  if (discontinuity_) {
-    output->SetUINT32(MFSampleExtension_Discontinuity, TRUE);
-    discontinuity_ = false;
-  }
-
-  CheckResult(hr);
-  return output;
-}
-
-void DecoderImpl::DeliverDecodedSample(ComPtr<IMFSample> sample) {
-  DWORD buff_count = 0;
-  HRESULT hr = sample->GetBufferCount(&buff_count);
-  CheckResult(hr);
-  SB_DCHECK(buff_count == 1);
-
-  ComPtr<IMFMediaBuffer> media_buffer;
-  hr = sample->GetBufferByIndex(0, &media_buffer);
-
-  if (SUCCEEDED(hr)) {
-    LONGLONG win32_timestamp = 0;
-    hr = sample->GetSampleTime(&win32_timestamp);
-    CheckResult(hr);
-    media_buffer_consumer_->Consume(media_buffer, win32_timestamp);
-  }
-}
-
-}  // namespace win32
-}  // namespace shared
-}  // namespace starboard
diff --git a/src/starboard/shared/win32/win32_decoder_impl.h b/src/starboard/shared/win32/win32_decoder_impl.h
deleted file mode 100644
index cef0d5d..0000000
--- a/src/starboard/shared/win32/win32_decoder_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 STARBOARD_SHARED_WIN32_WIN32_DECODER_IMPL_H_
-#define STARBOARD_SHARED_WIN32_WIN32_DECODER_IMPL_H_
-
-#include <D3D11.h>
-#include <mfapi.h>
-#include <mferror.h>
-#include <mfidl.h>
-#include <wrl\client.h>
-
-#include <string>
-#include <vector>
-
-#include "starboard/common/scoped_ptr.h"
-#include "starboard/drm.h"
-#include "starboard/media.h"
-#include "starboard/shared/win32/media_common.h"
-#include "starboard/types.h"
-
-// TODO: Win32 code shouldn't depend on Xb1 code.  Refactor this.
-#include "starboard/xb1/shared/playready/drm_system_playready.h"
-
-namespace starboard {
-namespace shared {
-namespace win32 {
-
-class MediaBufferConsumerInterface {
- public:
-  virtual void Consume(Microsoft::WRL::ComPtr<IMFMediaBuffer> media_buffer,
-                       int64_t win32_timestamp) = 0;
-
-  virtual void OnNewOutputType(const ComPtr<IMFMediaType>& type) = 0;
-
- protected:
-  ~MediaBufferConsumerInterface() {}
-};
-
-struct Subsample {
-  uint32_t clear_bytes;
-  uint32_t encrypted_bytes;
-};
-
-class DecoderImpl {
- public:
-  DecoderImpl(const std::string& type,
-              MediaBufferConsumerInterface* media_buffer_consumer,
-              SbDrmSystem drm_system);
-  ~DecoderImpl();
-
-  static Microsoft::WRL::ComPtr<IMFTransform> CreateDecoder(CLSID clsid);
-
-  void ActivateDecryptor();
-  bool TryWriteInputBuffer(const void* data,
-                           int size,
-                           std::int64_t win32_timestamp,
-                           const std::vector<uint8_t>& key_id,
-                           const std::vector<uint8_t>& iv,
-                           const std::vector<Subsample>& subsamples);
-
-  void DrainDecoder();
-
-  bool DeliverOutputOnAllTransforms();
-  bool DeliverOneOutputOnAllTransforms();
-
-  void set_decoder(Microsoft::WRL::ComPtr<IMFTransform> decoder);
-
-  bool has_decoder() const { return decoder_.Get(); }
-
- private:
-  static void SendMFTMessage(IMFTransform* transform, MFT_MESSAGE_TYPE msg);
-
-  void PrepareOutputDataBuffer(Microsoft::WRL::ComPtr<IMFTransform> transform,
-                               MFT_OUTPUT_DATA_BUFFER* output_data_buffer);
-  Microsoft::WRL::ComPtr<IMFSample> DeliverOutputOnTransform(
-      Microsoft::WRL::ComPtr<IMFTransform> transform);
-  void DeliverDecodedSample(Microsoft::WRL::ComPtr<IMFSample> sample);
-
-  const std::string type_;  // For debugging purpose.
-  // This is the target for the all completed media buffers.
-  MediaBufferConsumerInterface* media_buffer_consumer_;
-  ::starboard::xb1::shared::playready::SbDrmSystemPlayready* drm_system_;
-  bool discontinuity_;
-
-  Microsoft::WRL::ComPtr<IMFTransform> decryptor_;
-  Microsoft::WRL::ComPtr<IMFSample> cached_decryptor_output_;
-  Microsoft::WRL::ComPtr<IMFTransform> decoder_;
-};
-
-}  // namespace win32
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_WIN32_WIN32_DECODER_IMPL_H_
diff --git a/src/starboard/shared/win32/win32_video_decoder.cc b/src/starboard/shared/win32/win32_video_decoder.cc
index 667b2e5..d475a79 100644
--- a/src/starboard/shared/win32/win32_video_decoder.cc
+++ b/src/starboard/shared/win32/win32_video_decoder.cc
@@ -15,20 +15,24 @@
 #include "starboard/shared/win32/win32_video_decoder.h"
 
 #include <Codecapi.h>
+
+#include <queue>
 #include <utility>
 
+#include "starboard/shared/starboard/thread_checker.h"
 #include "starboard/shared/win32/atomic_queue.h"
+#include "starboard/shared/win32/decrypting_decoder.h"
 #include "starboard/shared/win32/dx_context_video_decoder.h"
 #include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/media_foundation_utils.h"
-#include "starboard/shared/win32/win32_decoder_impl.h"
 
 namespace starboard {
 namespace shared {
 namespace win32 {
 
 using Microsoft::WRL::ComPtr;
+using ::starboard::shared::starboard::ThreadChecker;
 using ::starboard::shared::win32::CheckResult;
 
 namespace {
@@ -47,22 +51,30 @@
 
 class VideoFrameFactory {
  public:
-  static VideoFramePtr Construct(SbMediaTime timestamp,
-                                 ComPtr<IMFMediaBuffer> media_buffer,
+  static VideoFramePtr Construct(ComPtr<IMFSample> sample,
                                  uint32_t width,
                                  uint32_t height) {
+    ComPtr<IMFMediaBuffer> media_buffer;
+    HRESULT hr = sample->GetBufferByIndex(0, &media_buffer);
+    CheckResult(hr);
+
+    LONGLONG win32_timestamp = 0;
+    hr = sample->GetSampleTime(&win32_timestamp);
+    CheckResult(hr);
+
     VideoFramePtr out(new VideoFrame(width, height,
-                                     timestamp, media_buffer.Detach(),
-                                     nullptr, DeleteTextureFn));
+                                     ConvertToMediaTime(win32_timestamp),
+                                     sample.Detach(),
+                                     nullptr, DeleteSample));
     frames_in_flight_.increment();
     return out;
   }
   static int frames_in_flight() { return frames_in_flight_.load(); }
 
  private:
-  static void DeleteTextureFn(void* context, void* texture) {
+  static void DeleteSample(void* context, void* sample) {
     SB_UNREFERENCED_PARAMETER(context);
-    IMFMediaBuffer* data_ptr = static_cast<IMFMediaBuffer*>(texture);
+    IMFSample* data_ptr = static_cast<IMFSample*>(sample);
     data_ptr->Release();
     frames_in_flight_.decrement();
   }
@@ -72,26 +84,27 @@
 
 atomic_int32_t VideoFrameFactory::frames_in_flight_;
 
-class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder,
-                                    public MediaBufferConsumerInterface {
+class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder {
  public:
   AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec, SbDrmSystem drm_system)
-      : codec_(codec), surface_width_(0), surface_height_(0) {
-    MediaBufferConsumerInterface* media_cb = this;
-    impl_.reset(new DecoderImpl("video", media_cb, drm_system));
-    EnsureVideoDecoderCreated();
+      : thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
+        codec_(codec),
+        impl_("video", CLSID_VideoDecoder, drm_system),
+        surface_width_(0),
+        surface_height_(0) {
+    Configure();
   }
 
-  virtual void Consume(ComPtr<IMFMediaBuffer> media_buffer,
-                       int64_t win32_timestamp) {
-    const SbMediaTime media_timestamp = ConvertToMediaTime(win32_timestamp);
+  void Consume(ComPtr<IMFSample> sample) {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
     VideoFramePtr frame_output =
-        VideoFrameFactory::Construct(media_timestamp, media_buffer,
-            surface_width_, surface_height_);
-    output_queue_.PushBack(frame_output);
+        VideoFrameFactory::Construct(sample, surface_width_, surface_height_);
+    output_queue_.push(frame_output);
   }
 
-  void OnNewOutputType(const ComPtr<IMFMediaType>& type) override {
+  void OnNewOutputType(const ComPtr<IMFMediaType>& type) {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
     MFVideoArea aperture;
     HRESULT hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
         reinterpret_cast<UINT8*>(&aperture), sizeof(MFVideoArea), nullptr);
@@ -112,7 +125,7 @@
     }
   }
 
-  ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
+  void Configure() {
     ComPtr<IMFMediaType> input_type;
     HRESULT hr = MFCreateMediaType(&input_type);
     SB_DCHECK(SUCCEEDED(hr));
@@ -142,48 +155,21 @@
 
     hr = input_type->SetGUID(MF_MT_SUBTYPE, selected_guid);
     SB_DCHECK(SUCCEEDED(hr));
-    hr = decoder->SetInputType(0, input_type.Get(), 0);
-    return input_type;
-  }
 
-  void EnsureVideoDecoderCreated() SB_OVERRIDE {
-    if (impl_->has_decoder()) {
-      return;
-    }
-
-    ComPtr<IMFTransform> decoder =
-        DecoderImpl::CreateDecoder(CLSID_VideoDecoder);
-
-    ComPtr<IMFMediaType> media_type = Configure(decoder.Get());
-
-    SB_DCHECK(decoder);
-
-    impl_->set_decoder(decoder);
+    MediaTransform& decoder = impl_.GetDecoder();
+    decoder.SetInputType(input_type);
 
     dx_decoder_ctx_ = GetDirectXForHardwareDecoding();
-    SB_UNREFERENCED_PARAMETER(dx_decoder_ctx_);
-
-    HRESULT hr = S_OK;
 
     DWORD input_stream_count = 0;
     DWORD output_stream_count = 0;
-    hr = decoder->GetStreamCount(&input_stream_count, &output_stream_count);
-    CheckResult(hr);
-
+    decoder.GetStreamCount(&input_stream_count, &output_stream_count);
     SB_DCHECK(1 == input_stream_count);
     SB_DCHECK(1 == output_stream_count);
 
-    ComPtr<IMFMediaType> output_type =
-        FindMediaType(MFVideoFormat_YV12, decoder.Get());
+    decoder.SetOutputTypeBySubType(MFVideoFormat_YV12);
 
-    SB_DCHECK(output_type);
-
-    hr = decoder->SetOutputType(0, output_type.Get(), 0);
-    CheckResult(hr);
-
-    ComPtr<IMFAttributes> attributes;
-    hr = decoder->GetAttributes(attributes.GetAddressOf());
-    CheckResult(hr);
+    ComPtr<IMFAttributes> attributes = decoder.GetAttributes();
 
     hr = attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT,
                                kMinimumOutputSampleCount);
@@ -197,106 +183,95 @@
     // SB_DCHECK(value == TRUE);
 
     // Enables DirectX video acceleration for video decoding.
-    hr = decoder->ProcessMessage(
-        MFT_MESSAGE_SET_D3D_MANAGER,
-        ULONG_PTR(dx_decoder_ctx_.dxgi_device_manager_out.Get()));
-    CheckResult(hr);
+    decoder.SendMessage(MFT_MESSAGE_SET_D3D_MANAGER,
+                        reinterpret_cast<ULONG_PTR>(
+                            dx_decoder_ctx_.dxgi_device_manager_out.Get()));
 
     // Only allowed to set once.
     SB_DCHECK(0 == surface_width_);
     SB_DCHECK(0 == surface_height_);
 
+    ComPtr<IMFMediaType> output_type = decoder.GetCurrentOutputType();
+    SB_DCHECK(output_type);
+
     hr = MFGetAttributeSize(output_type.Get(), MF_MT_FRAME_SIZE,
                             &surface_width_, &surface_height_);
-
     if (FAILED(hr)) {
       SB_NOTREACHED() << "could not get width & height, hr = " << hr;
       return;
     }
 
-    ComPtr<IMFAttributes> output_attributes;
-    hr = decoder->GetOutputStreamAttributes(0, &output_attributes);
-    SB_DCHECK(SUCCEEDED(hr));
+    ComPtr<IMFAttributes> output_attributes =
+        decoder.GetOutputStreamAttributes();
     // The decoder must output textures that are bound to shader resources,
     // or we can't draw them later via ANGLE.
     hr = output_attributes->SetUINT32(
         MF_SA_D3D11_BINDFLAGS, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_DECODER);
     SB_DCHECK(SUCCEEDED(hr));
-
-    decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
-    decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
   }
 
   bool TryWrite(const scoped_refptr<InputBuffer>& buff) {
-    std::pair<int, int> width_height(buff->video_sample_info()->frame_width,
-                                     buff->video_sample_info()->frame_height);
-    EnsureVideoDecoderCreated();
-    if (!impl_->has_decoder()) {
-      SB_NOTREACHED();
-      return false;  // TODO: Signal an error.
-    }
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
 
-    if (!output_queue_.IsEmpty()) {
-      return false;  // Wait for more data.
-    }
-
-    std::vector<uint8_t> key_id;
-    std::vector<uint8_t> iv;
-    std::vector<Subsample> subsamples;
-
-    // TODO: Merge this with similar code in the audio decoder.
-    const SbDrmSampleInfo* drm_info = buff->drm_info();
-
-    if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
-      key_id.assign(drm_info->identifier,
-                    drm_info->identifier + drm_info->identifier_size);
-      iv.assign(drm_info->initialization_vector,
-                drm_info->initialization_vector +
-                    drm_info->initialization_vector_size);
-      subsamples.reserve(drm_info->subsample_count);
-      for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
-        Subsample subsample = {
-            static_cast<uint32_t>(
-                drm_info->subsample_mapping[i].clear_byte_count),
-            static_cast<uint32_t>(
-                drm_info->subsample_mapping[i].encrypted_byte_count)};
-        subsamples.push_back(subsample);
-      }
-    }
-
-    const SbMediaTime media_timestamp = buff->pts();
-    const int64_t win32_timestamp = ConvertToWin32Time(media_timestamp);
-
-    const bool write_ok = impl_->TryWriteInputBuffer(
-        buff->data(), buff->size(), win32_timestamp, key_id, iv, subsamples);
-
+    const bool write_ok = impl_.TryWriteInputBuffer(buff, 0);
     return write_ok;
   }
 
   void WriteEndOfStream() SB_OVERRIDE {
-    if (impl_->has_decoder()) {
-      impl_->DrainDecoder();
-      while (VideoFrameFactory::frames_in_flight() <
-                 kSampleAllocatorFramesMax &&
-             impl_->DeliverOneOutputOnAllTransforms()) {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+    impl_.Drain();
+
+    ComPtr<IMFSample> sample;
+    ComPtr<IMFMediaType> media_type;
+    while (VideoFrameFactory::frames_in_flight() < kSampleAllocatorFramesMax &&
+           impl_.ProcessAndRead(&sample, &media_type)) {
+      if (media_type) {
+        OnNewOutputType(media_type);
       }
-    } else {
-      // Don't call DrainDecoder() if input data is never queued.
-      // TODO: Send EOS.
+      if (sample) {
+        Consume(sample);
+      }
     }
   }
 
-  VideoFramePtr ProcessAndRead() SB_OVERRIDE {
-    if (VideoFrameFactory::frames_in_flight() < kSampleAllocatorFramesMax) {
-      impl_->DeliverOneOutputOnAllTransforms();
+  VideoFramePtr ProcessAndRead(bool* too_many_outstanding_frames) SB_OVERRIDE {
+    SB_DCHECK(thread_checker_.CalledOnValidThread());
+    SB_DCHECK(too_many_outstanding_frames);
+
+    *too_many_outstanding_frames =
+        VideoFrameFactory::frames_in_flight() >= kSampleAllocatorFramesMax;
+
+    if (!*too_many_outstanding_frames) {
+      ComPtr<IMFSample> sample;
+      ComPtr<IMFMediaType> media_type;
+      impl_.ProcessAndRead(&sample, &media_type);
+      if (media_type) {
+        OnNewOutputType(media_type);
+      }
+      if (sample) {
+        Consume(sample);
+      }
     }
-    VideoFramePtr output = output_queue_.PopFront();
+    if (output_queue_.empty()) {
+      return NULL;
+    }
+    VideoFramePtr output = output_queue_.front();
+    output_queue_.pop();
     return output;
   }
 
-  AtomicQueue<VideoFramePtr> output_queue_;
-  SbMediaVideoCodec codec_;
-  scoped_ptr<DecoderImpl> impl_;
+  void Reset() SB_OVERRIDE {
+    impl_.Reset();
+    std::queue<VideoFramePtr> empty;
+    output_queue_.swap(empty);
+    thread_checker_.Detach();
+  }
+
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+  std::queue<VideoFramePtr> output_queue_;
+  const SbMediaVideoCodec codec_;
+  DecryptingDecoder impl_;
 
   uint32_t surface_width_;
   uint32_t surface_height_;
diff --git a/src/starboard/shared/win32/win32_video_decoder.h b/src/starboard/shared/win32/win32_video_decoder.h
index a32158b..643c34d 100644
--- a/src/starboard/shared/win32/win32_video_decoder.h
+++ b/src/starboard/shared/win32/win32_video_decoder.h
@@ -40,10 +40,8 @@
 
   virtual bool TryWrite(const scoped_refptr<InputBuffer>& buff) = 0;
   virtual void WriteEndOfStream() = 0;
-  virtual VideoFramePtr ProcessAndRead() = 0;
-
- private:
-  virtual void EnsureVideoDecoderCreated() = 0;
+  virtual VideoFramePtr ProcessAndRead(bool* too_many_outstanding_frames) = 0;
+  virtual void Reset() = 0;
 };
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/wrm_header.cc b/src/starboard/shared/win32/wrm_header.cc
new file mode 100644
index 0000000..97106fb
--- /dev/null
+++ b/src/starboard/shared/win32/wrm_header.cc
@@ -0,0 +1,395 @@
+// 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/wrm_header.h"
+
+#include <guiddef.h>
+#include <initguid.h>
+
+#include <algorithm>
+
+#include "starboard/byte_swap.h"
+#include "starboard/memory.h"
+#include "starboard/shared/win32/error_utils.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+using Microsoft::WRL::ComPtr;
+
+namespace {
+
+// The playready system id used to create the content header.
+DEFINE_GUID(kPlayreadyContentHeaderCLSID,
+            0xF4637010,
+            0x03C3,
+            0x42CD,
+            0xB9,
+            0x32,
+            0xB4,
+            0x8A,
+            0xDF,
+            0x3A,
+            0x6A,
+            0x54);
+
+// The playready system id used to identify the wrm header in the
+// initialization data.
+DEFINE_GUID(kPlayreadyInitializationDataCLSID,
+            0x79f0049a,
+            0x4098,
+            0x8642,
+            0xab,
+            0x92,
+            0xe6,
+            0x5b,
+            0xe0,
+            0x88,
+            0x5f,
+            0x95);
+
+const uint16_t kPlayreadyWRMTag = 0x0001;
+
+class Reader {
+ public:
+  Reader(const uint8_t* data, size_t length)
+      : start_(data), curr_(data), end_(data + length) {}
+
+  size_t GetPosition() const { return curr_ - start_; }
+  size_t GetRemaining() const { return end_ - curr_; }
+
+  const uint8_t* curr() const { curr_; }
+
+  bool Skip(size_t distance) {
+    if (distance > GetRemaining())
+      return false;
+    curr_ += distance;
+    return true;
+  }
+
+  bool ReadGUID(GUID* guid) {
+    if (sizeof(*guid) > GetRemaining()) {
+      return false;
+    }
+    SbMemoryCopy(guid, curr_, sizeof(*guid));
+    curr_ += sizeof(*guid);
+    return true;
+  }
+
+  bool ReadBigEndianU32(uint32_t* i) {
+    if (sizeof(*i) > GetRemaining())
+      return false;
+    *i =
+        curr_[0] * 0x1000000 + curr_[1] * 0x10000 + curr_[2] * 0x100 + curr_[3];
+    curr_ += sizeof(*i);
+    return true;
+  }
+
+  bool ReadLittleEndianU16(uint16_t* i) {
+    if (sizeof(*i) > GetRemaining())
+      return false;
+    *i = curr_[0] + curr_[1] * 0x100;
+    curr_ += sizeof(*i);
+    return true;
+  }
+
+ private:
+  const uint8_t* const start_;
+  const uint8_t* curr_;
+  const uint8_t* const end_;
+};
+
+std::vector<uint8_t> ParseWrmHeaderFromInitializationData(
+    const void* initialization_data,
+    int initialization_data_size) {
+  if (initialization_data_size == 0) {
+    SB_NOTIMPLEMENTED();
+    return std::vector<uint8_t>();
+  }
+
+  std::vector<uint8_t> output;
+  const uint8_t* data = static_cast<const uint8_t*>(initialization_data);
+
+  Reader reader(data, initialization_data_size);
+  while (reader.GetRemaining() > 0) {
+    // Parse pssh atom (big endian)
+    //
+    // 4 bytes -- size
+    // 4 bytes -- "pssh"
+    // 4 bytes -- flags
+    // 16 bytes -- guid
+    uint32_t pssh_size;
+    if (!reader.ReadBigEndianU32(&pssh_size)) {
+      return output;
+    }
+
+    // Skipping pssh and flags
+    if (!reader.Skip(8)) {
+      return output;
+    }
+    GUID system_id;
+    if (!reader.ReadGUID(&system_id)) {
+      return output;
+    }
+    if (system_id != kPlayreadyInitializationDataCLSID) {
+      // Skip entire pssh atom
+      if (!reader.Skip(pssh_size - 28)) {
+        return output;
+      }
+      continue;
+    }
+
+    // 4 bytes -- size of PlayreadyObject
+    // followed by PlayreadyObject
+
+    // Skip size, and continue parsing
+    if (!reader.Skip(4)) {
+      return output;
+    }
+
+    // Parse Playready object (little endian)
+    // 4 bytes -- size
+    // 2 bytes -- record count
+    //
+    // Playready Record
+    // 2 bytes -- type
+    // 2 bytes -- size of record
+    // n bytes -- record
+    if (!reader.Skip(4)) {
+      return output;
+    }
+    uint16_t num_records;
+    if (!reader.ReadLittleEndianU16(&num_records)) {
+      return output;
+    }
+
+    for (int i = 0; i < num_records; i++) {
+      uint16_t record_type;
+      if (!reader.ReadLittleEndianU16(&record_type)) {
+        return output;
+      }
+      uint16_t record_size;
+      if (!reader.ReadLittleEndianU16(&record_size)) {
+        return output;
+      }
+      if ((record_type & kPlayreadyWRMTag) == kPlayreadyWRMTag) {
+        std::copy(data + reader.GetPosition(),
+                  data + reader.GetPosition() + record_size,
+                  std::back_inserter(output));
+        return output;
+      }
+      if (!reader.Skip(record_size)) {
+        return output;
+      }
+    }
+  }
+
+  return output;
+}
+
+uint32_t Base64ToValue(uint8_t byte) {
+  if (byte >= 'A' && byte <= 'Z') {
+    return byte - 'A';
+  }
+  if (byte >= 'a' && byte <= 'z') {
+    return byte - 'a' + 26;
+  }
+  if (byte >= '0' && byte <= '9') {
+    return byte - '0' + 52;
+  }
+  if (byte == '+') {
+    return 62;
+  }
+  if (byte == '/') {
+    return 63;
+  }
+  SB_DCHECK(byte == '=');
+  return 0;
+}
+
+std::string Base64Decode(const std::wstring& input) {
+  SB_DCHECK(input.size() % 4 == 0);
+
+  std::string output;
+
+  output.reserve(input.size() / 4 * 3);
+  for (size_t i = 0; i < input.size() - 3; i += 4) {
+    uint32_t decoded =
+        Base64ToValue(input[i]) * 4 + Base64ToValue(input[i + 1]) / 16;
+    output += static_cast<char>(decoded);
+    if (input[i + 2] != '=') {
+      decoded = Base64ToValue(input[i + 1]) % 16 * 16 +
+                Base64ToValue(input[i + 2]) / 4;
+      output += static_cast<char>(decoded);
+      if (input[i + 3] != '=') {
+        decoded =
+            Base64ToValue(input[i + 2]) % 4 * 64 + Base64ToValue(input[i + 3]);
+        output += static_cast<char>(decoded);
+      }
+    }
+  }
+
+  return output;
+}
+
+GUID ParseKeyIdFromWrmHeader(const std::vector<uint8_t>& wrm_header) {
+  // The wrm_header is an XML in wchar_t that contains the base64 encoded key
+  // id in <KID> node in base64.  The original key id should be 16 characters,
+  // so it is 24 characters after based64 encoded.
+  const size_t kEncodedKeyIdLength = 24;
+
+  SB_DCHECK(wrm_header.size() % 2 == 0);
+  std::wstring wrm_header_copy(
+      reinterpret_cast<const wchar_t*>(wrm_header.data()),
+      wrm_header.size() / 2);
+  std::wstring::size_type begin = wrm_header_copy.find(L"<KID>");
+  if (begin == wrm_header_copy.npos) {
+    return WrmHeader::kInvalidKeyId;
+  }
+  std::wstring::size_type end = wrm_header_copy.find(L"</KID>", begin);
+  if (end == wrm_header_copy.npos) {
+    return WrmHeader::kInvalidKeyId;
+  }
+  begin += 5;
+  if (end - begin != kEncodedKeyIdLength) {
+    return WrmHeader::kInvalidKeyId;
+  }
+
+  std::string key_id_in_string =
+      Base64Decode(wrm_header_copy.substr(begin, kEncodedKeyIdLength));
+  if (key_id_in_string.size() != sizeof(GUID)) {
+    return WrmHeader::kInvalidKeyId;
+  }
+
+  GUID key_id = *reinterpret_cast<const GUID*>(key_id_in_string.data());
+
+  key_id.Data1 = SbByteSwap(key_id.Data1);
+  key_id.Data2 = SbByteSwap(key_id.Data2);
+  key_id.Data3 = SbByteSwap(key_id.Data3);
+
+  return key_id;
+}
+
+ComPtr<IStream> CreateWrmHeaderStream(const std::vector<uint8_t>& wrm_header) {
+  ComPtr<IStream> stream;
+  HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  DWORD wrm_header_size = static_cast<DWORD>(wrm_header.size());
+  ULONG bytes_written = 0;
+
+  if (wrm_header_size != 0) {
+    hr = stream->Write(wrm_header.data(), wrm_header_size, &bytes_written);
+    if (FAILED(hr)) {
+      return NULL;
+    }
+  }
+
+  return stream;
+}
+
+ComPtr<IStream> CreateContentHeaderFromWrmHeader(
+    const std::vector<uint8_t>& wrm_header) {
+  ComPtr<IStream> content_header;
+  // Assume we use one license for one stream.
+  const DWORD kNumStreams = 1;
+  const DWORD kNextStreamId = static_cast<DWORD>(-1);
+
+  HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &content_header);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  // Initialize spInitStm with the required data
+  // Format: (All DWORD values are serialized in little-endian order)
+  // [GUID (content protection system guid, see msprita.idl)]
+  // [DWORD (stream count, use the actual stream count even if all streams are
+  // encrypted using the same data, note that zero is invalid)] [DWORD (next
+  // stream ID, use -1 if all remaining streams are encrypted using the same
+  // data)] [DWORD (next stream's binary data size)] [BYTE* (next stream's
+  // binary data)] { Repeat from "next stream ID" above for each stream }
+  DWORD wrm_header_size = static_cast<DWORD>(wrm_header.size());
+  ULONG bytes_written = 0;
+  hr = content_header->Write(&kPlayreadyContentHeaderCLSID,
+                             sizeof(kPlayreadyContentHeaderCLSID),
+                             &bytes_written);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  hr = content_header->Write(&kNumStreams, sizeof(kNumStreams), &bytes_written);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  hr = content_header->Write(&kNextStreamId, sizeof(kNextStreamId),
+                             &bytes_written);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  hr = content_header->Write(&wrm_header_size, sizeof(wrm_header_size),
+                             &bytes_written);
+  if (FAILED(hr)) {
+    return NULL;
+  }
+
+  if (0 != wrm_header_size) {
+    hr = content_header->Write(wrm_header.data(), wrm_header_size,
+                               &bytes_written);
+    if (FAILED(hr)) {
+      return NULL;
+    }
+  }
+
+  return content_header;
+}
+
+ComPtr<IStream> ResetStreamPosition(const ComPtr<IStream>& stream) {
+  if (stream == NULL) {
+    return NULL;
+  }
+  LARGE_INTEGER seek_position = {0};
+  HRESULT hr = stream->Seek(seek_position, STREAM_SEEK_SET, NULL);
+  CheckResult(hr);
+  return stream;
+}
+
+}  // namespace.
+
+// static
+GUID WrmHeader::kInvalidKeyId;
+
+WrmHeader::WrmHeader(const void* initialization_data,
+                     int initialization_data_size) {
+  wrm_header_ = ParseWrmHeaderFromInitializationData(initialization_data,
+                                                     initialization_data_size);
+  key_id_ = ParseKeyIdFromWrmHeader(wrm_header_);
+}
+
+ComPtr<IStream> WrmHeader::content_header() const {
+  SB_DCHECK(is_valid());
+
+  if (key_id_ == kInvalidKeyId) {
+    return NULL;
+  }
+  return ResetStreamPosition(CreateContentHeaderFromWrmHeader(wrm_header_));
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/wrm_header.h b/src/starboard/shared/win32/wrm_header.h
new file mode 100644
index 0000000..af248d2
--- /dev/null
+++ b/src/starboard/shared/win32/wrm_header.h
@@ -0,0 +1,57 @@
+// 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_WRM_HEADER_H_
+#define STARBOARD_SHARED_WIN32_WRM_HEADER_H_
+
+#include <objidl.h>
+#include <wrl.h>
+#include <wrl/client.h>
+
+#include <string>
+#include <vector>
+
+#include "starboard/log.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class WrmHeader {
+ public:
+  WrmHeader(const void* initialization_data, int initialization_data_size);
+
+  static GUID kInvalidKeyId;
+
+  bool is_valid() const {
+    return key_id_ != kInvalidKeyId && !wrm_header_.empty();
+  }
+  const GUID& key_id() const {
+    SB_DCHECK(is_valid());
+    return key_id_;
+  }
+  Microsoft::WRL::ComPtr<IStream> content_header() const;
+  const std::vector<uint8_t>& wrm_header() const { return wrm_header_; }
+
+ private:
+  GUID key_id_;
+  std::vector<uint8_t> wrm_header_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_WRM_HEADER_H_
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi
index 43ec50b..6a20b46 100644
--- a/src/starboard/win/shared/starboard_platform.gypi
+++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -15,17 +15,6 @@
   'variables': {
     'sb_pedantic_warnings': 1,
     'winrt%': 1,
-    'stub_media_player': [
-    ],
-
-    'stub_drm_system': [
-      '<(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',
-    ],
 
     # TODO: Move this and the win32 dependencies below to a shared/win32/starboard_platform.gypi?
     'uwp_incompatible_win32': [
@@ -33,6 +22,7 @@
       '<(DEPTH)/starboard/shared/win32/application_win32.cc',
       '<(DEPTH)/starboard/shared/win32/dialog.cc',
       '<(DEPTH)/starboard/shared/win32/get_home_directory.cc',
+      '<(DEPTH)/starboard/shared/win32/playready_license.cc',
       '<(DEPTH)/starboard/shared/win32/starboard_main.cc',
       '<(DEPTH)/starboard/shared/win32/system_clear_platform_error.cc',
       '<(DEPTH)/starboard/shared/win32/system_get_device_type.cc',
@@ -48,6 +38,19 @@
       '<(DEPTH)/starboard/shared/win32/window_intsdfdsfernal.h',
       '<(DEPTH)/starboard/shared/win32/window_set_default_options.cc',
     ],
+    'win32_shared_drm_files': [
+      '<(DEPTH)/starboard/shared/starboard/drm/drm_close_session.cc',
+      '<(DEPTH)/starboard/shared/starboard/drm/drm_destroy_system.cc',
+      '<(DEPTH)/starboard/shared/starboard/drm/drm_generate_session_update_request.cc',
+      '<(DEPTH)/starboard/shared/starboard/drm/drm_system_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/drm/drm_update_session.cc',
+
+      '<(DEPTH)/starboard/shared/win32/drm_create_system.cc',
+      '<(DEPTH)/starboard/shared/win32/drm_system_playready.cc',
+      '<(DEPTH)/starboard/shared/win32/drm_system_playready.h',
+      '<(DEPTH)/starboard/shared/win32/wrm_header.cc',
+      '<(DEPTH)/starboard/shared/win32/wrm_header.h',
+    ],
     'win32_media_player_files': [
       '<(DEPTH)/starboard/shared/win32/atomic_queue.h',
       '<(DEPTH)/starboard/shared/win32/audio_decoder.cc',
@@ -56,6 +59,8 @@
       '<(DEPTH)/starboard/shared/win32/audio_decoder_thread.h',
       '<(DEPTH)/starboard/shared/win32/decode_target_internal.cc',
       '<(DEPTH)/starboard/shared/win32/decode_target_internal.h',
+      '<(DEPTH)/starboard/shared/win32/decrypting_decoder.cc',
+      '<(DEPTH)/starboard/shared/win32/decrypting_decoder.h',
       '<(DEPTH)/starboard/shared/win32/dx_context_video_decoder.cc',
       '<(DEPTH)/starboard/shared/win32/dx_context_video_decoder.h',
       '<(DEPTH)/starboard/shared/win32/media_common.cc',
@@ -65,6 +70,8 @@
       '<(DEPTH)/starboard/shared/win32/media_is_audio_supported.cc',
       '<(DEPTH)/starboard/shared/win32/media_is_supported.cc',
       '<(DEPTH)/starboard/shared/win32/media_is_video_supported.cc',
+      '<(DEPTH)/starboard/shared/win32/media_transform.cc',
+      '<(DEPTH)/starboard/shared/win32/media_transform.h',
       '<(DEPTH)/starboard/shared/win32/player_components_impl.cc',
       '<(DEPTH)/starboard/shared/win32/simple_thread.cc',
       '<(DEPTH)/starboard/shared/win32/simple_thread.h',
@@ -76,8 +83,6 @@
       '<(DEPTH)/starboard/shared/win32/video_renderer.h',
       '<(DEPTH)/starboard/shared/win32/win32_audio_decoder.cc',
       '<(DEPTH)/starboard/shared/win32/win32_audio_decoder.h',
-      '<(DEPTH)/starboard/shared/win32/win32_decoder_impl.cc',
-      '<(DEPTH)/starboard/shared/win32/win32_decoder_impl.h',
       '<(DEPTH)/starboard/shared/win32/win32_video_decoder.cc',
       '<(DEPTH)/starboard/shared/win32/win32_video_decoder.h',
     ],
@@ -125,6 +130,7 @@
     ],
     'starboard_platform_dependent_files': [
       '<@(win32_media_player_files)',
+      '<@(win32_shared_drm_files)',
       '<@(win32_shared_media_player_files)',
     ]
   },
@@ -253,11 +259,9 @@
         '<(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_symbolize.cc',
         '<(DEPTH)/starboard/shared/stub/time_zone_get_dst_name.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',
@@ -366,6 +370,7 @@
         '<(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/system_is_debugger_attached.cc',
         '<(DEPTH)/starboard/shared/win32/thread_create.cc',
         '<(DEPTH)/starboard/shared/win32/thread_create_local_key.cc',
         '<(DEPTH)/starboard/shared/win32/thread_detach.cc',
diff --git a/src/starboard/win/win32/starboard_platform.gyp b/src/starboard/win/win32/starboard_platform.gyp
index 2027d45..c2fda5c 100644
--- a/src/starboard/win/win32/starboard_platform.gyp
+++ b/src/starboard/win/win32/starboard_platform.gyp
@@ -1,24 +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.

-{

-  'includes': [

-    'starboard_platform.gypi',

-  ],

-  'variables': {

-    'starboard_platform_dependent_files': [

-      'main.cc',

-      '<@(base_win32_starboard_platform_dependent_files)',

-    ]

-  },

-}

+# 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.
+{
+  'includes': [
+    'starboard_platform.gypi',
+  ],
+  'variables': {
+    'starboard_platform_dependent_files': [
+      'main.cc',
+      '<@(base_win32_starboard_platform_dependent_files)',
+    ]
+  },
+}
diff --git a/src/starboard/win/win32/starboard_platform.gypi b/src/starboard/win/win32/starboard_platform.gypi
index 254ae7b..2335dbd 100644
--- a/src/starboard/win/win32/starboard_platform.gypi
+++ b/src/starboard/win/win32/starboard_platform.gypi
@@ -35,8 +35,6 @@
       '<(DEPTH)/starboard/shared/stub/decode_target_release.cc',
       '<(DEPTH)/starboard/win/shared/system_get_path.cc',
       '<@(uwp_incompatible_win32)',
-      '<@(stub_media_player)',
-      '<@(stub_drm_system)' ,
     ],
   },
 }