Import Cobalt 12.84472

Change-Id: I5b2e0e0f08b03fa9a5e428a4fb0618e00718d3eb
diff --git a/src/base/allocator/allocator.gyp b/src/base/allocator/allocator.gyp
index 7ab45bd..3c62f47 100644
--- a/src/base/allocator/allocator.gyp
+++ b/src/base/allocator/allocator.gyp
@@ -30,18 +30,6 @@
         'NO_HEAP_CHECK',
       ],
       'direct_dependent_settings': {
-        'configurations': {
-          'Common_Base': {
-            'msvs_settings': {
-              'VCLinkerTool': {
-                'IgnoreDefaultLibraryNames': ['libcmtd.lib', 'libcmt.lib'],
-                'AdditionalDependencies': [
-                  '<(SHARED_INTERMEDIATE_DIR)/allocator/libcmt.lib'
-                ],
-              },
-            },
-          },
-        },
         'conditions': [
           ['OS=="win"', {
             'defines': [
@@ -530,11 +518,6 @@
           'include_dirs': [
             '../../'
           ],
-          'configurations': {
-            'Common_Base': {
-              'msvs_target_platform': 'x64',
-            },
-          },
         },
       {
         'target_name': 'tcmalloc_unittest',
diff --git a/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp b/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp
index 1f1dc49..4021102 100644
--- a/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp
+++ b/src/base/third_party/dynamic_annotations/dynamic_annotations.gyp
@@ -35,11 +35,6 @@
             'dynamic_annotations.c',
             'dynamic_annotations.h',
           ],
-          'configurations': {
-            'Common_Base': {
-              'msvs_target_platform': 'x64',
-            },
-          },
         },
       ],
     }],
diff --git a/src/build/common.gypi b/src/build/common.gypi
index e41bd74..901b1eb 100644
--- a/src/build/common.gypi
+++ b/src/build/common.gypi
@@ -421,248 +421,5 @@
         ],
       }],
     ],  # target_conditions for 'target_defaults'
-    'configurations': {
-      # VCLinkerTool LinkIncremental values below:
-      #   0 == default
-      #   1 == /INCREMENTAL:NO
-      #   2 == /INCREMENTAL
-      # Debug links incremental, Release does not.
-      #
-      # Abstract base configurations to cover common attributes.
-      #
-      'Common_Base': {
-        'abstract': 1,
-        'msvs_configuration_attributes': {
-          'OutputDirectory': '<(DEPTH)\\build\\<(build_dir_prefix)$(ConfigurationName)',
-          'IntermediateDirectory': '$(OutDir)\\obj\\$(ProjectName)',
-          'CharacterSet': '1',
-        },
-      },
-      'x86_Base': {
-        'abstract': 1,
-        'msvs_settings': {
-          'VCLinkerTool': {
-            'TargetMachine': '1',
-          },
-        },
-        'msvs_configuration_platform': 'Win32',
-      },
-      'x64_Base': {
-        'abstract': 1,
-        'msvs_configuration_platform': 'x64',
-        'msvs_settings': {
-          'VCLinkerTool': {
-            'TargetMachine': '17', # x86 - 64
-            'AdditionalLibraryDirectories!':
-              ['<(windows_sdk_path)/Lib/win8/um/x86'],
-            'AdditionalLibraryDirectories':
-              ['<(windows_sdk_path)/Lib/win8/um/x64'],
-          },
-          'VCLibrarianTool': {
-            'AdditionalLibraryDirectories!':
-              ['<(windows_sdk_path)/Lib/win8/um/x86'],
-            'AdditionalLibraryDirectories':
-              ['<(windows_sdk_path)/Lib/win8/um/x64'],
-          },
-        },
-        'defines': [
-          # Not sure if tcmalloc works on 64-bit Windows.
-          'NO_TCMALLOC',
-        ],
-      },
-      'Debug_Base': {
-        'abstract': 1,
-        'defines': [
-          'DYNAMIC_ANNOTATIONS_ENABLED=1',
-          'WTF_USE_DYNAMIC_ANNOTATIONS=1',
-        ],
-        'xcode_settings': {
-          'COPY_PHASE_STRIP': 'NO',
-          'GCC_OPTIMIZATION_LEVEL': '<(mac_debug_optimization)',
-          'OTHER_CFLAGS': [
-            '<@(debug_extra_cflags)',
-          ],
-        },
-        'msvs_settings': {
-          'VCCLCompilerTool': {
-            'Optimization': '<(win_debug_Optimization)',
-            'PreprocessorDefinitions': ['_DEBUG'],
-            'BasicRuntimeChecks': '<(win_debug_RuntimeChecks)',
-            'RuntimeLibrary': '<(win_debug_RuntimeLibrary)',
-            'conditions': [
-              # According to MSVS, InlineFunctionExpansion=0 means
-              # "default inlining", not "/Ob0".
-              # Thus, we have to handle InlineFunctionExpansion==0 separately.
-              ['win_debug_InlineFunctionExpansion==0', {
-                'AdditionalOptions': ['/Ob0'],
-              }],
-              ['win_debug_InlineFunctionExpansion!=""', {
-                'InlineFunctionExpansion':
-                  '<(win_debug_InlineFunctionExpansion)',
-              }],
-              ['win_debug_disable_iterator_debugging==1', {
-                'PreprocessorDefinitions': ['_HAS_ITERATOR_DEBUGGING=0'],
-              }],
-
-              # if win_debug_OmitFramePointers is blank, leave as default
-              ['win_debug_OmitFramePointers==1', {
-                'OmitFramePointers': 'true',
-              }],
-              ['win_debug_OmitFramePointers==0', {
-                'OmitFramePointers': 'false',
-                # The above is not sufficient (http://crbug.com/106711): it
-                # simply eliminates an explicit "/Oy", but both /O2 and /Ox
-                # perform FPO regardless, so we must explicitly disable.
-                # We still want the false setting above to avoid having
-                # "/Oy /Oy-" and warnings about overriding.
-                'AdditionalOptions': ['/Oy-'],
-              }],
-            ],
-            'AdditionalOptions': [ '<@(win_debug_extra_cflags)', ],
-          },
-          'VCLinkerTool': {
-            'LinkIncremental': '<(msvs_debug_link_incremental)',
-            # ASLR makes debugging with windbg difficult because Chrome.exe and
-            # Chrome.dll share the same base name. As result, windbg will
-            # name the Chrome.dll module like chrome_<base address>, where
-            # <base address> typically changes with each launch. This in turn
-            # means that breakpoints in Chrome.dll don't stick from one launch
-            # to the next. For this reason, we turn ASLR off in debug builds.
-            # Note that this is a three-way bool, where 0 means to pick up
-            # the default setting, 1 is off and 2 is on.
-            'RandomizedBaseAddress': 1,
-          },
-          'VCResourceCompilerTool': {
-            'PreprocessorDefinitions': ['_DEBUG'],
-          },
-        },
-        'conditions': [
-          # Disabled on iOS because it was causing a crash on startup.
-          # TODO(michelea): investigate, create a reduced test and possibly
-          # submit a radar.
-          ['release_valgrind_build==0 and OS!="ios"', {
-            'xcode_settings': {
-              'OTHER_CFLAGS': [
-                '-fstack-protector-all',  # Implies -fstack-protector
-              ],
-            },
-          }],
-        ],
-      },
-      'Release_Base': {
-        'abstract': 1,
-        'defines': [
-          'NDEBUG',
-        ],
-        'xcode_settings': {
-          'DEAD_CODE_STRIPPING': 'YES',  # -Wl,-dead_strip
-          'GCC_OPTIMIZATION_LEVEL': '<(mac_release_optimization)',
-          'OTHER_CFLAGS': [ '<@(release_extra_cflags)', ],
-        },
-        'msvs_settings': {
-          'VCCLCompilerTool': {
-            'RuntimeLibrary': '<(win_release_RuntimeLibrary)',
-            'Optimization': '<(win_release_Optimization)',
-            'conditions': [
-              # According to MSVS, InlineFunctionExpansion=0 means
-              # "default inlining", not "/Ob0".
-              # Thus, we have to handle InlineFunctionExpansion==0 separately.
-              ['win_release_InlineFunctionExpansion==0', {
-                'AdditionalOptions': ['/Ob0'],
-              }],
-              ['win_release_InlineFunctionExpansion!=""', {
-                'InlineFunctionExpansion':
-                  '<(win_release_InlineFunctionExpansion)',
-              }],
-
-              # if win_release_OmitFramePointers is blank, leave as default
-              ['win_release_OmitFramePointers==1', {
-                'OmitFramePointers': 'true',
-              }],
-              ['win_release_OmitFramePointers==0', {
-                'OmitFramePointers': 'false',
-                # The above is not sufficient (http://crbug.com/106711): it
-                # simply eliminates an explicit "/Oy", but both /O2 and /Ox
-                # perform FPO regardless, so we must explicitly disable.
-                # We still want the false setting above to avoid having
-                # "/Oy /Oy-" and warnings about overriding.
-                'AdditionalOptions': ['/Oy-'],
-              }],
-            ],
-            'AdditionalOptions': [ '<@(win_release_extra_cflags)', ],
-          },
-          'VCLinkerTool': {
-            # LinkIncremental is a tri-state boolean, where 0 means default
-            # (i.e., inherit from parent solution), 1 means false, and
-            # 2 means true.
-            'LinkIncremental': '1',
-            # This corresponds to the /PROFILE flag which ensures the PDB
-            # file contains FIXUP information (growing the PDB file by about
-            # 5%) but does not otherwise alter the output binary. This
-            # information is used by the Syzygy optimization tool when
-            # decomposing the release image.
-            'Profile': 'true',
-          },
-        },
-        'conditions': [
-          ['release_valgrind_build==0 and tsan==0', {
-            'defines': [
-              'NVALGRIND',
-              'DYNAMIC_ANNOTATIONS_ENABLED=0',
-            ],
-          }, {
-            'defines': [
-              'DYNAMIC_ANNOTATIONS_ENABLED=1',
-              'WTF_USE_DYNAMIC_ANNOTATIONS=1',
-            ],
-          }],
-        ],
-      },
-    },
-  },
-  'conditions': [
-    ['clang==1', {
-      'conditions': [
-        ['OS=="android"', {
-          # Android could use the goma with clang.
-          'make_global_settings': [
-            ['CC', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang)'],
-            ['CXX', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang++)'],
-            ['LINK', '<!(/bin/echo -n ${ANDROID_GOMA_WRAPPER} ${CHROME_SRC}/<(make_clang_dir)/bin/clang++)'],
-            ['CC.host', '$(CC)'],
-            ['CXX.host', '$(CXX)'],
-            ['LINK.host', '$(LINK)'],
-          ],
-        }, {
-          'make_global_settings': [
-            ['CC', '<(make_clang_dir)/bin/clang'],
-            ['CXX', '<(make_clang_dir)/bin/clang++'],
-            ['LINK', '$(CXX)'],
-            ['CC.host', '$(CC)'],
-            ['CXX.host', '$(CXX)'],
-            ['LINK.host', '$(LINK)'],
-          ],
-        }],
-      ],
-    }],
-  ],
-  'xcode_settings': {
-    # DON'T ADD ANYTHING NEW TO THIS BLOCK UNLESS YOU REALLY REALLY NEED IT!
-    # This block adds *project-wide* configuration settings to each project
-    # file.  It's almost always wrong to put things here.  Specify your
-    # custom xcode_settings in target_defaults to add them to targets instead.
-
-    # The Xcode generator will look for an xcode_settings section at the root
-    # of each dict and use it to apply settings on a file-wide basis.  Most
-    # settings should not be here, they should be in target-specific
-    # xcode_settings sections, or better yet, should use non-Xcode-specific
-    # settings in target dicts.  SYMROOT is a special case, because many other
-    # Xcode variables depend on it, including variables such as
-    # PROJECT_DERIVED_FILE_DIR.  When a source group corresponding to something
-    # like PROJECT_DERIVED_FILE_DIR is added to a project, in order for the
-    # files to appear (when present) in the UI as actual files and not red
-    # red "missing file" proxies, the correct path to PROJECT_DERIVED_FILE_DIR,
-    # and therefore SYMROOT, needs to be set at the project level.
-    'SYMROOT': '<(DEPTH)/xcodebuild',
   },
 }
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 1cbd099..2abcfc8 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -3,6 +3,13 @@
 This document records all notable changes made to Cobalt since the last release.
 
 ## Version 11
+ - **Splash Screen Customization**
+
+   The Cobalt splash screen is customizable. Documents may use a link element
+   with attribute rel="splashscreen" to reference the splash screen which will
+   be cached if local cache is implemented on the platform. Additionally
+   fallbacks may be specified via command line parmeter or gypi variable.
+   For more information, see [doc/splash_screen.md](doc/splash_screen.md).
 
  - **Introduce C\+\+11**
 
diff --git a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_single_operation_interface.cc b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_single_operation_interface.cc
index bb92da0..6f5cd15 100644
--- a/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_single_operation_interface.cc
+++ b/src/cobalt/bindings/generated/mozjs45/testing/cobalt/bindings/testing/mozjs_single_operation_interface.cc
@@ -28,6 +28,7 @@
 #include "cobalt/script/mozjs-45/conversion_helpers.h"
 #include "cobalt/script/mozjs-45/mozjs_callback_interface.h"
 #include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
 #include "third_party/mozjs-45/js/src/jscntxt.h"
 
@@ -68,6 +69,7 @@
   base::optional<int32_t > cobalt_return_value;
   JSAutoRequest auto_request(context_);
   JS::AutoSaveExceptionState auto_save_exception_state(context_);
+  ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
 
   // This could be set to NULL if it was garbage collected.
   JS::RootedObject implementing_object(context_, implementing_object_.GetObject());
diff --git a/src/cobalt/bindings/mozjs45/templates/callback-interface.cc.template b/src/cobalt/bindings/mozjs45/templates/callback-interface.cc.template
index 129ff1c..9104deb 100644
--- a/src/cobalt/bindings/mozjs45/templates/callback-interface.cc.template
+++ b/src/cobalt/bindings/mozjs45/templates/callback-interface.cc.template
@@ -23,6 +23,7 @@
 #include "cobalt/script/mozjs-45/conversion_helpers.h"
 #include "cobalt/script/mozjs-45/mozjs_callback_interface.h"
 #include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
 #include "third_party/mozjs-45/js/src/jscntxt.h"
 {% endblock includes %}
@@ -67,6 +68,7 @@
 {% endif %}
   JSAutoRequest auto_request(context_);
   JS::AutoSaveExceptionState auto_save_exception_state(context_);
+  ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
 
   // This could be set to NULL if it was garbage collected.
   JS::RootedObject implementing_object(context_, implementing_object_.GetObject());
diff --git a/src/cobalt/bindings/testing/stack_trace_test.cc b/src/cobalt/bindings/testing/stack_trace_test.cc
index 8dbdb37..b900b72 100644
--- a/src/cobalt/bindings/testing/stack_trace_test.cc
+++ b/src/cobalt/bindings/testing/stack_trace_test.cc
@@ -32,7 +32,7 @@
   // 11).
   std::string script =
       "function bar() {\n"
-      "return getStackTrace();\n"
+      "  return getStackTrace();\n"
       "}\n"
       "function foo(depth) {\n"
       "  if  (depth <= 0) {\n"
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 3d88ac6..d97bbbb 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -147,26 +147,27 @@
   return GURL(kDefaultURL);
 }
 
-base::optional<GURL> GetSplashScreenURL() {
+base::optional<GURL> GetFallbackSplashScreenURL() {
   CommandLine* command_line = CommandLine::ForCurrentProcess();
-  std::string splash_screen_string;
+  std::string fallback_splash_screen_string;
   if (command_line->HasSwitch(switches::kFallbackSplashScreenURL)) {
-    splash_screen_string =
+    fallback_splash_screen_string =
         command_line->GetSwitchValueASCII(switches::kFallbackSplashScreenURL);
   } else {
-    splash_screen_string = COBALT_FALLBACK_SPLASH_SCREEN_URL;
+    fallback_splash_screen_string = COBALT_FALLBACK_SPLASH_SCREEN_URL;
   }
-  if (IsStringNone(splash_screen_string)) {
+  if (IsStringNone(fallback_splash_screen_string)) {
     return base::optional<GURL>();
   }
-  base::optional<GURL> splash_screen_url = GURL(splash_screen_string);
-  if (!splash_screen_url->is_valid() ||
-      !(StartsWithASCII(splash_screen_string, "file:///", false) ||
-        StartsWithASCII(splash_screen_string, "h5vcc-embedded://", false))) {
+  base::optional<GURL> fallback_splash_screen_url =
+      GURL(fallback_splash_screen_string);
+  if (!fallback_splash_screen_url->is_valid() ||
+      !(fallback_splash_screen_url->SchemeIsFile() ||
+        fallback_splash_screen_url->SchemeIs("h5vcc-embedded"))) {
     LOG(FATAL) << "Ignoring invalid fallback splash screen: "
-               << splash_screen_string;
+               << fallback_splash_screen_string;
   }
-  return splash_screen_url;
+  return fallback_splash_screen_url;
 }
 
 base::TimeDelta GetTimedTraceDuration() {
@@ -449,10 +450,12 @@
   GURL initial_url = GetInitialURL();
   DLOG(INFO) << "Initial URL: " << initial_url;
 
-  // Get the splash screen URL.
-  base::optional<GURL> splash_screen_url = GetSplashScreenURL();
-  DLOG(INFO) << "Splash screen URL: "
-             << (splash_screen_url ? splash_screen_url->spec() : "none");
+  // Get the fallback splash screen URL.
+  base::optional<GURL> fallback_splash_screen_url =
+      GetFallbackSplashScreenURL();
+  DLOG(INFO) << "Fallback splash screen URL: "
+             << (fallback_splash_screen_url ? fallback_splash_screen_url->spec()
+                                            : "none");
 
   // Get the system language and initialize our localized strings.
   std::string language = base::GetSystemLanguage();
@@ -472,7 +475,7 @@
   options.command_line_auto_mem_settings =
       memory_settings::GetSettings(*command_line);
   options.build_auto_mem_settings = memory_settings::GetDefaultBuildSettings();
-  options.splash_screen_url = splash_screen_url;
+  options.fallback_splash_screen_url = fallback_splash_screen_url;
 
   if (command_line->HasSwitch(browser::switches::kFPSPrint)) {
     options.renderer_module_options.enable_fps_stdout = true;
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp
index 321bc20..0ea1fe2 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -86,6 +86,8 @@
         'resource_provider_array_buffer_allocator.h',
         'splash_screen.cc',
         'splash_screen.h',
+        'splash_screen_cache.cc',
+        'splash_screen_cache.h',
         'storage_upgrade_handler.cc',
         'storage_upgrade_handler.h',
         'suspend_fuzzer.cc',
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 061a5df..392a6f5 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -218,6 +218,7 @@
         '../audio/audio_node_channel_interpretation.idl',
         '../dom/blob_property_bag.idl',
         '../dom/device_orientation_event_init.idl',
+        '../dom/document_ready_state.idl',
         '../dom/dom_parser_supported_type.idl',
         '../dom/event_init.idl',
         '../dom/event_modifier_init.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index d744b06..905991b 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -19,8 +19,10 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/debug/trace_event.h"
+#include "base/file_path.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
+#include "base/optional.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
 #include "base/string_number_conversions.h"
@@ -238,7 +240,8 @@
       render_timeout_count_(0),
 #endif
       will_quit_(false),
-      application_state_(initial_application_state) {
+      application_state_(initial_application_state),
+      splash_screen_cache_(new SplashScreenCache()) {
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
   on_error_triggered_count_ = 0;
@@ -305,7 +308,7 @@
   lifecycle_observers_.AddObserver(debug_console_.get());
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
-  splash_screen_url_ = options.splash_screen_url;
+  fallback_splash_screen_url_ = options.fallback_splash_screen_url;
   // Synchronously construct our WebModule object.
   NavigateInternal(url);
   DCHECK(web_module_);
@@ -377,14 +380,18 @@
 
   // Show a splash screen while we're waiting for the web page to load.
   const math::Size& viewport_size = GetViewportSize();
+
   DestroySplashScreen();
-  if (splash_screen_url_) {
-    splash_screen_.reset(
-        new SplashScreen(application_state_,
-                         base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
-                                    base::Unretained(this)),
-                         &network_module_, viewport_size, GetResourceProvider(),
-                         kLayoutMaxRefreshFrequencyInHz, *splash_screen_url_));
+  base::optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url);
+  if (fallback_splash_screen_url_ ||
+      (key && splash_screen_cache_->IsSplashScreenCached(*key))) {
+    splash_screen_.reset(new SplashScreen(
+        application_state_,
+        base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
+                   base::Unretained(this)),
+        &network_module_, viewport_size, GetResourceProvider(),
+        kLayoutMaxRefreshFrequencyInHz, *fallback_splash_screen_url_, url,
+        splash_screen_cache_.get()));
     lifecycle_observers_.AddObserver(splash_screen_.get());
   }
 
@@ -394,6 +401,7 @@
       dom::CspDelegateFactory::GetInsecureAllowedToken();
 #endif
   WebModule::Options options(options_.web_module_options);
+  options.splash_screen_cache = splash_screen_cache_.get();
   options.navigation_callback =
       base::Bind(&BrowserModule::Navigate, base::Unretained(this));
   options.loaded_callbacks.push_back(
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index fcba62b..ba3861d 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -85,7 +85,7 @@
     base::Closure web_module_recreated_callback;
     memory_settings::AutoMemSettings command_line_auto_mem_settings;
     memory_settings::AutoMemSettings build_auto_mem_settings;
-    base::optional<GURL> splash_screen_url;
+    base::optional<GURL> fallback_splash_screen_url;
     base::optional<math::Size> requested_viewport_size;
   };
 
@@ -453,9 +453,12 @@
   // memory.
   memory_settings::Checker memory_settings_checker_;
 
-  // The URL to the splash screen. If empty (the default), no splash
+  // The fallback URL to the splash screen. If empty (the default), no splash
   // screen will be displayed.
-  base::optional<GURL> splash_screen_url_;
+  base::optional<GURL> fallback_splash_screen_url_;
+
+  // The splash screen cache.
+  scoped_ptr<SplashScreenCache> splash_screen_cache_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/memory_tracker/tool/leak_finder_tool.cc b/src/cobalt/browser/memory_tracker/tool/leak_finder_tool.cc
index 95313d2..b778049 100644
--- a/src/cobalt/browser/memory_tracker/tool/leak_finder_tool.cc
+++ b/src/cobalt/browser/memory_tracker/tool/leak_finder_tool.cc
@@ -23,6 +23,7 @@
 #include "cobalt/browser/memory_tracker/tool/params.h"
 #include "cobalt/browser/memory_tracker/tool/tool_impl.h"
 #include "cobalt/browser/memory_tracker/tool/util.h"
+#include "cobalt/script/util/stack_trace_helpers.h"
 #include "nb/memory_scope.h"
 #include "starboard/string.h"
 
@@ -284,8 +285,18 @@
 }
 
 const std::string* LeakFinderTool::TryGetJavascriptSymbol() {
-  // TODO: Actually get and use a stack trace here.
-  return NULL;
+  auto* js_stack_gen = script::util::GetThreadLocalStackTraceGenerator();
+  if (!js_stack_gen || !js_stack_gen->Valid()) {
+    return NULL;
+  }
+
+  // Only get one symbol.
+  char buffer[256];
+  if (!js_stack_gen->GenerateStackTraceString(1, buffer, sizeof(buffer))) {
+    return NULL;
+  }
+  const char* file_name = BaseNameFast(buffer);
+  return &string_pool_.Intern(file_name);
 }
 
 void LeakFinderTool::SampleSnapshot(
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc
index 531ab13..8c78ce2 100644
--- a/src/cobalt/browser/splash_screen.cc
+++ b/src/cobalt/browser/splash_screen.cc
@@ -14,8 +14,12 @@
 
 #include "cobalt/browser/splash_screen.h"
 
+#include <string>
+
 #include "base/bind.h"
 #include "base/threading/platform_thread.h"
+#include "cobalt/browser/splash_screen_cache.h"
+#include "cobalt/loader/cache_fetcher.h"
 
 namespace cobalt {
 namespace browser {
@@ -26,7 +30,10 @@
                            network::NetworkModule* network_module,
                            const math::Size& window_dimensions,
                            render_tree::ResourceProvider* resource_provider,
-                           float layout_refresh_rate, const GURL& url)
+                           float layout_refresh_rate,
+                           const GURL& fallback_splash_screen_url,
+                           const GURL& initial_main_web_module_url,
+                           SplashScreenCache* splash_screen_cache)
     : render_tree_produced_callback_(render_tree_produced_callback),
       is_ready_(true, false) {
   WebModule::Options web_module_options;
@@ -38,9 +45,20 @@
   web_module_options.loader_thread_priority = base::kThreadPriority_High;
   web_module_options.animated_image_decode_thread_priority =
       base::kThreadPriority_High;
+  GURL url_to_pass = fallback_splash_screen_url;
+
+  // Use the cached URL rather than the passed in URL if it exists.
+  base::optional<std::string> key =
+      SplashScreenCache::GetKeyForStartUrl(initial_main_web_module_url);
+  if (key && splash_screen_cache &&
+      splash_screen_cache->IsSplashScreenCached(*key)) {
+    url_to_pass = GURL(loader::kCacheScheme + ("://" + *key));
+    web_module_options.can_fetch_cache = true;
+    web_module_options.splash_screen_cache = splash_screen_cache;
+  }
 
   web_module_.reset(new WebModule(
-      url, initial_application_state,
+      url_to_pass, initial_application_state,
       base::Bind(&SplashScreen::OnRenderTreeProduced, base::Unretained(this)),
       base::Bind(&SplashScreen::OnError, base::Unretained(this)),
       base::Bind(&SplashScreen::OnWindowClosed, base::Unretained(this)),
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index 1780bd5..0e07915 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -21,6 +21,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/synchronization/waitable_event.h"
 #include "cobalt/browser/lifecycle_observer.h"
+#include "cobalt/browser/splash_screen_cache.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/media/media_module_stub.h"
 #include "googleurl/src/gurl.h"
@@ -38,7 +39,10 @@
                network::NetworkModule* network_module,
                const math::Size& window_dimensions,
                render_tree::ResourceProvider* resource_provider,
-               float layout_refresh_rate, const GURL& url);
+               float layout_refresh_rate,
+               const GURL& fallback_splash_screen_url,
+               const GURL& initial_main_web_module_url,
+               cobalt::browser::SplashScreenCache* splash_screen_cache);
   ~SplashScreen();
 
   void SetSize(const math::Size& window_dimensions, float video_pixel_ratio) {
diff --git a/src/cobalt/browser/splash_screen_cache.cc b/src/cobalt/browser/splash_screen_cache.cc
new file mode 100644
index 0000000..ab674df
--- /dev/null
+++ b/src/cobalt/browser/splash_screen_cache.cc
@@ -0,0 +1,157 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/splash_screen_cache.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
+#include "base/string_util.h"
+#include "base/synchronization/lock.h"
+#include "starboard/directory.h"
+#include "starboard/file.h"
+#include "starboard/string.h"
+
+namespace cobalt {
+namespace browser {
+namespace {
+bool CreateDirsForKey(const std::string& key) {
+  char path[SB_FILE_MAX_PATH] = {0};
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+    return false;
+  }
+  std::size_t prev_found = 0;
+  std::size_t found = key.find(SB_FILE_SEP_STRING);
+  SbStringConcat(path, SB_FILE_SEP_STRING, SB_FILE_MAX_PATH);
+  while (found != std::string::npos) {
+    SbStringConcat(path, key.substr(prev_found, found - prev_found).c_str(),
+                   SB_FILE_MAX_PATH);
+    if (!SbDirectoryCreate(path)) {
+      return false;
+    }
+    prev_found = found;
+    found = key.find(SB_FILE_SEP_STRING, prev_found + 1);
+  }
+  return true;
+}
+
+}  // namespace
+
+SplashScreenCache::SplashScreenCache() { base::AutoLock lock(lock_); }
+
+bool SplashScreenCache::CacheSplashScreen(const std::string& key,
+                                          const std::string& content) const {
+  base::AutoLock lock(lock_);
+  if (key.empty()) {
+    return false;
+  }
+
+  char path[SB_FILE_MAX_PATH] = {0};
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+    return false;
+  }
+  if (!CreateDirsForKey(key)) {
+    return false;
+  }
+  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  starboard::ScopedFile cache_file(
+      full_path.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL);
+
+  return cache_file.WriteAll(content.c_str(),
+                             static_cast<int>(content.size())) > 0;
+}
+
+bool SplashScreenCache::IsSplashScreenCached(const std::string& key) const {
+  base::AutoLock lock(lock_);
+  char path[SB_FILE_MAX_PATH] = {0};
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+    return false;
+  }
+  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  return !key.empty() && SbFileExists(full_path.c_str());
+}
+
+int SplashScreenCache::ReadCachedSplashScreen(
+    const std::string& key, scoped_array<char>* result) const {
+  base::AutoLock lock(lock_);
+  if (!result) {
+    return 0;
+  }
+  char path[SB_FILE_MAX_PATH] = {0};
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH)) {
+    result->reset();
+    return 0;
+  }
+  std::string full_path = path + (SB_FILE_SEP_STRING + key);
+  starboard::ScopedFile cache_file(full_path.c_str(),
+                                   kSbFileOpenOnly | kSbFileRead, NULL, NULL);
+  SbFileInfo info;
+  bool success = SbFileGetPathInfo(full_path.c_str(), &info);
+  if (!success) {
+    result->reset();
+    return 0;
+  }
+  const int kFileSize = static_cast<int>(info.size);
+  result->reset(new char[kFileSize]);
+  int result_size = cache_file.ReadAll(result->get(), kFileSize);
+  return result_size;
+}
+
+// static
+base::optional<std::string> SplashScreenCache::GetKeyForStartUrl(
+    const GURL& url) {
+  std::string encoded_url = "";
+  if (!url.is_valid()) {
+    return base::nullopt;
+  }
+  base::Base64Encode(base::StringPiece(url.host() + url.path()), &encoded_url);
+
+  // Make web-safe.
+  ReplaceChars(encoded_url, "/", "_", &encoded_url);
+  ReplaceChars(encoded_url, "+", "-", &encoded_url);
+
+  char path[SB_FILE_MAX_PATH] = {0};
+  bool has_cache_dir =
+      SbSystemGetPath(kSbSystemPathCacheDirectory, path, SB_FILE_MAX_PATH);
+  if (!has_cache_dir) {
+    return base::nullopt;
+  }
+
+  std::string subpath = "";
+  std::string subcomponent = SB_FILE_SEP_STRING + std::string("splash_screen");
+  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >=
+      SB_FILE_MAX_PATH) {
+    return base::nullopt;
+  }
+  subpath += "splash_screen";
+  subcomponent = SB_FILE_SEP_STRING + encoded_url;
+  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >=
+      SB_FILE_MAX_PATH) {
+    return base::nullopt;
+  }
+  subpath += subcomponent;
+  subcomponent = SB_FILE_SEP_STRING + std::string("splash.html");
+  if (SbStringConcat(path, subcomponent.c_str(), SB_FILE_MAX_PATH) >
+      SB_FILE_MAX_PATH) {
+    return base::nullopt;
+  }
+  subpath += subcomponent;
+
+  return subpath;
+}
+
+}  // namespace browser
+}  // namespace cobalt
diff --git a/src/cobalt/browser/splash_screen_cache.h b/src/cobalt/browser/splash_screen_cache.h
new file mode 100644
index 0000000..83e9e7a
--- /dev/null
+++ b/src/cobalt/browser/splash_screen_cache.h
@@ -0,0 +1,61 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_BROWSER_SPLASH_SCREEN_CACHE_H_
+#define COBALT_BROWSER_SPLASH_SCREEN_CACHE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+#include "googleurl/src/gurl.h"
+
+namespace cobalt {
+namespace browser {
+
+// SplashScreenCache saves a splash screen document loaded by the
+// HTMLLinkElement into the system cache, if any, so that it's
+// available to BrowserModule to show on subsequent runs before
+// loading the initial URL. The key for the cached document is based
+// on the passed in initial URL. See cobalt/doc/splash_screen.md for
+// more information.
+class SplashScreenCache {
+ public:
+  SplashScreenCache();
+
+  // Cache the splash screen.
+  bool CacheSplashScreen(const std::string& key,
+                         const std::string& content) const;
+
+  // Read the cached the splash screen.
+  int ReadCachedSplashScreen(const std::string& key,
+                             scoped_array<char>* result) const;
+
+  // Determine if a splash screen is cached corresponding to the key.
+  bool IsSplashScreenCached(const std::string& key) const;
+
+  // Get the key that corresponds to a starting URL. Optionally create
+  // subdirectories along the path.
+  static base::optional<std::string> GetKeyForStartUrl(const GURL& url);
+
+ private:
+  // Lock to protect access to the cache file.
+  mutable base::Lock lock_;
+};
+
+}  // namespace browser
+}  // namespace cobalt
+
+#endif  // COBALT_BROWSER_SPLASH_SCREEN_CACHE_H_
diff --git a/src/cobalt/browser/testdata/media-element-demo/media-element-demo.js b/src/cobalt/browser/testdata/media-element-demo/media-element-demo.js
index 375e59f..ce74ac8 100644
--- a/src/cobalt/browser/testdata/media-element-demo/media-element-demo.js
+++ b/src/cobalt/browser/testdata/media-element-demo/media-element-demo.js
@@ -44,8 +44,12 @@
   xhr.open('GET', url, true);
   xhr.responseType = 'arraybuffer';
   xhr.addEventListener('load', function(e) {
-    source_buffer.append(new Uint8Array(e.target.response));
-    callback();
+    var onupdateend = function() {
+      source_buffer.removeEventListener('updateend', onupdateend);
+      callback();
+    };
+    source_buffer.addEventListener('updateend', onupdateend);
+    source_buffer.appendBuffer(new Uint8Array(e.target.response));
   });
   xhr.setRequestHeader('Range', ('bytes=' + begin +'-' + end));
   xhr.send();
diff --git a/src/cobalt/browser/testdata/splash_screen/link_splash_screen.html b/src/cobalt/browser/testdata/splash_screen/link_splash_screen.html
new file mode 100644
index 0000000..6d114b6
--- /dev/null
+++ b/src/cobalt/browser/testdata/splash_screen/link_splash_screen.html
@@ -0,0 +1,435 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="
+    default-src h5vcc-embedded://*/cobalt_splash_screen.html;
+    style-src 'unsafe-inline';"
+</head>
+
+<body style="background-color: #1f52a5;">
+  <link rel="splashscreen" href="h5vcc-embedded://cobalt_splash_screen.html">
+<h1>Heading</h1>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+</body>
+</html>
diff --git a/src/cobalt/browser/testdata/splash_screen/link_splash_screen_network.html b/src/cobalt/browser/testdata/splash_screen/link_splash_screen_network.html
new file mode 100644
index 0000000..1cc9819
--- /dev/null
+++ b/src/cobalt/browser/testdata/splash_screen/link_splash_screen_network.html
@@ -0,0 +1,676 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="
+    default-src 'unsafe-inline';
+    script-src https://*/storage.googleapis.com/yt-temp/self_contained_splash.html;
+    style-src 'unsafe-inline';"
+</head>
+
+<body style="background-color: #1f52a5;">
+  <link rel="splashscreen" href="https://storage.googleapis.com/yt-temp/self_contained_splash.html">
+<h1>Heading</h1>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
+interdum maximus finibus. Ut fermentum malesuada commodo. Sed
+faucibus, sapien a mattis lobortis, magna ante efficitur mauris, quis
+sodales nibh diam nec quam. Vestibulum magna libero, tincidunt non
+erat sed, molestie pulvinar ex. Maecenas semper blandit elit, id
+suscipit nulla venenatis pretium. Integer accumsan porta felis, vitae
+placerat urna accumsan vel. Aliquam eu aliquet mi. Aenean tincidunt
+eros lectus, sit amet efficitur orci ultrices at. Morbi lobortis ex
+quis luctus rutrum. In nulla velit, elementum vitae turpis vitae,
+finibus varius massa. Morbi id libero faucibus, tempus eros et,
+ullamcorper ipsum. Sed eleifend finibus bibendum. Nullam ut nunc nec
+lacus posuere dignissim. Nunc sollicitudin vitae augue id
+vulputate. Ut ac nibh gravida, volutpat est ac, facilisis neque.</p>
+
+<p>Nam dictum leo massa, non posuere dui bibendum id. Morbi sagittis est
+non est laoreet, a sollicitudin felis aliquet. Ut cursus vel leo a
+efficitur. Proin ut pellentesque sapien, vel maximus dui. Suspendisse
+eu felis eget leo elementum efficitur. Class aptent taciti sociosqu ad
+litora torquent per conubia nostra, per inceptos himenaeos. Fusce
+lobortis velit in elit pellentesque, ut auctor ipsum dignissim. Sed
+aliquet eleifend convallis. Duis mollis, dolor sed rutrum mollis,
+augue eros dignissim erat, eu dapibus augue turpis ac sapien. Morbi at
+volutpat odio, at molestie risus. Nulla quis nulla et magna vestibulum
+euismod. Praesent suscipit quam elit, non luctus turpis rutrum
+faucibus.</p>
+
+<p>Morbi feugiat lacus rhoncus, dignissim velit nec, dignissim
+lorem. Aliquam erat volutpat. Mauris semper dictum tempus. Nulla ex
+ligula, malesuada in ornare sed, euismod vitae massa. Etiam quis erat
+quis nisl facilisis suscipit. Mauris placerat ante et auctor
+fermentum. Donec tincidunt justo sem, ullamcorper vulputate nisl
+commodo a. Vestibulum quis ex non elit porttitor semper eget quis
+tortor. Suspendisse mattis neque non elementum scelerisque. Nulla
+facilisi. Nulla non felis et justo feugiat elementum. Aenean sodales
+turpis at erat eleifend lacinia. Proin eleifend volutpat purus id
+mollis. Proin vel tellus faucibus, sagittis libero at, lobortis
+odio. Praesent quam mauris, auctor vel velit eu, convallis molestie
+nisi. Pellentesque in nunc at orci ultrices vehicula.</p>
+
+<p>Praesent nibh lectus, efficitur sed risus in, rutrum tristique
+arcu. Curabitur non efficitur elit. Phasellus eget odio iaculis,
+molestie dui eget, venenatis erat. Nulla luctus facilisis lectus, nec
+dapibus tortor rhoncus vel. Donec nec arcu elit. Nullam ut faucibus
+purus, sed ultricies diam. Pellentesque at finibus ipsum. Vestibulum
+egestas dignissim nisl, ac rhoncus risus finibus sit amet. Donec non
+feugiat ante. Donec vehicula dui a lorem imperdiet, a tempus diam
+pulvinar. Nullam congue efficitur justo, non posuere ligula sodales
+in. Ut a urna ornare, ultrices velit in, pellentesque
+lorem. Vestibulum ante ipsum primis in faucibus orci luctus et
+ultrices posuere cubilia Curae;</p>
+
+<p>Orci varius natoque penatibus et magnis dis parturient montes,
+nascetur ridiculus mus. Morbi maximus quis magna et aliquet. Nam
+bibendum fermentum tempus. Praesent iaculis tortor metus, at
+vestibulum ipsum hendrerit mattis. Proin fringilla nisl sit amet
+tincidunt blandit. Interdum et malesuada fames ac ante ipsum primis in
+faucibus. Phasellus vel lectus leo. Curabitur fringilla, arcu non
+posuere viverra, urna metus blandit augue, convallis mattis tortor dui
+vel arcu. In sit amet metus vitae ex rhoncus hendrerit.</p>
+
+</body>
+</html>
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 8a4b237..efd1b83 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -15,17 +15,21 @@
 #include "cobalt/browser/web_module.h"
 
 #include <sstream>
+#include <string>
 
 #include "base/bind.h"
+#include "base/callback.h"
 #include "base/command_line.h"
 #include "base/debug/trace_event.h"
 #include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop_proxy.h"
 #include "base/optional.h"
 #include "base/stringprintf.h"
 #include "cobalt/base/startup_timer.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/browser/splash_screen_cache.h"
 #include "cobalt/browser/stack_size_constants.h"
 #include "cobalt/browser/switches.h"
 #include "cobalt/browser/web_module_stat_tracker.h"
@@ -85,6 +89,21 @@
     "To wipe the box tree and turn partial layout off.";
 #endif  // defined(ENABLE_PARTIAL_LAYOUT_CONTROL)
 
+base::Callback<bool(const std::string&)> SplashScreenCacheCallback(
+    const GURL& initial_url, SplashScreenCache* splash_screen_cache) {
+  base::Callback<bool(const std::string&)> splash_screen_cache_callback =
+      base::Callback<bool(const std::string&)>();
+  base::optional<std::string> key =
+      SplashScreenCache::GetKeyForStartUrl(initial_url);
+  if (splash_screen_cache && key) {
+    splash_screen_cache_callback =
+        base::Bind(base::Bind(&SplashScreenCache::CacheSplashScreen,
+                              base::Unretained(splash_screen_cache)),
+                   *key);
+  }
+  return splash_screen_cache_callback;
+}
+
 }  // namespace
 
 // Private WebModule implementation. Each WebModule owns a single instance of
@@ -396,9 +415,18 @@
 
   blob_registry_.reset(new dom::Blob::Registry);
 
+  base::Callback<int(const std::string&, scoped_array<char>*)>
+      read_cache_callback;
+  if (data.options.can_fetch_cache) {
+    read_cache_callback =
+        base::Bind(&browser::SplashScreenCache::ReadCachedSplashScreen,
+                   base::Unretained(data.options.splash_screen_cache));
+  }
+
   fetcher_factory_.reset(new loader::FetcherFactory(
       data.network_module, data.options.extra_web_file_dir,
-      dom::URL::MakeBlobResolverCallback(blob_registry_.get())));
+      dom::URL::MakeBlobResolverCallback(blob_registry_.get()),
+      read_cache_callback));
   DCHECK(fetcher_factory_);
 
   loader_factory_.reset(
@@ -466,6 +494,11 @@
   media_source_registry_.reset(new dom::MediaSource::Registry);
 
   media_session_client_ = media_session::MediaSessionClient::Create();
+
+  base::Callback<bool(const std::string&)> splash_screen_cache_callback =
+      SplashScreenCacheCallback(data.initial_url,
+                                data.options.splash_screen_cache);
+
   window_ = new dom::Window(
       data.window_dimensions.width(), data.window_dimensions.height(),
       data.video_pixel_ratio, data.initial_application_state, css_parser_.get(),
@@ -492,11 +525,11 @@
 #if defined(ENABLE_TEST_RUNNER)
       data.options.layout_trigger == layout::LayoutManager::kTestRunnerMode
           ? dom::Window::kClockTypeTestRunner
-          : dom::Window::kClockTypeSystemTime
+          : dom::Window::kClockTypeSystemTime,
 #else
-      dom::Window::kClockTypeSystemTime
+      dom::Window::kClockTypeSystemTime,
 #endif
-      );  // NOLINT(whitespace/parens)
+      splash_screen_cache_callback);
   DCHECK(window_);
 
   window_weak_ = base::AsWeakPtr(window_.get());
@@ -945,7 +978,8 @@
       loader_thread_priority(base::kThreadPriority_Low),
       animated_image_decode_thread_priority(base::kThreadPriority_Low),
       video_playback_rate_multiplier(1.f),
-      enable_image_animations(true) {}
+      enable_image_animations(true),
+      can_fetch_cache(false) {}
 
 WebModule::WebModule(
     const GURL& initial_url, base::ApplicationState initial_application_state,
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index ad3ab1e..07d8975 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -30,6 +30,7 @@
 #include "cobalt/base/console_commands.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/browser/lifecycle_observer.h"
+#include "cobalt/browser/splash_screen_cache.h"
 #include "cobalt/css_parser/parser.h"
 #if defined(ENABLE_DEBUG_CONSOLE)
 #include "cobalt/debug/debug_server.h"
@@ -179,6 +180,13 @@
     // Allows image animations to be enabled/disabled.  Its default value
     // is true to enable them.
     bool enable_image_animations;
+
+    // The splash screen cache object, owned by the BrowserModule.
+    SplashScreenCache* splash_screen_cache;
+
+    // Whether or not the WebModule is allowed to fetch from cache via
+    // h5vcc-cache://.
+    bool can_fetch_cache;
   };
 
   typedef layout::LayoutManager::LayoutResults LayoutResults;
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index 74e0b49..d5c614a 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -60,7 +60,7 @@
         '<(DEPTH)/cobalt/render_tree/render_tree.gyp:*',
         '<(DEPTH)/cobalt/renderer/renderer.gyp:*',
         '<(DEPTH)/cobalt/renderer/sandbox/sandbox.gyp:*',
-        '<(DEPTH)/cobalt/samples/samples.gyp:*',
+        '<(DEPTH)/cobalt/samples/simple_example/simple_example.gyp:*',
         '<(DEPTH)/cobalt/script/script.gyp:*',
         '<(DEPTH)/cobalt/script/engine.gyp:all_engines',
         '<(DEPTH)/cobalt/speech/sandbox/sandbox.gyp:*',
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 58c7a27..cf4e0e6 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-81256
\ No newline at end of file
+84472
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index 96d4005..0f974b5 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -254,11 +254,9 @@
     # Temporary indicator for Tizen - should eventually move to feature defines.
     'tizen_os%': 0,
 
-    # URL of default build time splash screen:
-    # TODO: Point this to cobalt_splash_screen.html and override it in
-    # ports' gyp_configuration.gypi (coordinate transition with
-    # partners).
-    'fallback_splash_screen_url%': 'h5vcc-embedded://splash_screen.html',
+    # The URL of default build time splash screen - see
+    #   cobalt/doc/splash_screen.md for information about this.
+    'fallback_splash_screen_url%': 'h5vcc-embedded://youtube_splash_screen.html',
 
     # Cache parameters
 
diff --git a/src/cobalt/build/copy_icu_data.gypi b/src/cobalt/build/copy_icu_data.gypi
index 2b15d2a..abbff85 100644
--- a/src/cobalt/build/copy_icu_data.gypi
+++ b/src/cobalt/build/copy_icu_data.gypi
@@ -39,15 +39,21 @@
         'little_endian%': '<(little_endian)',
         'use_icu_dat_file%': '<(use_icu_dat_file)',
       },
+      'little_endian%': '<(little_endian)',
     },
 
-    #'inputs_icu%': [ '<(inputs_icu)' ],
-    'inputs_icu%': [ '<(static_contents_source_dir)/icu/' ],
+    'conditions': [
+      ['little_endian==1', {
+        'inputs_icu%': [ '<(static_contents_source_dir)/icu/icudt56l' ],
+      }, {
+        'inputs_icu%': [ '<(static_contents_source_dir)/icu/icudt56b' ],
+      }],
+    ],
   },
 
   'copies': [
     {
-      'destination': '<(sb_static_contents_output_data_dir)/',
+      'destination': '<(sb_static_contents_output_data_dir)/icu/',
       'files': [ '<(inputs_icu)' ],
     },
   ],
diff --git a/src/cobalt/cssom/css_computed_style_declaration.cc b/src/cobalt/cssom/css_computed_style_declaration.cc
index f19c7d0..169f68d 100644
--- a/src/cobalt/cssom/css_computed_style_declaration.cc
+++ b/src/cobalt/cssom/css_computed_style_declaration.cc
@@ -20,12 +20,10 @@
 namespace cobalt {
 namespace cssom {
 // This returns the result of serializing a CSS declaration block.
-// The current implementation does not handle shorthands.
 //   https://www.w3.org/TR/cssom/#serialize-a-css-declaration-block
 std::string CSSComputedStyleDeclaration::css_text(
     script::ExceptionState* /*exception_state*/) const {
-  // TODO: This should enumerate all supported properties, not just
-  // the declared ones.
+  // The current implementation does not handle shorthands.
   NOTIMPLEMENTED();
   return data_ ? data_->SerializeCSSDeclarationBlock() : std::string();
 }
@@ -59,6 +57,18 @@
   if (!data_ || key == kNoneProperty) {
     return std::string();
   }
+  if (key > kMaxLonghandPropertyKey) {
+    // Shorthand properties are never directly stored as computed style
+    // properties.
+    // TODO: Implement serialization of css values, see
+    // https://www.w3.org/TR/cssom-1/#serializing-css-values
+    DCHECK_LE(key, kMaxEveryPropertyKey);
+    NOTIMPLEMENTED();
+    DLOG(WARNING) << "Unsupported property query for \"" << GetPropertyName(key)
+                  << "\": Returning of property value strings is not "
+                     "supported for shorthand properties.";
+    return std::string();
+  }
   const scoped_refptr<PropertyValue>& property_value =
       data_->GetPropertyValueReference(key);
   DCHECK(property_value);
diff --git a/src/cobalt/cssom/css_declared_style_declaration.cc b/src/cobalt/cssom/css_declared_style_declaration.cc
index a704fda..149941b 100644
--- a/src/cobalt/cssom/css_declared_style_declaration.cc
+++ b/src/cobalt/cssom/css_declared_style_declaration.cc
@@ -105,6 +105,18 @@
 
 std::string CSSDeclaredStyleDeclaration::GetDeclaredPropertyValueStringByKey(
     const PropertyKey key) const {
+  if (key > kMaxLonghandPropertyKey) {
+    // Shorthand properties are never directly stored as declared properties,
+    // but are expanded into their longhand property components during parsing.
+    // TODO: Implement serialization of css values, see
+    // https://www.w3.org/TR/cssom-1/#serializing-css-values
+    DCHECK_LE(key, kMaxEveryPropertyKey);
+    NOTIMPLEMENTED();
+    DLOG(WARNING) << "Unsupported property query for \"" << GetPropertyName(key)
+                  << "\": Returning of property value strings is not "
+                     "supported for shorthand properties.";
+    return std::string();
+  }
   return (data_ && key != kNoneProperty) ? data_->GetPropertyValueString(key)
                                          : std::string();
 }
diff --git a/src/cobalt/cssom/css_style_declaration.cc b/src/cobalt/cssom/css_style_declaration.cc
index 7542e1b..7736040 100644
--- a/src/cobalt/cssom/css_style_declaration.cc
+++ b/src/cobalt/cssom/css_style_declaration.cc
@@ -25,8 +25,7 @@
 
 std::string CSSStyleDeclaration::animation(
     script::ExceptionState* /*exception_state*/) const {
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kAnimationProperty);
 }
 
 void CSSStyleDeclaration::set_animation(
@@ -120,11 +119,7 @@
 
 std::string CSSStyleDeclaration::background(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other background properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBackgroundProperty);
 }
 
 void CSSStyleDeclaration::set_background(
@@ -194,11 +189,7 @@
 
 std::string CSSStyleDeclaration::border(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderProperty);
 }
 
 void CSSStyleDeclaration::set_border(const std::string& border,
@@ -208,11 +199,7 @@
 
 std::string CSSStyleDeclaration::border_bottom(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border bottom properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderBottomProperty);
 }
 
 void CSSStyleDeclaration::set_border_bottom(
@@ -223,11 +210,7 @@
 
 std::string CSSStyleDeclaration::border_left(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border left properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderLeftProperty);
 }
 
 void CSSStyleDeclaration::set_border_left(
@@ -238,11 +221,7 @@
 
 std::string CSSStyleDeclaration::border_right(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border right properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderRightProperty);
 }
 
 void CSSStyleDeclaration::set_border_right(
@@ -253,11 +232,7 @@
 
 std::string CSSStyleDeclaration::border_top(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border top properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderTopProperty);
 }
 
 void CSSStyleDeclaration::set_border_top(
@@ -267,11 +242,7 @@
 
 std::string CSSStyleDeclaration::border_color(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border color properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderColorProperty);
 }
 
 void CSSStyleDeclaration::set_border_color(
@@ -330,11 +301,7 @@
 
 std::string CSSStyleDeclaration::border_style(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border style properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderStyleProperty);
 }
 
 void CSSStyleDeclaration::set_border_style(
@@ -393,11 +360,7 @@
 
 std::string CSSStyleDeclaration::border_width(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other border width properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kBorderWidthProperty);
 }
 
 void CSSStyleDeclaration::set_border_width(
@@ -527,8 +490,7 @@
 
 std::string CSSStyleDeclaration::font(
     script::ExceptionState* /*exception_state*/) const {
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kFontProperty);
 }
 
 void CSSStyleDeclaration::set_font(const std::string& font,
@@ -611,11 +573,7 @@
 
 std::string CSSStyleDeclaration::margin(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other margin properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kMarginProperty);
 }
 
 void CSSStyleDeclaration::set_margin(const std::string& margin,
@@ -782,11 +740,7 @@
 
 std::string CSSStyleDeclaration::padding(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other padding properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kPaddingProperty);
 }
 
 void CSSStyleDeclaration::set_padding(const std::string& padding,
@@ -1000,11 +954,7 @@
 
 std::string CSSStyleDeclaration::transition(
     script::ExceptionState* /*exception_state*/) const {
-  // In order to implement this properly we must either save the incoming string
-  // values when they are being set, or combine the results of getting the
-  // styles from all the other transition properties.
-  NOTIMPLEMENTED();
-  return "";
+  return GetDeclaredPropertyValueStringByKey(kTransitionProperty);
 }
 
 void CSSStyleDeclaration::set_transition(
@@ -1129,19 +1079,7 @@
 
 std::string CSSStyleDeclaration::GetPropertyValue(
     const std::string& property_name) {
-  PropertyKey key = GetPropertyKey(property_name);
-  if (key > kMaxLonghandPropertyKey) {
-    // Shorthand properties are never directly stored as declared properties,
-    // but are expanded into their longhand property components during parsing.
-    // TODO: Implement serialization of css values, see
-    // https://www.w3.org/TR/cssom-1/#serializing-css-values
-    DCHECK_LE(key, kMaxEveryPropertyKey);
-    DLOG(WARNING) << "Unsupported property query for \"" << property_name
-                  << "\": Returning of property value strings is only "
-                     "supported for longhand properties.";
-    return std::string();
-  }
-  return GetDeclaredPropertyValueStringByKey(key);
+  return GetDeclaredPropertyValueStringByKey(GetPropertyKey(property_name));
 }
 
 void CSSStyleDeclaration::SetPropertyValueStringByKey(
diff --git a/src/cobalt/doc/splash_screen.md b/src/cobalt/doc/splash_screen.md
new file mode 100644
index 0000000..201d5e1
--- /dev/null
+++ b/src/cobalt/doc/splash_screen.md
@@ -0,0 +1,100 @@
+# Cobalt Splash Screen
+
+## Startup splash screen sequence
+
+There can be up to three splash screens shown when launching web applications on
+Cobalt:
+
+  * one from the system
+  * one from Cobalt
+  * one from the web application itself
+
+The system splash screen is often a transition from the application icon
+on the home screen to a static asset dictated by the platform (which is outside
+of Cobalt's control). The Cobalt splash screen is shown as soon as Cobalt can
+render until the web application is loaded. The web application splash screen is
+the HTML content shown immediately upon loading the web application (this may
+resemble a typical splash screen, but it really can be whatever the application
+chooses to show on starting).
+
+## Cobalt splash screen priority order
+
+The Cobalt splash screen must be specified as a URL to a document. The document
+must be either self-contained or all of its references must be local. That means
+the document should not reference any external CSS, JavaScript, or image files,
+for example. This simplifies the caching process so that only a single document
+must be cached without tracing references. All fallback splash screens must
+refer to local documents. This is so the fallback splash screen can be shown
+without latency and even when there is no network available. Specifically, the
+fallback splash screen URL and its references should start with either
+`file:///` or `h5vcc-embedded://`. Additionally `none` can be used to specify
+that no Cobalt splash screen should be constructed; the system splash sequence
+transitions directly into the application splash sequence once the page is
+loaded.
+
+The Cobalt splash screen is one of the following, in order of precedence:
+
+  1. **Web cached splash screen:** If a splash screen specified by a web
+     application is cached from a previous instance of Cobalt, it will be loaded
+     at startup. The key for the cache splash screen is based on host & path of
+     the initial URL with no query or hash. If network connectivity is available
+     at startup, when the initial web application URL is processed, a custom
+     `rel="splashscreen"` attribute of the link element is used to specify and
+     cache the splashscreen URL for future runs.
+
+  2. **Command line fallback splash screen:** This is specified as a command
+     line argument `--fallback_splash_screen_url` via the system and used when
+     cache is unavailable.  This is the case when there is no local cache
+     storage, cache has been cleared, or the application is started for the
+     first time.
+
+  3. **Build-time fallback splash screen:** If a web cached splash screen is
+     unavailable and command line parameters are not passed by the system, a
+     `gyp_configuration.gypi` fallback splash screen may be used. Porters should
+     set the gypi variable `fallback_splash_screen_url` to the splash screen
+     URL.
+
+  4. **Default splash screen:** If no web cached splash screen is
+     available, and command line and `gyp_configuration.gypi` fallbacks are not
+     set, a default splash screen will be used. This is set in `base.gypi` via
+     `fallback_splash_screen_url%` to refer to a black splash screen.
+
+## Web-updatability
+
+Since Cobalt parses the link element's `rel="splashscreen"` attribute for the
+splash screen URL in the content fetched from the initial URL, an application
+developer may update the splash screen by changing that attribute in the link
+element. On the next load of the application, the new splash screen will be
+cached, and on the subsequent load of the application, the new cached splash
+screen will be shown.
+
+For example, the document at the initial URL could contain
+```
+<link rel="splashscreen" href="https://www.example.com/self-contained.html">
+```
+where `"https://www.example.com/self-contained.html"` is the address of some
+self-contained splash screen document. The document must not violate the Content
+Security Policy. The splash screen is treated as a script resource by the CSP.
+
+## Application-specific splash screens
+
+On systems that plan to support multiple Cobalt-based applications, an
+application developer may wish to use the command line arguments for the
+fallback splash screen to display different Cobalt splash screens for different
+applications. The logic for passing in these different command line arguments to
+the Cobalt binary must be handled by the system.
+
+Alternatively, an application developer may use the default black splash screen
+specified in base.gypi whenever a cached splash screen is not available and rely
+on the web application to specify an application-specific cached splash screen
+otherwise.
+
+## Provided embedded resource splash screens
+For convenience, we currently provide the following splash screens as embedded
+resources:
+
+  * `h5vcc-embedded://black_splash_screen.html` - a black splash screen
+  * `h5vcc-embedded://cobalt_splash_screen.html` - a splash screen showing the
+    Cobalt logo
+  * `h5vcc-embedded://youtube_splash_screen.html` - a splash screen showing the
+    YouTube logo
diff --git a/src/cobalt/dom/array_buffer.cc b/src/cobalt/dom/array_buffer.cc
index 9b2d29a..ac89aff 100644
--- a/src/cobalt/dom/array_buffer.cc
+++ b/src/cobalt/dom/array_buffer.cc
@@ -25,6 +25,18 @@
 namespace cobalt {
 namespace dom {
 
+namespace {
+
+int ClampIndex(int index, int length) {
+  if (index < 0) {
+    index = length + index;
+  }
+  index = std::max(index, 0);
+  return std::min(index, length);
+}
+
+}  // namespace
+
 ArrayBuffer::Data::Data(script::EnvironmentSettings* settings, size_t size)
     : allocator_(NULL), cache_(NULL), offloaded_(false), data_(NULL), size_(0) {
   Initialize(settings, size);
@@ -216,26 +228,12 @@
 
 void ArrayBuffer::ClampRange(int start, int end, int source_length,
                              int* clamped_start, int* clamped_end) {
-  // Clamp out of range start/end to valid indices.
-  if (start > source_length) {
-    start = source_length;
-  }
-  if (end > source_length) {
-    end = source_length;
-  }
-
-  // Wrap around negative indices.
-  if (start < 0) {
-    start = source_length + start;
-  }
-  if (end < 0) {
-    end = source_length + end;
-  }
+  start = ClampIndex(start, source_length);
+  end = ClampIndex(end, source_length);
 
   // Clamp the length of the new array to non-negative.
   if (end - start < 0) {
-    start = 0;
-    end = 0;
+    end = start;
   }
   *clamped_start = start;
   *clamped_end = end;
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc
index aaa034b..d0a5b39 100644
--- a/src/cobalt/dom/document.cc
+++ b/src/cobalt/dom/document.cc
@@ -135,6 +135,8 @@
           &CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
           CspDelegate::kImage));
     }
+
+    ready_state_ = kDocumentReadyStateLoading;
   }
 
   // Sample the timeline upon initialization.
@@ -906,6 +908,15 @@
     UpdateComputedStyles();
   }
 
+  // Adjust the document ready state to reflect the fact that the document has
+  // finished loading.  Performing this update and firing the readystatechange
+  // event before the load event matches Chromium's behavior.
+  ready_state_ = kDocumentReadyStateComplete;
+
+  // Dispatch the readystatechange event (before the load event), since we
+  // have changed the document ready state.
+  DispatchEvent(new Event(base::Tokens::readystatechange()));
+
   // Dispatch the document's onload event.
   DispatchEvent(new Event(base::Tokens::load()));
 
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h
index da896bf..2995d8b 100644
--- a/src/cobalt/dom/document.h
+++ b/src/cobalt/dom/document.h
@@ -33,6 +33,7 @@
 #include "cobalt/cssom/selector_tree.h"
 #include "cobalt/cssom/style_sheet_list.h"
 #include "cobalt/dom/csp_delegate_type.h"
+#include "cobalt/dom/document_ready_state.h"
 #include "cobalt/dom/document_timeline.h"
 #include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element_context.h"
@@ -216,6 +217,11 @@
   void set_cookie(const std::string& cookie);
   std::string cookie() const;
 
+  // Returns the document's ready state, i.e. whether the document's 'load'
+  // event has fired yet or not.
+  // https://www.w3.org/TR/html5/dom.html#dom-document-readystate
+  DocumentReadyState ready_state() const { return ready_state_; }
+
   // Custom, not in any spec: Node.
   //
   Document* AsDocument() OVERRIDE { return this; }
@@ -469,6 +475,10 @@
       initial_computed_style_declaration_;
   scoped_refptr<const cssom::CSSComputedStyleData> initial_computed_style_data_;
 
+  // The document's current ready state (e.g. has the 'load' event been fired
+  // yet)
+  DocumentReadyState ready_state_;
+
   // The max depth of elements that are guaranteed to be rendered.
   int dom_max_element_depth_;
 
diff --git a/src/cobalt/dom/document_html5.idl b/src/cobalt/dom/document_html5.idl
index df87548..1ea8041 100644
--- a/src/cobalt/dom/document_html5.idl
+++ b/src/cobalt/dom/document_html5.idl
@@ -27,4 +27,5 @@
   readonly attribute Element? activeElement;
 
   [LenientThis] attribute EventHandler onreadystatechange;
+  readonly attribute DocumentReadyState readyState;
 };
diff --git a/src/starboard/win/console/atomic_public.h b/src/cobalt/dom/document_ready_state.idl
similarity index 75%
copy from src/starboard/win/console/atomic_public.h
copy to src/cobalt/dom/document_ready_state.idl
index 51f81a1..2b26c4d 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/cobalt/dom/document_ready_state.idl
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+// https://www.w3.org/TR/html5/dom.html#the-document-object
 
-#include "starboard/shared/win32/atomic_public.h"
-
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+enum DocumentReadyState {
+  "loading",
+  "interactive",
+  "complete",
+};
diff --git a/src/cobalt/dom/html_collection.cc b/src/cobalt/dom/html_collection.cc
index 5bff1e3..98381f7 100644
--- a/src/cobalt/dom/html_collection.cc
+++ b/src/cobalt/dom/html_collection.cc
@@ -62,7 +62,7 @@
   void MaybeRefreshCollection() const;
 
   // Base node that was used to generate the collection.
-  const scoped_refptr<const Node> base_;
+  const base::WeakPtr<const Node> base_;
   // Predicate callback.
   const Predicate predicate_;
   // Generation of the base node that was used to create the cache.
@@ -74,14 +74,19 @@
 template <typename NodeIterator>
 NodeCollection<NodeIterator>::NodeCollection(
     const scoped_refptr<const Node>& base, const Predicate& predicate)
-    : base_(base),
+    : base_(base::AsWeakPtr(const_cast<Node*>(base.get()))),
       predicate_(predicate),
       base_node_generation_(Node::kInvalidNodeGeneration) {}
 
 template <typename NodeIterator>
 void NodeCollection<NodeIterator>::MaybeRefreshCollection() const {
-  if (base_node_generation_ != base_->node_generation()) {
-    NodeIterator iterator(base_);
+  scoped_refptr<const Node> base(base_);
+  if (!base) {
+    return;
+  }
+
+  if (base_node_generation_ != base->node_generation()) {
+    NodeIterator iterator(base);
 
     cached_collection_.clear();
     Node* child = iterator.First();
@@ -91,7 +96,7 @@
       }
       child = iterator.Next();
     }
-    base_node_generation_ = base_->node_generation();
+    base_node_generation_ = base->node_generation();
   }
 }
 
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc
index ade761c..ac0f16f 100644
--- a/src/cobalt/dom/html_link_element.cc
+++ b/src/cobalt/dom/html_link_element.cc
@@ -14,7 +14,9 @@
 
 #include "cobalt/dom/html_link_element.h"
 
+#include <algorithm>
 #include <string>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/debug/trace_event.h"
@@ -23,18 +25,41 @@
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element_context.h"
+#include "cobalt/dom/window.h"
 #include "googleurl/src/gurl.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
 namespace dom {
+namespace {
+
+CspDelegate::ResourceType GetCspResourceTypeForRel(const std::string& rel) {
+  if (rel == "stylesheet") {
+    return CspDelegate::kStyle;
+  } else if (rel == "splashscreen") {
+    return CspDelegate::kScript;
+  } else {
+    NOTIMPLEMENTED();
+    return CspDelegate::kImage;
+  }
+}
+
+bool IsRelContentCriticalResource(const std::string& rel) {
+  return rel == "stylesheet";
+}
+
+}  // namespace
 
 // static
 const char HTMLLinkElement::kTagName[] = "link";
+// static
+const std::vector<std::string> HTMLLinkElement::kSupportedRelValues = {
+    "stylesheet", "splashscreen"};
 
 void HTMLLinkElement::OnInsertedIntoDocument() {
   HTMLElement::OnInsertedIntoDocument();
-  if (rel() == "stylesheet") {
+  if (std::find(kSupportedRelValues.begin(), kSupportedRelValues.end(),
+                rel()) != kSupportedRelValues.end()) {
     Obtain();
   } else {
     LOG(WARNING) << "<link> has unsupported rel value: " << rel() << ".";
@@ -61,7 +86,7 @@
   Document* document = node_document();
 
   // If the document has no browsing context, do not obtain, parse or apply the
-  // style sheet.
+  // resource.
   if (!document->html_element_context()) {
     return;
   }
@@ -89,7 +114,7 @@
   // the default origin behaviour set to taint.
   csp::SecurityCallback csp_callback = base::Bind(
       &CspDelegate::CanLoad, base::Unretained(document->csp_delegate()),
-      CspDelegate::kStyle);
+      GetCspResourceTypeForRel(rel()));
 
   loader_ = make_scoped_ptr(new loader::Loader(
       base::Bind(
@@ -100,10 +125,12 @@
           base::Bind(&HTMLLinkElement::OnLoadingDone, base::Unretained(this)))),
       base::Bind(&HTMLLinkElement::OnLoadingError, base::Unretained(this))));
 
-  // The element must delay the load event of the element's document until all
-  // the attempts to obtain the resource and its critical subresources are
-  // complete.
-  document->IncreaseLoadingCounter();
+  if (IsRelContentCriticalResource(rel())) {
+    // The element must delay the load event of the element's document until all
+    // the attempts to obtain the resource and its critical subresources are
+    // complete.
+    document->IncreaseLoadingCounter();
+  }
 }
 
 void HTMLLinkElement::OnLoadingDone(const std::string& content) {
@@ -112,25 +139,29 @@
   TRACE_EVENT0("cobalt::dom", "HTMLLinkElement::OnLoadingDone()");
 
   Document* document = node_document();
-  scoped_refptr<cssom::CSSStyleSheet> 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);
-
-  // Once the attempts to obtain the resource and its critical subresources are
-  // complete, the user agent must, if the loads were successful, queue a task
-  // to fire a simple event named load at the link element, or, if the resource
-  // or one of its critical subresources failed to completely load for any
-  // reason (e.g. DNS error, HTTP 404 response, a connection being prematurely
-  // closed, unsupported Content-Type), queue a task to fire a simple event
-  // named error at the link element.
+  if (rel() == "stylesheet") {
+    OnStylesheetLoaded(document, content);
+  } else if (rel() == "splashscreen") {
+    OnSplashscreenLoaded(document, content);
+  } else {
+    NOTIMPLEMENTED();
+    return;
+  }
+  // Once the attempts to obtain the resource and its critical subresources
+  // are complete, the user agent must, if the loads were successful, queue a
+  // task to fire a simple event named load at the link element, or, if the
+  // resource or one of its critical subresources failed to completely load
+  // for any reason (e.g. DNS error, HTTP 404 response, a connection being
+  // prematurely closed, unsupported Content-Type), queue a task to fire a
+  // simple event named error at the link element.
   PostToDispatchEvent(FROM_HERE, base::Tokens::load());
 
-  // The element must delay the load event of the element's document until all
-  // the attempts to obtain the resource and its critical subresources are
-  // complete.
-  document->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
+  if (IsRelContentCriticalResource(rel())) {
+    // The element must delay the load event of the element's document until all
+    // the attempts to obtain the resource and its critical subresources are
+    // complete.
+    document->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
+  }
 
   MessageLoop::current()->PostTask(
       FROM_HERE, base::Bind(&HTMLLinkElement::ReleaseLoader, this));
@@ -160,6 +191,26 @@
       FROM_HERE, base::Bind(&HTMLLinkElement::ReleaseLoader, this));
 }
 
+void HTMLLinkElement::OnSplashscreenLoaded(Document* document,
+                                           const std::string& content) {
+  scoped_refptr<Window> window = document->window();
+
+  const base::optional<base::Callback<bool(const std::string&)>>
+      splash_screen_cache_callback = window->splash_screen_cache_callback();
+  if (splash_screen_cache_callback) {
+    splash_screen_cache_callback->Run(content);
+  }
+}
+
+void HTMLLinkElement::OnStylesheetLoaded(Document* document,
+                                         const std::string& content) {
+  scoped_refptr<cssom::CSSStyleSheet> 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);
+}
+
 void HTMLLinkElement::ReleaseLoader() {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(loader_);
diff --git a/src/cobalt/dom/html_link_element.h b/src/cobalt/dom/html_link_element.h
index 6f2e021..c0abd2c 100644
--- a/src/cobalt/dom/html_link_element.h
+++ b/src/cobalt/dom/html_link_element.h
@@ -16,6 +16,7 @@
 #define COBALT_DOM_HTML_LINK_ELEMENT_H_
 
 #include <string>
+#include <vector>
 
 #include "base/memory/scoped_ptr.h"
 #include "base/threading/thread_checker.h"
@@ -34,6 +35,7 @@
 class HTMLLinkElement : public HTMLElement {
  public:
   static const char kTagName[];
+  static const std::vector<std::string> kSupportedRelValues;
 
   explicit HTMLLinkElement(Document* document)
       : HTMLElement(document, base::Token(kTagName)) {}
@@ -68,6 +70,8 @@
 
   void OnLoadingDone(const std::string& content);
   void OnLoadingError(const std::string& error);
+  void OnSplashscreenLoaded(Document* document, const std::string& content);
+  void OnStylesheetLoaded(Document* document, const std::string& content);
   void ReleaseLoader();
 
   // Thread checker ensures all calls to DOM element are made from the same
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index dd2e52b..169ea07 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -215,15 +215,15 @@
       html_element_context()->can_play_type_handler()->CanPlayType(mime_type,
                                                                    "");
   MLOG() << "(" << mime_type << ") => " << result;
-  DLOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ") -> "
-             << result;
+  LOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ") -> "
+            << result;
 #else   // defined(COBALT_MEDIA_SOURCE_2016)
   std::string result =
       html_element_context()->can_play_type_handler()->CanPlayType(mime_type,
                                                                    key_system);
   MLOG() << "(" << mime_type << ", " << key_system << ") => " << result;
-  DLOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
-             << key_system << ") -> " << result;
+  LOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", "
+            << key_system << ") -> " << result;
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
   return result;
 }
diff --git a/src/cobalt/dom/keyboard_event.cc b/src/cobalt/dom/keyboard_event.cc
index 8b3356e..5439346 100644
--- a/src/cobalt/dom/keyboard_event.cc
+++ b/src/cobalt/dom/keyboard_event.cc
@@ -52,6 +52,7 @@
 
 KeyboardEvent::KeyboardEvent(UninitializedFlag uninitialized_flag)
     : UIEventWithKeyState(uninitialized_flag),
+      key_location_(kDomKeyLocationStandard),
       key_code_(0),
       char_code_(0),
       repeat_(false) {}
diff --git a/src/cobalt/dom/media_source/media_source.cc b/src/cobalt/dom/media_source/media_source.cc
index c3fab91..e90cf46 100644
--- a/src/cobalt/dom/media_source/media_source.cc
+++ b/src/cobalt/dom/media_source/media_source.cc
@@ -319,17 +319,17 @@
   SbMediaSupportType support_type =
       SbMediaCanPlayMimeAndKeySystem(type.c_str(), "");
   if (support_type == kSbMediaSupportTypeNotSupported) {
-    DLOG(INFO) << "MediaSource::IsTypeSupported(" << type
-               << ") -> not supported/false";
+    LOG(INFO) << "MediaSource::IsTypeSupported(" << type
+              << ") -> not supported/false";
     return false;
   }
   if (support_type == kSbMediaSupportTypeMaybe) {
-    DLOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> maybe/true";
+    LOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> maybe/true";
     return true;
   }
   if (support_type == kSbMediaSupportTypeProbably) {
-    DLOG(INFO) << "MediaSource::IsTypeSupported(" << type
-               << ") -> probably/true";
+    LOG(INFO) << "MediaSource::IsTypeSupported(" << type
+              << ") -> probably/true";
     return true;
   }
   NOTREACHED();
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index 733bfd2..82f47a1 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -340,8 +340,11 @@
   return PreRemove(node);
 }
 
-scoped_refptr<HTMLCollection> Node::children() const {
-  return HTMLCollection::CreateWithChildElements(this);
+scoped_refptr<HTMLCollection> Node::children() {
+  if (!children_collection_) {
+    children_collection_ = HTMLCollection::CreateWithChildElements(this);
+  }
+  return children_collection_;
 }
 
 Element* Node::first_element_child() const {
@@ -491,7 +494,10 @@
   while (node) {
     node->parent_ = NULL;
     node->next_sibling_ = NULL;
-    node = node->previous_sibling_;
+
+    Node* previous_sibling = node->previous_sibling_;
+    node->previous_sibling_ = NULL;
+    node = previous_sibling;
   }
   --(node_count_log.Get().count);
   GlobalStats::GetInstance()->Remove(this);
diff --git a/src/cobalt/dom/node.h b/src/cobalt/dom/node.h
index c32f3f1..c1ceb48 100644
--- a/src/cobalt/dom/node.h
+++ b/src/cobalt/dom/node.h
@@ -157,7 +157,7 @@
   // objects that can have children.
   //   https://www.w3.org/TR/dom/#parentnode
   //
-  scoped_refptr<HTMLCollection> children() const;
+  scoped_refptr<HTMLCollection> children();
   Element* first_element_child() const;
   Element* last_element_child() const;
   unsigned int child_element_count() const;
@@ -315,6 +315,8 @@
 
   RegisteredObserverList registered_observers_;
 
+  scoped_refptr<HTMLCollection> children_collection_;
+
   DISALLOW_COPY_AND_ASSIGN(Node);
 };
 
diff --git a/src/cobalt/dom/pointer_event.idl b/src/cobalt/dom/pointer_event.idl
index 0fd4b20..94a2bfa 100644
--- a/src/cobalt/dom/pointer_event.idl
+++ b/src/cobalt/dom/pointer_event.idl
@@ -12,7 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/2015/REC-pointerevents-20150224/
+// https://www.w3.org/TR/2015/REC-pointerevents-20150224/#pointerevent-interface
 
-[Constructor(DOMString type)]
-interface PointerEvent : MouseEvent {};
+[Constructor(DOMString type, optional PointerEventInit eventInitDict)]
+interface PointerEvent : MouseEvent {
+    readonly    attribute long      pointerId;
+    readonly    attribute double    width;
+    readonly    attribute double    height;
+    readonly    attribute float     pressure;
+    readonly    attribute long      tiltX;
+    readonly    attribute long      tiltY;
+    readonly    attribute DOMString pointerType;
+    readonly    attribute boolean   isPrimary;
+};
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index eaba14d..7252556 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -104,7 +104,9 @@
                const scoped_refptr<input::Camera3D>& camera_3d,
                const scoped_refptr<MediaSession>& media_session,
                int csp_insecure_allowed_token, int dom_max_element_depth,
-               float video_playback_rate_multiplier, ClockType clock_type)
+               float video_playback_rate_multiplier, ClockType clock_type,
+               const base::Callback<bool(const std::string&)>&
+                   splash_screen_cache_callback)
     : width_(width),
       height_(height),
       device_pixel_ratio_(device_pixel_ratio),
@@ -154,7 +156,8 @@
       ran_animation_frame_callbacks_callback_(
           ran_animation_frame_callbacks_callback),
       window_close_callback_(window_close_callback),
-      window_minimize_callback_(window_minimize_callback) {
+      window_minimize_callback_(window_minimize_callback),
+      splash_screen_cache_callback_(splash_screen_cache_callback) {
 #if !defined(ENABLE_TEST_RUNNER)
   UNREFERENCED_PARAMETER(clock_type);
 #endif
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index 8c669e8..8e95a10 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -142,7 +142,10 @@
       const scoped_refptr<cobalt::media_session::MediaSession>& media_session,
       int csp_insecure_allowed_token = 0, int dom_max_element_depth = 0,
       float video_playback_rate_multiplier = 1.f,
-      ClockType clock_type = kClockTypeSystemTime);
+      ClockType clock_type = kClockTypeSystemTime,
+      const base::Callback<bool(const std::string&)>&
+          splash_screen_cache_callback =
+              base::Callback<bool(const std::string&)>());
 
   // Web API: Window
   //
@@ -316,6 +319,11 @@
 
   DEFINE_WRAPPABLE_TYPE(Window);
 
+  const base::Callback<bool(const std::string&)> splash_screen_cache_callback()
+      const {
+    return splash_screen_cache_callback_;
+  }
+
  private:
   void StartDocumentLoad(
       loader::FetcherFactory* fetcher_factory, const GURL& url,
@@ -363,6 +371,8 @@
   const base::Closure window_close_callback_;
   const base::Closure window_minimize_callback_;
 
+  base::Callback<bool(const std::string&)> splash_screen_cache_callback_;
+
   DISALLOW_COPY_AND_ASSIGN(Window);
 };
 
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 946c261..7d1eb9a 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/input/input_device_manager_desktop.h"
 
+#include <cmath>
 #include <string>
 
 #include "cobalt/base/token.h"
@@ -155,6 +156,11 @@
   mouse_event->set_client_y(static_cast<float>(input_event->position().y()));
 }
 
+// Returns the value or the default_value when value is NaN.
+float value_or(float value, float default_value) {
+  return std::isnan(value) ? default_value : value;
+}
+
 }  // namespace
 
 void InputDeviceManagerDesktop::HandleKeyboardEvent(
@@ -180,11 +186,14 @@
 
   pointer_event.set_pointer_id(input_event->device_id());
 #if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
-  pointer_event.set_width(input_event->size().x());
-  pointer_event.set_height(input_event->size().y());
-  pointer_event.set_pressure(input_event->pressure());
-  pointer_event.set_tilt_x(static_cast<float>(input_event->tilt().x()));
-  pointer_event.set_tilt_y(static_cast<float>(input_event->tilt().y()));
+  pointer_event.set_width(value_or(input_event->size().x(), 0.0f));
+  pointer_event.set_height(value_or(input_event->size().y(), 0.0f));
+  pointer_event.set_pressure(value_or(input_event->pressure(),
+                                      input_event->modifiers() ? 0.5f : 0.0f));
+  pointer_event.set_tilt_x(
+      value_or(static_cast<float>(input_event->tilt().x()), 0.0f));
+  pointer_event.set_tilt_y(
+      value_or(static_cast<float>(input_event->tilt().y()), 0.0f));
 #endif
   pointer_event.set_is_primary(true);
   pointer_event_callback_.Run(type, pointer_event);
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index d07fc5f..a65f43f 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -518,7 +518,7 @@
   }
 
   const bool overflow_hidden =
-      computed_style()->overflow().get() == cssom::KeywordValue::GetHidden();
+      computed_style()->overflow() == cssom::KeywordValue::GetHidden();
 
   bool overflow_hidden_needs_to_be_applied = overflow_hidden;
 
@@ -1434,8 +1434,7 @@
     const scoped_refptr<render_tree::Node>& content_node,
     AnimateNode::Builder* /* animate_node_builder */,
     const math::Vector2dF& border_node_offset) {
-  DCHECK_EQ(computed_style()->overflow().get(),
-            cssom::KeywordValue::GetHidden());
+  DCHECK_EQ(computed_style()->overflow(), cssom::KeywordValue::GetHidden());
 
   // The "overflow" property specifies whether a box is clipped to its padding
   // edge.  Use a render_tree viewport filter to implement it.
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index b7edf46..e64f3b2 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -135,41 +135,34 @@
   // Invalidate the size now that the children have changed.
   update_size_results_valid_ = false;
 
-  // Children are only being removed from this container. As a result, the cross
-  // references only need to be invalidated if there is a non-empty cross
-  // reference list that can potentially lose an element.
+  // Handle invalidating cross references. Children are only being removed from
+  // this container, so cross references only need to be invalidated if there is
+  // a non-empty cross reference list that can potentially be impacted.
+
+  // If there are any positioned boxes, then they need to be re-generated.
   if (!positioned_child_boxes_.empty()) {
     are_cross_references_valid_ = false;
   }
-  if (!negative_z_index_stacking_context_children_.empty() ||
-      !non_negative_z_index_stacking_context_children_.empty()) {
-    // If this box is a stacking context, then any stacking context children
-    // were added directly by it; otherwise, the stacking context children were
-    // added by the containing stacking context (only a stacking context can
-    // cause stacking context children to be added).
-    if (IsStackingContext()) {
-      are_cross_references_valid_ = false;
-    } else {
-      DCHECK(negative_z_index_stacking_context_children_.empty());
-      GetStackingContext()->are_cross_references_valid_ = false;
+
+  // There are two cases where the stacking context's cross references can be
+  // impacted by children moving from one container to another. With both cases,
+  // stacking context children must exist or there is nothing to update.
+  //   1. Stacking context children are potentially moving from this child
+  //      container to the split sibling child container.
+  //   2. Stacking context children contained within this overflow hidden
+  //      container are potentially moving to the split sibling overflow hidden
+  //      container.
+  if (HasStackingContextChildren() ||
+      computed_style()->overflow() == cssom::KeywordValue::GetHidden()) {
+    // Walk up the tree until the nearest stacking context is found. If this box
+    // is a stacking context, then it will be used.
+    ContainerBox* nearest_stacking_context = this;
+    while (!nearest_stacking_context->IsStackingContext()) {
+      nearest_stacking_context = nearest_stacking_context->parent();
     }
-  } else if (computed_style()->overflow().get() ==
-                 cssom::KeywordValue::GetHidden() &&
-             !IsStackingContext()) {
-    // If this container box hides overflow and isn't a stacking context, then
-    // the nearest ancestor that is a stacking context needs to be invalidated.
-    // The reason for this is that the ancestor stacking context may have
-    // stacking context children that include this container as an overflow
-    // hidden containing block.
-    // NOTE: GetStackingContext() can't simply be called, because that will
-    // incorrectly return the parent, regardless of whether or not it is a
-    // stacking context, in the case where the containing block is not
-    // positioned.
-    ContainerBox* stacking_context_ancestor = parent();
-    while (!stacking_context_ancestor->IsStackingContext()) {
-      stacking_context_ancestor = stacking_context_ancestor->parent();
+    if (nearest_stacking_context->HasStackingContextChildren()) {
+      nearest_stacking_context->are_cross_references_valid_ = false;
     }
-    stacking_context_ancestor->are_cross_references_valid_ = false;
   }
 
   // Invalidate the render tree nodes now that the children have changed.
@@ -278,6 +271,11 @@
       overflow_hidden_to_apply));
 }
 
+bool ContainerBox::HasStackingContextChildren() const {
+  return !negative_z_index_stacking_context_children_.empty() ||
+         !non_negative_z_index_stacking_context_children_.empty();
+}
+
 namespace {
 
 Vector2dLayoutUnit GetOffsetFromContainingBlockToParent(Box* child_box) {
@@ -288,9 +286,8 @@
        ancestor_box = ancestor_box->parent()) {
     DCHECK(ancestor_box)
         << "Unable to find containing block while traversing parents.";
-    // We should not determine a used position through a transform, as
-    // rectangles may not remain rectangles past it, and thus obtaining
-    // a position may be misleading.
+    // It is not possible for the containing block to be more distant than an
+    // ancestor that is transformed.
     DCHECK(!ancestor_box->IsTransformed());
 
     relative_position += ancestor_box->GetContentBoxOffsetFromMarginBox();
@@ -343,8 +340,8 @@
       UpdateRectOfAbsolutelyPositionedChildBox(child_box,
                                                absolute_child_layout_params);
     } else if (child_box_position == cssom::KeywordValue::GetFixed()) {
-      UpdateRectOfFixedPositionedChildBox(child_box,
-                                          relative_child_layout_params);
+      UpdateRectOfAbsolutelyPositionedChildBox(child_box,
+                                               relative_child_layout_params);
     } else {
       DCHECK(child_box_position == cssom::KeywordValue::GetRelative());
       UpdateOffsetOfRelativelyPositionedChildBox(child_box,
@@ -422,17 +419,6 @@
   child_box->set_top(child_box->top() + offset.y());
 }
 
-void ContainerBox::UpdateRectOfFixedPositionedChildBox(
-    Box* child_box, const LayoutParams& child_layout_params) {
-  Vector2dLayoutUnit offset_from_containing_block_to_parent =
-      GetOffsetFromContainingBlockToParent(child_box);
-  child_box->SetStaticPositionLeftFromContainingBlockToParent(
-      offset_from_containing_block_to_parent.x());
-  child_box->SetStaticPositionTopFromContainingBlockToParent(
-      offset_from_containing_block_to_parent.y());
-  child_box->UpdateSize(child_layout_params);
-}
-
 void ContainerBox::UpdateRectOfAbsolutelyPositionedChildBox(
     Box* child_box, const LayoutParams& child_layout_params) {
   Vector2dLayoutUnit offset_from_containing_block_to_parent =
@@ -440,6 +426,10 @@
   // 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();
   child_box->SetStaticPositionLeftFromContainingBlockToParent(
       offset_from_containing_block_to_parent.x());
@@ -531,11 +521,14 @@
       GetOffsetFromChildContainerToContainingBlock(
           child_containing_block, child_info.containing_block_relationship) +
       child_container_offset_from_parent_node_;
-  if (child_info.box->computed_style()->position() ==
-      cssom::KeywordValue::GetAbsolute()) {
+  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();
   }
@@ -583,7 +576,7 @@
   OverflowHiddenInfo& overflow_hidden_info = overflow_hidden_stack_.back();
 
   ContainerBox* containing_block = overflow_hidden_info.containing_block;
-  DCHECK_EQ(containing_block->computed_style()->overflow().get(),
+  DCHECK_EQ(containing_block->computed_style()->overflow(),
             cssom::KeywordValue::GetHidden());
 
   // Determine the offset from the child container to this containing block's
@@ -619,72 +612,66 @@
         const Box* containing_block,
         const Box::RelationshipToBox
             containing_block_relationship_to_child_container) const {
+  if (containing_block_relationship_to_child_container == Box::kIsBox) {
+    DCHECK_EQ(child_container_, containing_block);
+    return Vector2dLayoutUnit();
+  }
+
   Vector2dLayoutUnit relative_position;
-  if (containing_block_relationship_to_child_container != Box::kIsBox) {
-    const Box* current_box =
-        containing_block_relationship_to_child_container == Box::kIsBoxAncestor
-            ? child_container_
-            : containing_block;
-    const Box* end_box =
-        containing_block_relationship_to_child_container == Box::kIsBoxAncestor
-            ? containing_block
-            : child_container_;
+  const Box* current_box =
+      containing_block_relationship_to_child_container == Box::kIsBoxAncestor
+          ? child_container_
+          : containing_block;
+  const Box* end_box =
+      containing_block_relationship_to_child_container == Box::kIsBoxAncestor
+          ? containing_block
+          : child_container_;
 
-    while (current_box != end_box) {
-#if !defined(NDEBUG)
-      // We should not determine a used position through a transform, as
-      // rectangles may not remain rectangles past it, and thus obtaining a
-      // position may be misleading.
-      if (current_box->IsTransformed()) {
-        DLOG(WARNING) << "Containing block offset calculations that include "
-                         "transforms may not be positioned correctly.";
-      }
-#endif
+  // Walk up the containing blocks from |current_box| to |end_box| adding the
+  // offsets from each box to its containing block.
+  // NOTE: |end_box| can be skipped during this walk both when |end_box| is not
+  // positioned and when a fixed position box is encountered during the walk. In
+  // this case, the walk will end when a box is found that either does not have
+  // a parent (meaning that it's the root box) or is transformed (it is
+  // impossible for |end_box| to be a more distant ancestor than a transformed
+  // 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();
 
-      relative_position += current_box->GetContentBoxOffsetFromMarginBox();
-      relative_position +=
-          current_box->margin_box_offset_from_containing_block();
-
-      const Box* next_box = current_box->GetContainingBlock();
-      if (!next_box) {
-        break;
-      }
-
-      if (current_box->computed_style()->position() ==
-          cssom::KeywordValue::GetAbsolute()) {
-        relative_position -= next_box->GetContentBoxOffsetFromPaddingBox();
-      }
-      current_box = next_box;
+    const Box* next_box = current_box->GetContainingBlock();
+    if (current_box->IsAbsolutelyPositioned()) {
+      relative_position -= next_box->GetContentBoxOffsetFromPaddingBox();
     }
+    current_box = next_box;
+  }
 
-    // If |current_box| does not equal |end_box|, then |end_box| was skipped
-    // during the walk up the tree. Initiate a second walk up the tree from the
-    // end box to the root (which is where the first walk ended).
-    // The end box can be skipped during the initial walk both when the end box
-    // is not positioned and also when a fixed position box is encountered
-    // during the walk. Subtract the offsets during this walk to remove the
-    // extra offsets added after passing the end box during the first walk.
-    std::swap(current_box, end_box);
-    while (current_box != end_box) {
-      relative_position -= current_box->GetContentBoxOffsetFromMarginBox();
-      relative_position -=
-          current_box->margin_box_offset_from_containing_block();
+  // If |current_box| does not equal |end_box|, then |end_box| was skipped
+  // during the walk up the tree. Initiate a second walk up the tree from the
+  // end box to the box where the first walk ended, subtracting the offsets
+  // during this walk to remove the extra offsets added after passing |end_box|
+  // during the first walk.
+  std::swap(current_box, end_box);
+  while (current_box != end_box) {
+    DCHECK(current_box->parent());
+    DCHECK(!current_box->IsTransformed());
 
-      const Box* next_box = current_box->GetContainingBlock();
-      if (current_box->computed_style()->position() ==
-          cssom::KeywordValue::GetAbsolute()) {
-        relative_position += next_box->GetContentBoxOffsetFromPaddingBox();
-      }
-      current_box = next_box;
+    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();
     }
+    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.
-    if (containing_block_relationship_to_child_container ==
-        Box::kIsBoxAncestor) {
-      relative_position = -relative_position;
-    }
+  // 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.
+  if (containing_block_relationship_to_child_container == Box::kIsBoxAncestor) {
+    relative_position = -relative_position;
   }
 
   return relative_position;
@@ -788,7 +775,7 @@
     bool has_absolute_position =
         computed_style()->position() == cssom::KeywordValue::GetAbsolute();
     bool has_overflow_hidden =
-        computed_style()->overflow().get() == cssom::KeywordValue::GetHidden();
+        computed_style()->overflow() == cssom::KeywordValue::GetHidden();
 
     stacking_context_container_box_stack->push_back(
         StackingContextContainerBoxInfo(
diff --git a/src/cobalt/layout/container_box.h b/src/cobalt/layout/container_box.h
index 810dcc4..3d184c4 100644
--- a/src/cobalt/layout/container_box.h
+++ b/src/cobalt/layout/container_box.h
@@ -172,6 +172,9 @@
       const ContainingBlocksWithOverflowHidden&
           containing_blocks_with_overflow_hidden_to_apply);
 
+  // Returns whether or not the container has any stacking context children.
+  bool HasStackingContextChildren() const;
+
   // Updates used values of left/top/right/bottom given the child_box's
   // 'position' property is set to 'relative'.
   //    https://www.w3.org/TR/CSS21/visuren.html#relative-positioning
@@ -182,13 +185,6 @@
   // This is meant to be called by UpdateRectOfPositionedChildBoxes(), after the
   // child has gone through the in-flow layout.
   //    https://www.w3.org/TR/CSS21/visuren.html#absolute-positioning
-  void UpdateRectOfFixedPositionedChildBox(
-      Box* child_box, const LayoutParams& child_layout_params);
-
-  // Updates the sizes of the absolutely positioned child box.
-  // This is meant to be called by UpdateRectOfPositionedChildBoxes(), after the
-  // child has gone through the in-flow layout.
-  //    https://www.w3.org/TR/CSS21/visuren.html#absolute-positioning
   void UpdateRectOfAbsolutelyPositionedChildBox(
       Box* child_box, const LayoutParams& child_layout_params);
 
diff --git a/src/cobalt/layout/layout.gyp b/src/cobalt/layout/layout.gyp
index f13959e..6b6031e 100644
--- a/src/cobalt/layout/layout.gyp
+++ b/src/cobalt/layout/layout.gyp
@@ -102,6 +102,11 @@
       'export_dependent_settings': [
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
       ],
+      'conditions': [
+        ['cobalt_enable_lib == 1', {
+          'defines' : ['FORCE_VIDEO_EXTERNAL_MESH'],
+        }],
+      ],
     },
 
     {
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index e8776e2..c61322a 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -308,13 +308,24 @@
     PunchThroughVideoNode::Builder builder(math::RectF(content_box_size()),
                                            set_bounds_cb_);
     border_node_builder->AddChild(new PunchThroughVideoNode(builder));
+  } else if (mtm_filter_function) {
+    RenderAndAnimateContentWithMapToMesh(border_node_builder,
+                                         mtm_filter_function);
   } else {
-    if (mtm_filter_function) {
-      RenderAndAnimateContentWithMapToMesh(border_node_builder,
-                                           mtm_filter_function);
-    } else {
-      RenderAndAnimateContentWithLetterboxing(border_node_builder);
-    }
+#if defined(FORCE_VIDEO_EXTERNAL_MESH)
+    AnimateNode::Builder animate_node_builder;
+    scoped_refptr<ImageNode> image_node = new ImageNode(NULL);
+    animate_node_builder.Add(image_node,
+                             base::Bind(&AnimateVideoImage, replace_image_cb_));
+
+    // Attach an empty map to mesh filter node to signal the need for an
+    // external mesh.
+    border_node_builder->AddChild(
+        new FilterNode(MapToMeshFilter(render_tree::kMono),
+                       new AnimateNode(animate_node_builder, image_node)));
+#else
+    RenderAndAnimateContentWithLetterboxing(border_node_builder);
+#endif
   }
 }
 
@@ -585,9 +596,9 @@
   if (spec.mesh_type() == cssom::MapToMeshFunction::kUrls) {
     // Custom mesh URLs.
     // Set a default mesh (in case no resolution-specific mesh matches).
-    cssom::URLValue* spec_url_value =
+    cssom::URLValue* default_url_value =
         base::polymorphic_downcast<cssom::URLValue*>(spec.mesh_url().get());
-    GURL default_url(spec_url_value->value());
+    GURL default_url(default_url_value->value());
 
     if (!default_url.is_valid()) {
       DLOG(WARNING) << kWarningInvalidMeshUrl;
@@ -629,8 +640,8 @@
 
       TRACE_EVENT2("cobalt::layout",
                    "ReplacedBox::RenderAndAnimateContentWithMapToMesh()",
-                   "height", meshes[i]->height_match(),
-                   "crc", mesh_projection->crc().value_or(-1));
+                   "height", meshes[i]->height_match(), "crc",
+                   mesh_projection->crc().value_or(-1));
 
       builder.AddResolutionMatchedMeshes(
           math::Size(meshes[i]->width_match(), meshes[i]->height_match()),
@@ -666,9 +677,9 @@
           filter_node, mtm_function->horizontal_fov_in_radians(),
           mtm_function->vertical_fov_in_radians()));
 #else
-  // Camera node unnecessary in VR, since the 3D scene is completely immersive,
-  // and the whole render tree is placed within it and subject to camera
-  // transforms, not just the map-to-mesh node.
+  // Camera node unnecessary in VR, since the 3D scene is completely
+  // immersive, and the whole render tree is placed within it and subject to
+  // camera transforms, not just the map-to-mesh node.
   // TODO: Reconcile both paths with respect to this if Cobalt adopts a global
   // camera or a document-wide notion of 3D space layout.
   border_node_builder->AddChild(filter_node);
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements-expected.png
new file mode 100644
index 0000000..e7a38ac
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements.html b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements.html
new file mode 100644
index 0000000..e06c01e
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<!--
+ | If the element has "position: fixed", the containing block is established
+ | by the padding edge of the nearest ancestor with a transform.
+ |   https://www.w3.org/TR/CSS21/visudet.html#containing-block-details
+ | NOTE: While this is not explicitly stated in the spec, which states that
+ | the containing block of a fixed position element must always be the viewport
+ | this is how all major browsers handle them.
+ -->
+<html>
+<head>
+  <style>
+    body {
+      margin: 0px;
+    }
+    .fixed-positioned {
+      position: fixed;
+    }
+    .transformed {
+      transform: rotate(0deg);
+    }
+    .level-1 {
+      background-color: #b3e5fc;
+      padding: 10px;
+    }
+    .level-2 {
+      background-color: #40c4ff;
+      padding: 20px;
+    }
+    .level-3 {
+      background-color: #00b0ff;
+      padding: 40px;
+    }
+    .level-4 {
+      background-color: #0091ea;
+      height: 120px;
+      width: 120px;
+    }
+    .top {
+      top: 0;
+      width: 60px;
+      height: 60px;
+    }
+    .fixed-positioned.level-4 {
+      background-color: #01579b;
+    }
+  </style>
+</head>
+<body>
+  <div class="fixed-positioned transformed level-1">
+    <div class="fixed-positioned transformed level-2">
+      <div class="level-3">
+        <div class="level-4"></div>
+        <div class="fixed-positioned top level-4"></div>
+      </div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees-expected.png
new file mode 100644
index 0000000..6a7d50c
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees.html
similarity index 100%
rename from src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees.html
rename to src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees.html
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms-expected.png
new file mode 100644
index 0000000..9244d5d
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-9-1-stacking-contexts-and-containing-blocks-with-transforms.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms.html
similarity index 100%
rename from src/cobalt/layout_tests/testdata/css-2-1/DISABLED-9-9-1-stacking-contexts-and-containing-blocks-with-transforms.html
rename to src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms.html
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks-expected.png
similarity index 100%
rename from src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks-expected.png
rename to src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks.html b/src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks.html
similarity index 100%
rename from src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks.html
rename to src/cobalt/layout_tests/testdata/css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks.html
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
index c0ef673..995b02a 100644
--- a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
@@ -4,6 +4,7 @@
 10-1-absolute-positioned-elements-container-block-is-absolute-positioned-ancestor
 10-1-absolute-positioned-elements-do-not-effect-containing-block-size
 10-1-containing-block-should-be-ancestor-padding-edge-for-absolutely-positioned-elements
+10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements
 10-1-containing-block-should-be-ancestor-padding-edge-for-percentage-of-absolutely-positioned-elements
 10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements
 10-1-non-positioned-stacking-context-above-containing-block-should-apply-proper-offset-to-children
@@ -153,6 +154,8 @@
 9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling
 9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context
 9-9-1-simple-positive-z-indices
-9-9-1-stacking-context-should-take-into-account-intermediate-containing-blocks
+9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees
+9-9-1-stacking-contexts-and-containing-blocks-with-transforms
+9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks
 9-9-1-stacking-contexts-differ-from-containing-blocks
 9-9-1-z-index-should-only-be-applied-to-positioned-elements
diff --git a/src/cobalt/loader/cache_fetcher.cc b/src/cobalt/loader/cache_fetcher.cc
new file mode 100644
index 0000000..e1e7350
--- /dev/null
+++ b/src/cobalt/loader/cache_fetcher.cc
@@ -0,0 +1,105 @@
+// 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/loader/cache_fetcher.h"
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/debug/trace_event.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+
+namespace cobalt {
+namespace loader {
+
+namespace {
+bool CacheURLToKey(const GURL& url, std::string* key) {
+  DCHECK(url.is_valid() && url.SchemeIs(kCacheScheme));
+  *key = url.path();
+  DCHECK_EQ('/', (*key)[0]);
+  DCHECK_EQ('/', (*key)[1]);
+  (*key).erase(0, 2);
+  return !key->empty();
+}
+}  // namespace
+
+const char kCacheScheme[] = "h5vcc-cache";
+
+CacheFetcher::CacheFetcher(
+    const GURL& url, const csp::SecurityCallback& security_callback,
+    Handler* handler,
+    const base::Callback<int(const std::string&, scoped_array<char>*)>&
+        read_cache_callback)
+    : Fetcher(handler),
+      url_(url),
+      security_callback_(security_callback),
+      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
+      read_cache_callback_(read_cache_callback) {
+  TRACE_EVENT0("cobalt::loader", "CacheFetcher::CacheFetcher");
+  // Cache assets can be loaded synchronously.
+  Fetch();
+}
+
+CacheFetcher::~CacheFetcher() {}
+
+void CacheFetcher::Fetch() {
+  if (!IsAllowedByCsp()) {
+    std::string msg(base::StringPrintf("URL %s rejected by security policy.",
+                                       url_.spec().c_str()));
+    handler()->OnError(this, msg);
+    return;
+  }
+
+  std::string key;
+  if (!CacheURLToKey(url_, &key)) {
+    std::string msg(
+        base::StringPrintf("Invalid cache URL: %s.", url_.spec().c_str()));
+    handler()->OnError(this, msg);
+    return;
+  }
+
+  GetCacheData(key);
+}
+
+void CacheFetcher::GetCacheData(const std::string& key) {
+  TRACE_EVENT0("cobalt::loader", "CacheFetcher::GetCacheData");
+  const char kFailedToReadCache[] = "Failed to read cache.";
+
+  DCHECK(!read_cache_callback_.is_null());
+  scoped_array<char> buffer;
+  int buffer_size = read_cache_callback_.Run(key, &buffer);
+  if (!buffer.get()) {
+    handler()->OnError(this, kFailedToReadCache);
+    return;
+  }
+
+  handler()->OnReceived(this, buffer.get(), buffer_size);
+  handler()->OnDone(this);
+}
+
+bool CacheFetcher::IsAllowedByCsp() {
+  bool did_redirect = false;
+  if (security_callback_.is_null() ||
+      security_callback_.Run(url_, did_redirect)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/cache_fetcher.h b/src/cobalt/loader/cache_fetcher.h
new file mode 100644
index 0000000..9c9e274
--- /dev/null
+++ b/src/cobalt/loader/cache_fetcher.h
@@ -0,0 +1,63 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_LOADER_CACHE_FETCHER_H_
+#define COBALT_LOADER_CACHE_FETCHER_H_
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "cobalt/csp/content_security_policy.h"
+#include "cobalt/loader/fetcher.h"
+
+namespace cobalt {
+namespace loader {
+
+extern const char kCacheScheme[];
+
+// CacheFetcher is for fetching data in the system cache,
+// kSbSystemPathCacheDirectory. Cached splash screen HTML documents
+// are stored there, for instance. Their subpaths are based on the
+// initial URL they correspond to and must be fully specified in the
+// URL passed into CacheFetcher. See SplashScreenCache for an example usage.
+class CacheFetcher : public Fetcher {
+ public:
+  CacheFetcher(
+      const GURL& url, const csp::SecurityCallback& security_callback,
+      Handler* handler,
+      const base::Callback<int(const std::string&, scoped_array<char>*)>&
+          read_cache_callback =
+              base::Callback<int(const std::string&, scoped_array<char>*)>());
+
+  ~CacheFetcher() OVERRIDE;
+
+ private:
+  void Fetch();
+  void GetCacheData(const std::string& key);
+  bool IsAllowedByCsp();
+
+  GURL url_;
+  csp::SecurityCallback security_callback_;
+  base::WeakPtrFactory<CacheFetcher> weak_ptr_factory_;
+  base::Callback<int(const std::string&, scoped_array<char>*)>
+      read_cache_callback_;
+};
+
+}  // namespace loader
+}  // namespace cobalt
+
+#endif  // COBALT_LOADER_CACHE_FETCHER_H_
diff --git a/src/cobalt/loader/embedded_fetcher.cc b/src/cobalt/loader/embedded_fetcher.cc
index de82486..1f73f16 100644
--- a/src/cobalt/loader/embedded_fetcher.cc
+++ b/src/cobalt/loader/embedded_fetcher.cc
@@ -36,6 +36,8 @@
 }
 }  // namespace
 
+const char kEmbeddedScheme[] = "h5vcc-embedded";
+
 EmbeddedFetcher::EmbeddedFetcher(const GURL& url,
                                  const csp::SecurityCallback& security_callback,
                                  Handler* handler, const Options& options)
diff --git a/src/cobalt/loader/embedded_fetcher.h b/src/cobalt/loader/embedded_fetcher.h
index a577556..20b1295 100644
--- a/src/cobalt/loader/embedded_fetcher.h
+++ b/src/cobalt/loader/embedded_fetcher.h
@@ -25,7 +25,7 @@
 namespace cobalt {
 namespace loader {
 
-const char kEmbeddedScheme[] = "h5vcc-embedded";
+extern const char kEmbeddedScheme[];
 
 // EmbeddedFetcher is for fetching data embedded in the binary, probably
 // generated using generate_data_header.py. Data will be in the form of a
diff --git a/src/cobalt/loader/embedded_resources/black_splash_screen.html b/src/cobalt/loader/embedded_resources/black_splash_screen.html
new file mode 100644
index 0000000..40e5bcc
--- /dev/null
+++ b/src/cobalt/loader/embedded_resources/black_splash_screen.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+  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.
+-->
+<html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy: style-src 'sha256-xmVxpUwZn65/t+v5EPwcm3g3WxZz1bJoPW/GtjJs75I=';">
+</head>
+
+<body style="background-color: #000000;"></body>
+</html>
diff --git a/src/cobalt/loader/embedded_resources/cobalt_splash_screen.html b/src/cobalt/loader/embedded_resources/cobalt_splash_screen.html
index df1ac86..8c06361 100644
--- a/src/cobalt/loader/embedded_resources/cobalt_splash_screen.html
+++ b/src/cobalt/loader/embedded_resources/cobalt_splash_screen.html
@@ -17,10 +17,10 @@
 <html>
 
 <head>
-<meta http-equiv="Content-Security-Policy" content="default-src 'none';
-    script-src h5vcc-embedded://*/splash_screen.js;
-    style-src h5vcc-embedded://*/cobalt_splash_screen.css;
-    img-src h5vcc-embedded://*/cobalt_word_logo_1024.png;">
+  <meta http-equiv="Content-Security-Policy" content="default-src 'none';
+      script-src h5vcc-embedded://*/splash_screen.js;
+      style-src h5vcc-embedded://*/cobalt_splash_screen.css;
+      img-src h5vcc-embedded://*/cobalt_word_logo_1024.png;">
 <link rel="stylesheet" type="text/css"
     href="h5vcc-embedded://cobalt_splash_screen.css">
 </head>
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.css b/src/cobalt/loader/embedded_resources/youtube_splash_screen.css
similarity index 100%
rename from src/cobalt/loader/embedded_resources/splash_screen.css
rename to src/cobalt/loader/embedded_resources/youtube_splash_screen.css
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.html b/src/cobalt/loader/embedded_resources/youtube_splash_screen.html
similarity index 80%
rename from src/cobalt/loader/embedded_resources/splash_screen.html
rename to src/cobalt/loader/embedded_resources/youtube_splash_screen.html
index f79d132..d40bf9b 100644
--- a/src/cobalt/loader/embedded_resources/splash_screen.html
+++ b/src/cobalt/loader/embedded_resources/youtube_splash_screen.html
@@ -17,12 +17,12 @@
 <html>
 
 <head>
-<meta http-equiv="Content-Security-Policy" content="default-src 'none';
-    script-src h5vcc-embedded://*/splash_screen.js;
-    style-src h5vcc-embedded://*/splash_screen.css;
-    img-src h5vcc-embedded://*/you_tube_logo.png;">
+  <meta http-equiv="Content-Security-Policy" content="default-src 'none';
+      script-src h5vcc-embedded://*/splash_screen.js;
+      style-src h5vcc-embedded://*/youtube_splash_screen.css;
+      img-src h5vcc-embedded://*/you_tube_logo.png;">
 <link rel="stylesheet" type="text/css"
-    href="h5vcc-embedded://splash_screen.css">
+    href="h5vcc-embedded://youtube_splash_screen.css">
 </head>
 
 <body>
diff --git a/src/cobalt/loader/fetcher_factory.cc b/src/cobalt/loader/fetcher_factory.cc
index d9ea1bf..1e70cf4 100644
--- a/src/cobalt/loader/fetcher_factory.cc
+++ b/src/cobalt/loader/fetcher_factory.cc
@@ -19,9 +19,11 @@
 #include "base/bind.h"
 #include "base/file_path.h"
 #include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/path_service.h"
 #include "cobalt/loader/about_fetcher.h"
 #include "cobalt/loader/blob_fetcher.h"
+#include "cobalt/loader/cache_fetcher.h"
 #include "cobalt/loader/embedded_fetcher.h"
 #include "cobalt/loader/file_fetcher.h"
 #include "cobalt/loader/net_fetcher.h"
@@ -56,7 +58,9 @@
 }  // namespace
 
 FetcherFactory::FetcherFactory(network::NetworkModule* network_module)
-    : file_thread_("File"), network_module_(network_module) {
+    : file_thread_("File"),
+      network_module_(network_module),
+      read_cache_callback_() {
   file_thread_.Start();
 }
 
@@ -64,17 +68,21 @@
                                const FilePath& extra_search_dir)
     : file_thread_("File"),
       network_module_(network_module),
-      extra_search_dir_(extra_search_dir) {
+      extra_search_dir_(extra_search_dir),
+      read_cache_callback_() {
   file_thread_.Start();
 }
 
 FetcherFactory::FetcherFactory(
     network::NetworkModule* network_module, const FilePath& extra_search_dir,
-    const BlobFetcher::ResolverCallback& blob_resolver)
+    const BlobFetcher::ResolverCallback& blob_resolver,
+    const base::Callback<int(const std::string&, scoped_array<char>*)>&
+        read_cache_callback)
     : file_thread_("File"),
       network_module_(network_module),
       extra_search_dir_(extra_search_dir),
-      blob_resolver_(blob_resolver) {
+      blob_resolver_(blob_resolver),
+      read_cache_callback_(read_cache_callback) {
   file_thread_.Start();
 }
 
@@ -121,6 +129,17 @@
                     "could not fetch the URL: "
                  << url;
     }
+  } else if (url.SchemeIs(kCacheScheme)) {
+    if (read_cache_callback_.is_null()) {
+      LOG(ERROR) << "read_cache_callback_ must be provided to CacheFetcher for "
+                    "accessing h5vcc-cache:// . This is not available in the "
+                    "main WebModule.";
+      DCHECK(!read_cache_callback_.is_null());
+      return fetcher.Pass();
+    }
+
+    fetcher.reset(new CacheFetcher(url, url_security_callback, handler,
+                                   read_cache_callback_));
   } else {  // NOLINT(readability/braces)
     DCHECK(network_module_) << "Network module required.";
     NetFetcher::Options options;
diff --git a/src/cobalt/loader/fetcher_factory.h b/src/cobalt/loader/fetcher_factory.h
index bd60a4f..a367df7 100644
--- a/src/cobalt/loader/fetcher_factory.h
+++ b/src/cobalt/loader/fetcher_factory.h
@@ -15,8 +15,11 @@
 #ifndef COBALT_LOADER_FETCHER_FACTORY_H_
 #define COBALT_LOADER_FETCHER_FACTORY_H_
 
+#include <string>
+
+#include "base/callback.h"
 #include "base/file_path.h"
-#include "base/optional.h"
+#include "base/memory/scoped_ptr.h"
 #include "base/threading/thread.h"
 #include "cobalt/csp/content_security_policy.h"
 #include "cobalt/loader/blob_fetcher.h"
@@ -35,9 +38,12 @@
   explicit FetcherFactory(network::NetworkModule* network_module);
   FetcherFactory(network::NetworkModule* network_module,
                  const FilePath& extra_search_dir);
-  FetcherFactory(network::NetworkModule* network_module,
-                 const FilePath& extra_search_dir,
-                 const BlobFetcher::ResolverCallback& blob_resolver);
+  FetcherFactory(
+      network::NetworkModule* network_module, const FilePath& extra_search_dir,
+      const BlobFetcher::ResolverCallback& blob_resolver,
+      const base::Callback<int(const std::string&, scoped_array<char>*)>&
+          read_cache_callback =
+              base::Callback<int(const std::string&, scoped_array<char>*)>());
 
   // Creates a fetcher. Returns NULL if the creation fails.
   scoped_ptr<Fetcher> CreateFetcher(const GURL& url, Fetcher::Handler* handler);
@@ -53,6 +59,8 @@
   network::NetworkModule* network_module_;
   FilePath extra_search_dir_;
   BlobFetcher::ResolverCallback blob_resolver_;
+  base::Callback<int(const std::string&, scoped_array<char>*)>
+      read_cache_callback_;
 };
 
 }  // namespace loader
diff --git a/src/cobalt/loader/image/image_data_decoder.cc b/src/cobalt/loader/image/image_data_decoder.cc
index 33e939f..0e20ce7 100644
--- a/src/cobalt/loader/image/image_data_decoder.cc
+++ b/src/cobalt/loader/image/image_data_decoder.cc
@@ -23,8 +23,8 @@
 namespace image {
 
 namespace {
-// The capacity of data buffer.
-uint32 kMaxBufferSizeBytes = 32 * 1024L;
+// Sanity check max size of data buffer.
+uint32 kMaxBufferSizeBytes = 4 * 1024 * 1024L;
 }  // namespace
 
 ImageDataDecoder::ImageDataDecoder(
@@ -36,7 +36,6 @@
 void ImageDataDecoder::DecodeChunk(const uint8* data, size_t size) {
   TRACE_EVENT0("cobalt::loader::image_decoder",
                "ImageDataDecoder::DecodeChunk");
-
   size_t offset = 0;
   while (offset < size) {
     if (state_ == kError) {
@@ -48,11 +47,12 @@
     size_t input_size;
 
     if (data_buffer_.empty()) {
-      // Nothing in the data_buffer, so no data append needs to be performed.
+      // Nothing in |data_buffer_|, so no data append needs to be performed.
       input_bytes = data + offset;
       input_size = size - offset;
       offset += input_size;
     } else {
+      DCHECK_GE(kMaxBufferSizeBytes, data_buffer_.size());
       size_t fill_buffer_size =
           std::min(kMaxBufferSizeBytes - data_buffer_.size(), size - offset);
 
@@ -67,18 +67,30 @@
     }
 
     size_t decoded_size = DecodeChunkInternal(input_bytes, input_size);
-    size_t undecoded_size = input_size - decoded_size;
+    if (decoded_size == 0 && offset < size) {
+      LOG(ERROR) << "Unable to make progress decoding image.";
+      state_ = kError;
+      return;
+    }
 
+    size_t undecoded_size = input_size - decoded_size;
     if (undecoded_size == 0) {
       // Remove all elements from the data_buffer.
       data_buffer_.clear();
     } else {
-      data_buffer_.reserve(kMaxBufferSizeBytes);
       if (data_buffer_.empty()) {
-        // data_buffer is empty, so assign the undecoded data to it.
+        if (undecoded_size > kMaxBufferSizeBytes) {
+          LOG(ERROR) << "Max buffer size too small: " << undecoded_size
+                     << "bytes required!";
+          state_ = kError;
+          return;
+        }
+
+        // |data_buffer_| is empty, so assign the undecoded data to it.
+        data_buffer_.reserve(undecoded_size);
         data_buffer_.assign(data + offset - undecoded_size, data + offset);
       } else if (decoded_size != 0) {
-        // data_buffer is not empty, so erase the decoded data from it.
+        // |data_buffer_| is not empty, so erase the decoded data from it.
         data_buffer_.erase(
             data_buffer_.begin(),
             data_buffer_.begin() + static_cast<ptrdiff_t>(decoded_size));
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index b4fb1fe..2fad2ec 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -23,6 +23,8 @@
       'sources': [
         'blob_fetcher.cc',
         'blob_fetcher.h',
+        'cache_fetcher.cc',
+        'cache_fetcher.h',
         'decoder.h',
         'embedded_fetcher.cc',
         'embedded_fetcher.h',
@@ -176,9 +178,12 @@
         'input_directory': 'embedded_resources',
       },
       'sources': [
+        '<(input_directory)/black_splash_screen.html',
+        '<(input_directory)/cobalt_splash_screen.css',
+        '<(input_directory)/cobalt_splash_screen.html',
         '<(input_directory)/equirectangular_40_40.msh',
-        '<(input_directory)/splash_screen.css',
-        '<(input_directory)/splash_screen.html',
+        '<(input_directory)/youtube_splash_screen.css',
+        '<(input_directory)/youtube_splash_screen.html',
         '<(input_directory)/splash_screen.js',
         '<(input_directory)/you_tube_logo.png',
       ],
diff --git a/src/cobalt/media/sandbox/sandbox.gyp b/src/cobalt/media/sandbox/sandbox.gyp
index 1a2a762..4ac0fb6 100644
--- a/src/cobalt/media/sandbox/sandbox.gyp
+++ b/src/cobalt/media/sandbox/sandbox.gyp
@@ -62,6 +62,7 @@
         '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
         '<(DEPTH)/cobalt/trace_event/trace_event.gyp:trace_event',
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
+        '<@(cobalt_platform_dependencies)',
       ],
     },
 
@@ -100,6 +101,7 @@
         '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
         '<(DEPTH)/cobalt/trace_event/trace_event.gyp:trace_event',
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
+        '<@(cobalt_platform_dependencies)',
       ],
     },
 
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h
index 2c126e6..060b080 100644
--- a/src/cobalt/render_tree/map_to_mesh_filter.h
+++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -28,17 +28,17 @@
 namespace render_tree {
 
 enum StereoMode {
-  kMono,
+  kMono = 0,
   // Stereoscopic modes where each half of the video represents the view of
   // one eye, and where the texture coordinates of the meshes need to be
   // scaled and offset to the appropriate half. The same mesh may be used for
   // both eyes, or there may be one for each eye.
-  kLeftRight,
-  kTopBottom,
+  kLeftRight = 1,
+  kTopBottom = 2,
   // Like kLeftRight, but where the texture coordinates already refer to the
   // corresponding half of the video and need no adjustment. This can only
   // happen when there are distinct meshes for each eye.
-  kLeftRightUnadjustedTextureCoords
+  kLeftRightUnadjustedTextureCoords = 3
 };
 
 // A MapToMeshFilter can be used to map source content onto a 3D mesh, within a
@@ -106,6 +106,13 @@
     }
   }
 
+  // A filter without a an explicit mesh, to represent mesh-mapped content whose
+  // mesh will be supplied externally.
+  explicit MapToMeshFilter(StereoMode stereo_mode)
+      : stereo_mode_(stereo_mode), data_() {
+    DCHECK(stereo_mode != kLeftRightUnadjustedTextureCoords);
+  }
+
   StereoMode stereo_mode() const { return stereo_mode_; }
 
   // The omission of the |resolution| parameter will yield the default
diff --git a/src/cobalt/render_tree/mesh.h b/src/cobalt/render_tree/mesh.h
index 8f6c7a2..fdb827f 100644
--- a/src/cobalt/render_tree/mesh.h
+++ b/src/cobalt/render_tree/mesh.h
@@ -45,13 +45,15 @@
     Vertex(float x, float y, float z, float u, float v)
         : x(x), y(y), z(z), u(u), v(v) {}
   };
+  COMPILE_ASSERT(sizeof(Vertex) == sizeof(float) * 5,
+                 vertex_struct_size_exceeds_5_floats);
 
   // Defines how the mesh faces are constructed from the ordered list of
   // vertices.
   enum DrawMode {
-    kDrawModeTriangles,
-    kDrawModeTriangleStrip,
-    kDrawModeTriangleFan
+    kDrawModeTriangles = 0,
+    kDrawModeTriangleStrip = 1,
+    kDrawModeTriangleFan = 2
   };
 
   virtual uint32 GetEstimatedSizeInBytes() const = 0;
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc
index c7708af..e29c3f4 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -274,8 +274,13 @@
     // with eglMakeCurrent to avoid polluting the previous EGLSurface target.
     DCHECK_NE(surface->GetPlatformHandle(), 0);
 
-    egl_surface = null_surface_->GetSurface();
-    EGL_CALL(eglMakeCurrent(display_, egl_surface, egl_surface, context_));
+    // Since we don't care about what surface is backing the default
+    // framebuffer, don't change draw surfaces unless we simply don't have one
+    // already.
+    if (!IsCurrent()) {
+      egl_surface = null_surface_->GetSurface();
+      EGL_CALL(eglMakeCurrent(display_, egl_surface, egl_surface, context_));
+    }
     GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, surface->GetPlatformHandle()));
   }
 
@@ -303,7 +308,12 @@
 }
 
 void GraphicsContextEGL::MakeCurrent() {
-  MakeCurrentWithSurface(null_surface_);
+  // Some GL drivers do *not* handle switching contexts in the middle of a
+  // frame very well, so with this change we avoid making a new surface
+  // current if we don't actually care about what surface is current.
+  if (!IsCurrent()) {
+    MakeCurrentWithSurface(null_surface_);
+  }
 }
 
 void GraphicsContextEGL::ReleaseCurrentContext() {
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h
index 6ed533f..ef6d668 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.h
+++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -116,6 +116,9 @@
 
   void Blit(GLuint texture, int x, int y, int width, int height);
 
+  // Returns if this graphics context is current on the calling thread or not.
+  bool IsCurrent() const { return is_current_; }
+
  private:
   // Performs a test to determine if the pixel data returned by glReadPixels
   // needs to be vertically flipped or not.  This test is expensive, so it
diff --git a/src/cobalt/renderer/rasterizer/common/common.gyp b/src/cobalt/renderer/rasterizer/common/common.gyp
index af0e808..9c17122 100644
--- a/src/cobalt/renderer/rasterizer/common/common.gyp
+++ b/src/cobalt/renderer/rasterizer/common/common.gyp
@@ -19,6 +19,8 @@
       'type': 'static_library',
 
       'sources': [
+        'find_node.cc',
+        'find_node.h',
         'offscreen_render_coordinate_mapping.cc',
         'offscreen_render_coordinate_mapping.h',
         'scratch_surface_cache.cc',
diff --git a/src/cobalt/renderer/rasterizer/common/find_node.cc b/src/cobalt/renderer/rasterizer/common/find_node.cc
new file mode 100644
index 0000000..111aee1
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/common/find_node.cc
@@ -0,0 +1,142 @@
+// 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/common/find_node.h"
+
+#include "base/optional.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/render_tree/animations/animate_node.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace common {
+
+class FinderNodeVisitor : public render_tree::NodeVisitor {
+ public:
+  FinderNodeVisitor(NodeFilterFunction<render_tree::Node> filter_function,
+                    base::optional<NodeReplaceFunction> replace_function)
+      : filter_function_(filter_function),
+        replace_function_(replace_function) {}
+
+  void Visit(render_tree::animations::AnimateNode* animate) OVERRIDE {
+    VisitNode(animate);
+  }
+  void Visit(render_tree::CompositionNode* composition_node) OVERRIDE {
+    VisitNode(composition_node);
+  }
+  void Visit(render_tree::FilterNode* filter_node) OVERRIDE {
+    VisitNode(filter_node);
+  }
+  void Visit(render_tree::ImageNode* image_node) OVERRIDE {
+    VisitNode(image_node);
+  }
+  void Visit(render_tree::MatrixTransform3DNode* transform_3d_node) OVERRIDE {
+    VisitNode(transform_3d_node);
+  }
+  void Visit(render_tree::MatrixTransformNode* transform_node) OVERRIDE {
+    VisitNode(transform_node);
+  }
+  void Visit(render_tree::PunchThroughVideoNode* punch_through) OVERRIDE {
+    VisitNode(punch_through);
+  }
+  void Visit(render_tree::RectNode* rect) OVERRIDE { VisitNode(rect); }
+  void Visit(render_tree::RectShadowNode* rect) OVERRIDE { VisitNode(rect); }
+  void Visit(render_tree::TextNode* text) OVERRIDE { VisitNode(text); }
+
+  virtual ~FinderNodeVisitor() {}
+
+  scoped_refptr<render_tree::Node> GetFoundNode() const { return found_node_; }
+
+  scoped_refptr<render_tree::Node> GetReplaceWithNode() {
+    return replace_with_;
+  }
+
+ private:
+  template <typename T>
+  void VisitNode(T* node) {
+    if (filter_function_.Run(static_cast<render_tree::Node*>(node))) {
+      found_node_ = node;
+      if (replace_function_) {
+        replace_with_ = replace_function_->Run(node);
+      }
+    } else {
+      VisitNodeChildren(node);
+    }
+  }
+
+  template <typename T>
+  typename base::enable_if<!render_tree::ChildIterator<T>::has_children>::type
+  VisitNodeChildren(T* node) {
+    // No children to visit.
+  }
+
+  template <typename T>
+  typename base::enable_if<render_tree::ChildIterator<T>::has_children>::type
+  VisitNodeChildren(T* node) {
+    render_tree::ChildIterator<T> child_iterator(node);
+    while (render_tree::Node* child = child_iterator.GetCurrent()) {
+      child->Accept(this);
+      if (found_node_) {
+        if (replace_with_) {
+          child_iterator.ReplaceCurrent(replace_with_);
+        }
+        break;
+      }
+      child_iterator.Next();
+    }
+    if (replace_with_) {
+      replace_with_ = new T(child_iterator.TakeReplacedChildrenBuilder());
+    }
+  }
+
+  NodeFilterFunction<render_tree::Node> filter_function_;
+  base::optional<NodeReplaceFunction> replace_function_;
+
+  scoped_refptr<render_tree::Node> found_node_;
+  scoped_refptr<render_tree::Node> replace_with_;
+};
+
+template <>
+NodeSearchResult<render_tree::Node> FindNode<render_tree::Node>(
+    const scoped_refptr<render_tree::Node>& tree,
+    NodeFilterFunction<render_tree::Node> filter_function,
+    base::optional<NodeReplaceFunction> replace_function) {
+  FinderNodeVisitor visitor(filter_function, replace_function);
+  tree->Accept(&visitor);
+
+  NodeSearchResult<render_tree::Node> result;
+  result.found_node = visitor.GetFoundNode();
+  result.replaced_tree = visitor.GetReplaceWithNode();
+
+  if (result.replaced_tree == NULL) {
+    result.replaced_tree = tree;
+  }
+  return result;
+}
+
+bool HasMapToMesh(render_tree::FilterNode* filter_node) {
+  return static_cast<bool>(filter_node->data().map_to_mesh_filter);
+}
+
+render_tree::Node* ReplaceWithEmptyCompositionNode(
+    render_tree::Node* filter_node) {
+  return new render_tree::CompositionNode(
+      render_tree::CompositionNode::Builder());
+}
+
+}  // namespace common
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/renderer/rasterizer/common/find_node.h b/src/cobalt/renderer/rasterizer/common/find_node.h
new file mode 100644
index 0000000..af694b0
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/common/find_node.h
@@ -0,0 +1,98 @@
+// 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_COMMON_FIND_NODE_H_
+#define COBALT_RENDERER_RASTERIZER_COMMON_FIND_NODE_H_
+
+#include "base/bind.h"
+#include "cobalt/base/enable_if.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/base/type_id.h"
+#include "cobalt/render_tree/child_iterator.h"
+#include "cobalt/render_tree/node.h"
+#include "cobalt/render_tree/node_visitor.h"
+
+namespace cobalt {
+namespace renderer {
+namespace rasterizer {
+namespace common {
+
+using NodeReplaceFunction =
+    base::Callback<render_tree::Node*(render_tree::Node*)>;
+
+template <typename T = render_tree::Node>
+using NodeFilterFunction = base::Callback<bool(T*)>;
+
+template <typename T = render_tree::Node>
+struct NodeSearchResult {
+  scoped_refptr<T> found_node;  // Null if no such node is found.
+  scoped_refptr<render_tree::Node> replaced_tree;
+};
+
+// Finds the first node of type |T| that passes an optional |filter_function|,
+// and optionally replaces it in the tree with the result of |replace_function|.
+//
+// Example usage:
+//     auto filter = base::Bind(&CheckSomethingOnNode)
+//     auto replace = base::Bind(&ReplaceNode)
+//     auto search_results = FindNode<SomeNode>(tree, filter, replace);
+//     DoSomethingWithNode(search_results.found_node);
+//     DoSomethingWithTree(search_results.replaced_tree);
+template <typename T>
+NodeSearchResult<T> FindNode(
+    const scoped_refptr<render_tree::Node>& tree,
+    NodeFilterFunction<T> typed_filter_function = base::Bind([](T*) {
+      return true;
+    }),
+    base::optional<NodeReplaceFunction> replace_function = base::nullopt) {
+  // Wrap the typed filter with an untyped callback.
+  auto type_checking_filter_function =
+      base::Bind([typed_filter_function](render_tree::Node* node) {
+        if (node->GetTypeId() != base::GetTypeId<T>()) {
+          return false;
+        }
+
+        auto* typed_node = base::polymorphic_downcast<T*>(node);
+        return typed_filter_function.Run(typed_node);
+      });
+  // Call the default untyped FindNode with the above wrap.
+  NodeSearchResult<> untyped_result = FindNode<render_tree::Node>(
+      tree, type_checking_filter_function, replace_function);
+  NodeSearchResult<T> typed_result;
+  typed_result.replaced_tree = untyped_result.replaced_tree;
+  typed_result.found_node =
+      base::polymorphic_downcast<T*>(untyped_result.found_node.get());
+
+  return typed_result;
+}
+
+// Same as the above, but not filtering nodes by type, so the filter can act on
+// nodes of different classes.
+template <>
+NodeSearchResult<render_tree::Node> FindNode<render_tree::Node>(
+    const scoped_refptr<render_tree::Node>& tree,
+    NodeFilterFunction<render_tree::Node> filter_function,
+    base::optional<NodeReplaceFunction> replace_function);
+
+// Checks whether the given filter node has a MapToMesh filter in it.
+bool HasMapToMesh(render_tree::FilterNode* node);
+
+render_tree::Node* ReplaceWithEmptyCompositionNode(render_tree::Node* node);
+
+}  // namespace common
+}  // namespace rasterizer
+}  // namespace renderer
+}  // namespace cobalt
+
+#endif  // COBALT_RENDERER_RASTERIZER_COMMON_FIND_NODE_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 88d6bdd..8153798 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -125,8 +125,7 @@
     return 1.0f;
   }
 
-  // The cached contents' sub-pixel offset must be within 0.5 pixels to ensure
-  // appropriate positioning.
+  // Use the cached contents' sub-pixel offset as the error rating.
   math::PointF desired_offset(
       desired_bounds.x() - std::floor(desired_bounds.x()),
       desired_bounds.y() - std::floor(desired_bounds.y()));
@@ -135,11 +134,9 @@
       cached_bounds.y() - std::floor(cached_bounds.y()));
   float error_x = std::abs(desired_offset.x() - cached_offset.x());
   float error_y = std::abs(desired_offset.y() - cached_offset.y());
-  if (error_x >= 0.5f || error_y >= 0.5f) {
-    return 1.0f;
-  }
 
-  return error_x + error_y;
+  // Any sub-pixel offset is okay. Return something less than 1.
+  return (error_x + error_y) * 0.49f;
 }
 
 }  // namespace
@@ -628,6 +625,9 @@
   if (!(*out_content_cached)) {
     offscreen_target_manager_->AllocateOffscreenTarget(node,
         content_size, mapped_bounds, out_target_info);
+  } else {
+    // Maintain the size of the cached contents to avoid scaling artifacts.
+    content_size = out_target_info->region.size();
   }
 
   // If no offscreen target could be allocated, then the render tree node will
diff --git a/src/cobalt/renderer/rasterizer/lib/exported/video.h b/src/cobalt/renderer/rasterizer/lib/exported/video.h
new file mode 100644
index 0000000..5cb080c
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/lib/exported/video.h
@@ -0,0 +1,94 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_RENDERER_RASTERIZER_LIB_EXPORTED_VIDEO_H_
+#define COBALT_RENDERER_RASTERIZER_LIB_EXPORTED_VIDEO_H_
+
+#include "starboard/export.h"
+#include "starboard/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Functions to be called to set callbacks for the updates to video rendering
+// parameters. When the host application is notified that the graphics context
+// has been created, it should install the callbacks within the same call stack
+// of CbLibOnGraphicsContextCreated. Failing to do so will crash the application
+// on debug mode and no-op on production builds.
+
+typedef enum {
+  kCbLibVideoProjectionTypeNone = 0,  // When no offscreen video is playing.
+  kCbLibVideoProjectionTypeRectangular = 1,
+  kCbLibVideoProjectionTypeMesh = 2,
+} CbLibVideoProjectionType;
+
+// If projection_type is Rectangular or Mesh, this signals the start of off-
+// screen video playback. If it is None, this signals the end of playback.
+typedef void (*CbLibVideoUpdateProjectionTypeCallback)(
+    void* context, CbLibVideoProjectionType projection_type);
+
+SB_EXPORT_PLATFORM void CbLibVideoSetOnUpdateProjectionType(
+    void* context, CbLibVideoUpdateProjectionTypeCallback callback);
+
+typedef enum {
+  kCbLibVideoMeshDrawModeTriangles = 0,
+  kCbLibVideoMeshDrawModeTriangleStrip = 1,
+  kCbLibVideoMeshDrawModeTriangleFan = 2,
+} CbLibVideoMeshDrawMode;
+
+typedef struct {
+  int vertex_count;
+  CbLibVideoMeshDrawMode draw_mode;
+  // Memory owned and deleted by Cobalt. The array holds |count| tuples of
+  // interleaved position and texture coordinates in single precision floating
+  // point numbers, X, Y, Z, U, V.
+  const float* vertices;
+} CbLibVideoMesh;
+
+// Called when two new video meshes are detected; each should be applied to the
+// view of the corresponding eye on stereo rendering.
+typedef void (*CbLibVideoUpdateMeshesCallback)(void* context,
+                                               CbLibVideoMesh left_eye_mesh,
+                                               CbLibVideoMesh right_eye_mesh);
+
+SB_EXPORT_PLATFORM void CbLibVideoSetOnUpdateMeshes(
+    void* context, CbLibVideoUpdateMeshesCallback callback);
+
+typedef enum {
+  kCbLibVideoStereoModeMono = 0,
+  kCbLibVideoStereoModeStereoLeftRight = 1,
+  kCbLibVideoStereoModeStereoTopBottom = 2,
+  kCbLibVideoStereoModeStereoLeftRightUnadjustedCoordinates = 3,
+} CbLibVideoStereoMode;
+
+// Called when the layout of the different stereo views changes on the video
+// texture.
+typedef void (*CbLibVideoUpdateStereoModeCallback)(
+    void* context, CbLibVideoStereoMode stereo_mode);
+
+SB_EXPORT_PLATFORM void CbLibVideoSetOnUpdateStereoMode(
+    void* context, CbLibVideoUpdateStereoModeCallback callback);
+
+// Called to set the RGB video texture ID.
+typedef void (*CbLibVideoUpdateRgbTextureIdCallback)(void* context, int id);
+
+SB_EXPORT_PLATFORM void CbLibVideoSetOnUpdateRgbTextureId(
+    void* context, CbLibVideoUpdateRgbTextureIdCallback callback);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // COBALT_RENDERER_RASTERIZER_LIB_EXPORTED_VIDEO_H_
diff --git a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc
index d65ebd6..7b8415a 100644
--- a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc
@@ -14,16 +14,58 @@
 
 #include "cobalt/renderer/rasterizer/lib/external_rasterizer.h"
 
+#include <algorithm>
 #include <cmath>
 #include <vector>
 
+#include "base/bind.h"
+#include "base/callback.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/math/clamp.h"
+#include "cobalt/render_tree/image.h"
 #include "cobalt/renderer/backend/egl/graphics_context.h"
 #include "cobalt/renderer/backend/egl/render_target.h"
+#include "cobalt/renderer/rasterizer/common/find_node.h"
+#include "cobalt/renderer/rasterizer/lib/exported/video.h"
 #include "cobalt/renderer/rasterizer/lib/imported/graphics.h"
+#include "cobalt/renderer/rasterizer/skia/hardware_image.h"
+#include "cobalt/renderer/rasterizer/skia/hardware_mesh.h"
 #include "cobalt/renderer/rasterizer/skia/hardware_rasterizer.h"
 #include "starboard/shared/gles/gl_call.h"
 
+COMPILE_ASSERT(
+    cobalt::render_tree::kMono == kCbLibVideoStereoModeMono &&
+        cobalt::render_tree::kLeftRight ==
+            kCbLibVideoStereoModeStereoLeftRight &&
+        cobalt::render_tree::kTopBottom ==
+            kCbLibVideoStereoModeStereoTopBottom &&
+        cobalt::render_tree::kLeftRightUnadjustedTextureCoords ==
+            kCbLibVideoStereoModeStereoLeftRightUnadjustedCoordinates,
+    lib_video_and_map_to_mesh_stereo_mode_constants_mismatch);
+
+using cobalt::renderer::rasterizer::lib::ExternalRasterizer;
+
+namespace {
+
+const float kMaxRenderTargetSize = 15360.0f;
+
+ExternalRasterizer::Impl* g_external_rasterizer_impl_;
+
+void DefaultOnUpdateProjectionType(CbLibVideoProjectionType) {
+  LOG(DFATAL) << "CbLibVideoUpdateProjectionTypeCallback not set.";
+}
+void DefaultOnUpdateMeshes(CbLibVideoMesh, CbLibVideoMesh) {
+  LOG(DFATAL) << "CbLibVideoUpdateMeshesCallback not set.";
+}
+void DefaultOnUpdateStereoMode(CbLibVideoStereoMode) {
+  LOG(DFATAL) << "CbLibVideoStereoMode not set.";
+}
+void DefaultOnUpdateRgbTextureId(int) {
+  LOG(DFATAL) << "CbLibVideoUpdateRgbTextureIdCallback not set.";
+}
+
+}  // namespace
+
 namespace cobalt {
 namespace renderer {
 namespace rasterizer {
@@ -44,7 +86,34 @@
 
   void MakeCurrent() { hardware_rasterizer_.MakeCurrent(); }
 
+  // Equivalent to the callback types defined in exported/video.h but with the
+  // context bound.
+  typedef base::Callback<void(CbLibVideoMesh, CbLibVideoMesh)>
+      UpdateMeshesCallback;
+  typedef base::Callback<void(CbLibVideoStereoMode)> UpdateStereoModeCallback;
+  typedef base::Callback<void(int)> UpdateRgbTextureIdCallback;
+  typedef base::Callback<void(CbLibVideoProjectionType projection_type)>
+      UpdateProjectionTypeCallback;
+
+  void SetUpdateMeshesCallback(UpdateMeshesCallback update_meshes) {
+    update_meshes_ = update_meshes;
+  }
+  void SetUpdateStereoModeCallback(
+      UpdateStereoModeCallback update_stereo_mode) {
+    update_stereo_mode_ = update_stereo_mode;
+  }
+  void SetUpdateRgbTextureIdCallback(
+      UpdateRgbTextureIdCallback update_rgb_texture_id) {
+    update_rgb_texture_id_ = update_rgb_texture_id;
+  }
+  void SetUpdateProjectionTypeCallback(
+      UpdateProjectionTypeCallback update_projection_type) {
+    update_projection_type_ = update_projection_type;
+  }
+
  private:
+  void RenderOffscreenVideo(render_tree::FilterNode* map_to_mesh_filter_node);
+
   base::ThreadChecker thread_checker_;
 
   backend::GraphicsContextEGL* graphics_context_;
@@ -56,7 +125,22 @@
   // video.
   scoped_refptr<backend::RenderTarget> main_offscreen_render_target_;
   scoped_ptr<backend::TextureEGL> main_texture_;
-  // TODO: Add in a RenderTarget for the video texture.
+
+  // TODO: do not actually rasterize offscreen video, but just submit it to the
+  // host directly.
+  scoped_refptr<backend::RenderTarget> video_offscreen_render_target_;
+  scoped_ptr<backend::TextureEGL> video_texture_;
+
+  CbLibVideoProjectionType video_projection_type_;
+  scoped_refptr<skia::HardwareMesh> left_eye_video_mesh_;
+  scoped_refptr<skia::HardwareMesh> right_eye_video_mesh_;
+  render_tree::StereoMode video_stereo_mode_;
+  int video_texture_rgb_;
+
+  UpdateMeshesCallback update_meshes_;
+  UpdateStereoModeCallback update_stereo_mode_;
+  UpdateRgbTextureIdCallback update_rgb_texture_id_;
+  UpdateProjectionTypeCallback update_projection_type_;
 };
 
 ExternalRasterizer::Impl::Impl(backend::GraphicsContext* graphics_context,
@@ -71,7 +155,10 @@
       hardware_rasterizer_(
           graphics_context, skia_atlas_width, skia_atlas_height,
           skia_cache_size_in_bytes, scratch_surface_cache_size_in_bytes,
-          surface_cache_size_in_bytes, purge_skia_font_caches_on_destruction) {
+          surface_cache_size_in_bytes, purge_skia_font_caches_on_destruction),
+      video_projection_type_(kCbLibVideoProjectionTypeNone),
+      video_stereo_mode_(render_tree::StereoMode::kMono),
+      video_texture_rgb_(0) {
   options_.flags = skia::HardwareRasterizer::kSubmitFlags_Clear;
   graphics_context_->MakeCurrent();
 
@@ -84,10 +171,22 @@
       make_scoped_refptr(base::polymorphic_downcast<backend::RenderTargetEGL*>(
           main_offscreen_render_target_.get()))));
 
+  DCHECK(!g_external_rasterizer_impl_);
+  g_external_rasterizer_impl_ = this;
+
+  // Default parameter update callbacks.
+  update_projection_type_ = base::Bind(&DefaultOnUpdateProjectionType);
+  update_meshes_ = base::Bind(&DefaultOnUpdateMeshes);
+  update_stereo_mode_ = base::Bind(&DefaultOnUpdateStereoMode);
+  update_rgb_texture_id_ = base::Bind(&DefaultOnUpdateRgbTextureId);
+
   CbLibOnGraphicsContextCreated();
 }
 
-ExternalRasterizer::Impl::~Impl() { graphics_context_->MakeCurrent(); }
+ExternalRasterizer::Impl::~Impl() {
+  g_external_rasterizer_impl_ = NULL;
+  graphics_context_->MakeCurrent();
+}
 
 void ExternalRasterizer::Impl::Submit(
     const scoped_refptr<render_tree::Node>& render_tree,
@@ -95,6 +194,7 @@
   backend::RenderTargetEGL* render_target_egl =
       base::polymorphic_downcast<backend::RenderTargetEGL*>(
           render_target.get());
+
   // When the provided RenderTarget is not a window RenderTarget, then this
   // implies the rasterized RenderTree should not be shown directly to the user
   // and thus should not be rasterized into a texture and sent through to the
@@ -106,17 +206,96 @@
 
   graphics_context_->MakeCurrentWithSurface(render_target_egl);
 
+  // Attempt to find map to mesh filter node, then render video subtree
+  // offscreen.
+  auto map_to_mesh_search = common::FindNode<render_tree::FilterNode>(
+      render_tree, base::Bind(common::HasMapToMesh),
+      base::Bind(common::ReplaceWithEmptyCompositionNode));
+  if (map_to_mesh_search.found_node != NULL) {
+    base::optional<render_tree::MapToMeshFilter> filter =
+        map_to_mesh_search.found_node->data().map_to_mesh_filter;
+
+    CbLibVideoProjectionType new_projection_type;
+    if (!filter->left_eye_mesh()) {
+      // Video is rectangular. Mesh is provided externally (by host).
+      new_projection_type = kCbLibVideoProjectionTypeRectangular;
+    } else {
+      new_projection_type = kCbLibVideoProjectionTypeMesh;
+    }
+
+    if (video_projection_type_ != new_projection_type) {
+      video_projection_type_ = new_projection_type;
+      update_projection_type_.Run(video_projection_type_);
+    }
+
+    if (filter->stereo_mode() != video_stereo_mode_) {
+      video_stereo_mode_ = filter->stereo_mode();
+      update_stereo_mode_.Run(
+          static_cast<CbLibVideoStereoMode>(video_stereo_mode_));
+    }
+
+    if (video_projection_type_ == kCbLibVideoProjectionTypeMesh) {
+      // Use resolution to lookup custom mesh map.
+      const scoped_refptr<render_tree::Node>& video_render_tree =
+          map_to_mesh_search.found_node->data().source;
+      math::SizeF resolutionf = video_render_tree->GetBounds().size();
+      int width = static_cast<int>(resolutionf.width());
+      int height = static_cast<int>(resolutionf.height());
+
+      math::Size resolution(width, height);
+      scoped_refptr<skia::HardwareMesh> left_eye_video_mesh(
+          base::polymorphic_downcast<skia::HardwareMesh*>(
+              filter->left_eye_mesh(resolution).get()));
+      scoped_refptr<skia::HardwareMesh> right_eye_video_mesh(
+          base::polymorphic_downcast<skia::HardwareMesh*>(
+              filter->right_eye_mesh(resolution).get()));
+
+      DCHECK(left_eye_video_mesh);
+
+      if (left_eye_video_mesh_.get() != left_eye_video_mesh.get() ||
+          right_eye_video_mesh_.get() != right_eye_video_mesh.get()) {
+        left_eye_video_mesh_ = left_eye_video_mesh;
+        right_eye_video_mesh_ = right_eye_video_mesh;
+
+        CbLibVideoMesh left_mesh;
+        left_mesh.vertex_count =
+            static_cast<int>(left_eye_video_mesh_->GetVertexCount());
+        left_mesh.draw_mode = static_cast<CbLibVideoMeshDrawMode>(
+            left_eye_video_mesh_->GetDrawMode());
+        left_mesh.vertices = left_eye_video_mesh_->GetVertices();
+
+        if (right_eye_video_mesh_) {
+          CbLibVideoMesh right_mesh;
+          right_mesh.vertex_count =
+              static_cast<int>(right_eye_video_mesh_->GetVertexCount());
+          right_mesh.draw_mode = static_cast<CbLibVideoMeshDrawMode>(
+              right_eye_video_mesh_->GetDrawMode());
+          right_mesh.vertices = right_eye_video_mesh_->GetVertices();
+          update_meshes_.Run(left_mesh, right_mesh);
+        } else {
+          update_meshes_.Run(left_mesh, left_mesh);
+        }
+      }
+    }
+
+    // Render video to external texture(s) and pass those to the host.
+    RenderOffscreenVideo(map_to_mesh_search.found_node);
+  } else {
+    if (video_projection_type_ != kCbLibVideoProjectionTypeNone) {
+      video_projection_type_ = kCbLibVideoProjectionTypeNone;
+      update_projection_type_.Run(video_projection_type_);
+    }
+  }
+
   backend::RenderTargetEGL* main_texture_render_target_egl =
       base::polymorphic_downcast<backend::RenderTargetEGL*>(
           main_offscreen_render_target_.get());
-  hardware_rasterizer_.Submit(render_tree, main_offscreen_render_target_,
-                              options_);
+  hardware_rasterizer_.Submit(map_to_mesh_search.replaced_tree,
+                              main_offscreen_render_target_, options_);
 
-  const intptr_t texture_handle = main_texture_->GetPlatformHandle();
-  // TODO: Provide mesh data to clients for map-to-mesh playbacks and a
-  // separate video texture handle.
   // TODO: Allow clients to specify arbitrary subtrees to render into
   // different textures?
+  const intptr_t texture_handle = main_texture_->GetPlatformHandle();
   CbLibRenderFrame(texture_handle);
 
   graphics_context_->SwapBuffers(render_target_egl);
@@ -126,6 +305,74 @@
   return hardware_rasterizer_.GetResourceProvider();
 }
 
+void ExternalRasterizer::Impl::RenderOffscreenVideo(
+    render_tree::FilterNode* map_to_mesh_filter_node) {
+  DCHECK(map_to_mesh_filter_node);
+  if (!map_to_mesh_filter_node) {
+    return;
+  }
+
+  // Render the mesh-video into a texture to render into 3D space.
+  const scoped_refptr<render_tree::Node>& video_render_tree =
+      map_to_mesh_filter_node->data().source;
+  // Search the video_render_tree for the video frame ImageNode.
+  auto image_node =
+      common::FindNode<render_tree::ImageNode>(video_render_tree).found_node;
+
+  math::SizeF video_size_float = video_render_tree->GetBounds().size();
+  if (image_node.get() && image_node->data().source) {
+    video_size_float = image_node->data().source.get()->GetSize();
+  }
+
+  // Width and height of the video render target are based on the size of the
+  // video clamped to the valid range for creating an offscreen render target.
+  const float target_width = math::Clamp(std::floor(video_size_float.width()),
+                                         1.0f, kMaxRenderTargetSize);
+  const float target_height = math::Clamp(std::floor(video_size_float.height()),
+                                          1.0f, kMaxRenderTargetSize);
+  const math::Size video_size(target_width, target_height);
+
+  if (!video_offscreen_render_target_ ||
+      video_offscreen_render_target_->GetSize() != video_size) {
+    video_offscreen_render_target_ =
+        graphics_context_->CreateOffscreenRenderTarget(video_size);
+    DLOG(INFO) << "Created new video_offscreen_render_target_: "
+               << video_offscreen_render_target_->GetSize();
+
+    // Note: The TextureEGL this pointer references must first be destroyed by
+    // calling reset() before a new TextureEGL can be constructed.
+    video_texture_.reset();
+    video_texture_.reset(new backend::TextureEGL(
+        graphics_context_,
+        make_scoped_refptr(
+            base::polymorphic_downcast<backend::RenderTargetEGL*>(
+                video_offscreen_render_target_.get()))));
+  }
+
+  if (image_node.get()) {
+    // Create a new ImageNode around the raw image data which will
+    // automatically scale it to the right size.
+    backend::RenderTargetEGL* video_offscreen_render_target_egl =
+        base::polymorphic_downcast<backend::RenderTargetEGL*>(
+            video_offscreen_render_target_.get());
+
+    const scoped_refptr<render_tree::ImageNode> correctly_scaled_image_node(
+        new render_tree::ImageNode(image_node->data().source));
+
+    // TODO: Instead of submitting the image for rendering and producing an
+    // RGB texture, try to cast to HardwareMultiPlaneImage to use the YUV
+    // textures already produced by decode-to-texture.
+    hardware_rasterizer_.Submit(correctly_scaled_image_node,
+                                video_offscreen_render_target_, options_);
+
+    const intptr_t video_texture_handle = video_texture_->GetPlatformHandle();
+    if (video_texture_rgb_ != video_texture_handle) {
+      video_texture_rgb_ = video_texture_handle;
+      update_rgb_texture_id_.Run(video_texture_handle);
+    }
+  }
+}
+
 ExternalRasterizer::ExternalRasterizer(
     backend::GraphicsContext* graphics_context, int skia_atlas_width,
     int skia_atlas_height, int skia_cache_size_in_bytes,
@@ -158,3 +405,39 @@
 }  // namespace rasterizer
 }  // namespace renderer
 }  // namespace cobalt
+
+void CbLibVideoSetOnUpdateMeshes(void* context,
+                                 CbLibVideoUpdateMeshesCallback callback) {
+  if (g_external_rasterizer_impl_) {
+    g_external_rasterizer_impl_->SetUpdateMeshesCallback(
+        callback ? base::Bind(callback, context)
+                 : base::Bind(&DefaultOnUpdateMeshes));
+  }
+}
+
+void CbLibVideoSetOnUpdateStereoMode(
+    void* context, CbLibVideoUpdateStereoModeCallback callback) {
+  if (g_external_rasterizer_impl_) {
+    g_external_rasterizer_impl_->SetUpdateStereoModeCallback(
+        callback ? base::Bind(callback, context)
+                 : base::Bind(&DefaultOnUpdateStereoMode));
+  }
+}
+
+void CbLibVideoSetOnUpdateRgbTextureId(
+    void* context, CbLibVideoUpdateRgbTextureIdCallback callback) {
+  if (g_external_rasterizer_impl_) {
+    g_external_rasterizer_impl_->SetUpdateRgbTextureIdCallback(
+        callback ? base::Bind(callback, context)
+                 : base::Bind(&DefaultOnUpdateRgbTextureId));
+  }
+}
+
+void CbLibVideoSetOnUpdateProjectionType(
+    void* context, CbLibVideoUpdateProjectionTypeCallback callback) {
+  if (g_external_rasterizer_impl_) {
+    g_external_rasterizer_impl_->SetUpdateProjectionTypeCallback(
+        callback ? base::Bind(callback, context)
+                 : base::Bind(&DefaultOnUpdateProjectionType));
+  }
+}
diff --git a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h
index 39b7559..9ad1fd0 100644
--- a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h
+++ b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h
@@ -51,8 +51,9 @@
 
   void MakeCurrent() OVERRIDE;
 
- private:
   class Impl;
+
+ private:
   scoped_ptr<Impl> impl_;
 };
 
diff --git a/src/cobalt/renderer/rasterizer/lib/lib.gyp b/src/cobalt/renderer/rasterizer/lib/lib.gyp
index df38bb4..7a3914a 100644
--- a/src/cobalt/renderer/rasterizer/lib/lib.gyp
+++ b/src/cobalt/renderer/rasterizer/lib/lib.gyp
@@ -42,6 +42,11 @@
          '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
          '<(DEPTH)/starboard/egl_and_gles/egl_and_gles.gyp:egl_and_gles',
        ],
+      'conditions': [
+        ['enable_map_to_mesh == 1', {
+          'defines' : ['ENABLE_MAP_TO_MESH'],
+        }],
+      ],
     }
   ],
 }
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
index 4aa00fb..51a0f84 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
@@ -180,16 +180,27 @@
       HardwareBackendImage* backend) {
     TRACE_EVENT0("cobalt::renderer",
                  "HardwareBackendImage::InitializeFromRenderTree()");
+    backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
+        cobalt_context);
 
     scoped_refptr<backend::FramebufferRenderTargetEGL> render_target(
         new backend::FramebufferRenderTargetEGL(cobalt_context, size));
 
+    // The above call to FramebufferRenderTargetEGL() may have dirtied graphics
+    // state, so tell Skia to reset its context.
+    gr_context->resetContext(kRenderTarget_GrGLBackendState |
+                             kTextureBinding_GrGLBackendState);
     submit_offscreen_callback.Run(root, render_target);
 
     scoped_ptr<backend::TextureEGL> texture(
         new backend::TextureEGL(cobalt_context, render_target));
 
     InitializeFromTexture(texture.Pass(), gr_context, backend);
+
+    // Tell Skia that the graphics state is unknown because we issued custom
+    // GL commands above.
+    gr_context->resetContext(kRenderTarget_GrGLBackendState |
+                             kTextureBinding_GrGLBackendState);
   }
 
   // Initiate all texture initialization code here, which should be executed
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_mesh.h b/src/cobalt/renderer/rasterizer/skia/hardware_mesh.h
index 88f8963..ba251c7 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_mesh.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_mesh.h
@@ -41,6 +41,25 @@
 
   uint32 GetEstimatedSizeInBytes() const OVERRIDE;
 
+  // Float array of vertices, contiguously interleaved X, Y, Z, U, V coords.
+  const float* GetVertices() const {
+    DCHECK(vertices_ && vertices_->size());
+    return reinterpret_cast<const float*>(vertices_->data());
+  }
+
+  const size_t GetVertexCount() const {
+    DCHECK(vertices_);
+    return vertices_->size();
+  }
+
+  const render_tree::Mesh::DrawMode GetDrawMode() const {
+    return draw_mode_ == GL_TRIANGLE_FAN
+               ? render_tree::Mesh::DrawMode::kDrawModeTriangleFan
+               : draw_mode_ == GL_TRIANGLE_STRIP
+                     ? render_tree::Mesh::DrawMode::kDrawModeTriangleStrip
+                     : render_tree::Mesh::DrawMode::kDrawModeTriangles;
+  }
+
   // Obtains a vertex buffer object from this mesh. Called right before first
   // rendering it so that the graphics context has already been made current.
   const VertexBufferObject* GetVBO() const;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 9117c4c..603eb8e 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -696,8 +696,6 @@
   backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
       graphics_context_, render_target_egl);
 
-  gr_context_->resetContext();
-
   // Create a canvas from the render target.
   GrBackendRenderTargetDesc skia_desc =
       CobaltRenderTargetToSkiaBackendRenderTargetDesc(render_target.get());
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkAtomics_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkAtomics_cobalt.h
index de8c187..ae812ea 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkAtomics_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkAtomics_cobalt.h
@@ -18,14 +18,6 @@
 #include "base/atomicops.h"
 #include "cobalt/renderer/rasterizer/skia/skia/src/ports/atomic_type_conversions.h"
 
-#if defined(STARBOARD)
-/* Don't undefine MemoryBarrier */
-#elif defined(ARCH_CPU_64_BITS) || defined(__LB_XB360__)
-// windows.h #defines this (only on x64). This causes problems because the
-// public API also uses MemoryBarrier at the public name for this fence.
-#undef MemoryBarrier
-#endif
-
 static inline int32_t sk_atomic_inc(int32_t* addr) {
   return base::subtle::Barrier_AtomicIncrement(addr, 1) - 1;
 }
@@ -52,10 +44,10 @@
   // to, and after a value has been read.  E.g. we don't want previous
   // instructions to be moved after this instruction and we don't want after
   // instructions to be moved before this one.
-  base::subtle::MemoryBarrier();
+  SbAtomicMemoryBarrier();
   bool did_swap =
       base::subtle::NoBarrier_CompareAndSwap(addr, before, after) == before;
-  base::subtle::MemoryBarrier();
+  SbAtomicMemoryBarrier();
 
   return did_swap;
 }
@@ -63,13 +55,13 @@
 static inline void* sk_atomic_cas(void** addr, void* before, void* after) {
   typedef AtomicTraits<void*>::atomic_type AtomicType;
 
-  base::subtle::MemoryBarrier();
+  SbAtomicMemoryBarrier();
   void* previous_value =
       reinterpret_cast<void*>(base::subtle::NoBarrier_CompareAndSwap(
           reinterpret_cast<AtomicType*>(addr),
           reinterpret_cast<AtomicType>(before),
           reinterpret_cast<AtomicType>(after)));
-  base::subtle::MemoryBarrier();
+  SbAtomicMemoryBarrier();
 
   return previous_value;
 }
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkBarriers_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkBarriers_cobalt.h
index 00b666f..d19d655 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkBarriers_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkBarriers_cobalt.h
@@ -18,14 +18,6 @@
 #include "base/atomicops.h"
 #include "cobalt/renderer/rasterizer/skia/skia/src/ports/atomic_type_conversions.h"
 
-#if defined(STARBOARD)
-/* Don't undefine MemoryBarrier */
-#elif defined(ARCH_CPU_64_BITS) || defined(__LB_XB360__) || defined(__LB_XB1__)
-// windows.h #defines this (only on x64). This causes problems because the
-// public API also uses MemoryBarrier at the public name for this fence.
-#undef MemoryBarrier
-#endif
-
 template <typename T, bool compatible_with_atomics>
 class SkAcquireReleaseWithAtomicCompatibility {};
 
@@ -36,12 +28,12 @@
   // barriers.
   static T DoAcquireLoad(T* ptr) {
     bool ret = *ptr;
-    base::subtle::MemoryBarrier();
+    SbAtomicMemoryBarrier();
     return ret;
   }
 
   static void DoReleaseStore(T* ptr, T val) {
-    base::subtle::MemoryBarrier();
+    SbAtomicMemoryBarrier();
     *ptr = val;
   }
 };
diff --git a/src/cobalt/samples/simple_example.cc b/src/cobalt/samples/simple_example/simple_example.cc
similarity index 93%
rename from src/cobalt/samples/simple_example.cc
rename to src/cobalt/samples/simple_example/simple_example.cc
index fc47fb8..3edb4b8 100644
--- a/src/cobalt/samples/simple_example.cc
+++ b/src/cobalt/samples/simple_example/simple_example.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/samples/simple_example.h"
+#include "cobalt/samples/simple_example/simple_example.h"
 
 namespace cobalt {
 namespace samples {
diff --git a/src/cobalt/samples/samples.gyp b/src/cobalt/samples/simple_example/simple_example.gyp
similarity index 94%
rename from src/cobalt/samples/samples.gyp
rename to src/cobalt/samples/simple_example/simple_example.gyp
index 34d1aa9..f1660fb 100644
--- a/src/cobalt/samples/samples.gyp
+++ b/src/cobalt/samples/simple_example/simple_example.gyp
@@ -76,7 +76,7 @@
       'variables': {
         'executable_name': 'simple_example',
       },
-      'includes': [ '../../starboard/build/deploy.gypi' ],
+      'includes': [ '../../../starboard/build/deploy.gypi' ],
     },
 
     # This target will create a test for simple_example.
@@ -107,7 +107,7 @@
             ],
             'output_dir': 'cobalt/samples',
           },
-          'includes': [ '../build/copy_test_data.gypi' ],
+          'includes': [ '../../build/copy_test_data.gypi' ],
         },
       ],
     },
@@ -123,7 +123,7 @@
       'variables': {
         'executable_name': 'simple_example_test',
       },
-      'includes': [ '../../starboard/build/deploy.gypi' ],
+      'includes': [ '../../../starboard/build/deploy.gypi' ],
     },
   ],
 }
diff --git a/src/cobalt/samples/simple_example.h b/src/cobalt/samples/simple_example/simple_example.h
similarity index 86%
rename from src/cobalt/samples/simple_example.h
rename to src/cobalt/samples/simple_example/simple_example.h
index cd19184..ea1aea1 100644
--- a/src/cobalt/samples/simple_example.h
+++ b/src/cobalt/samples/simple_example/simple_example.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_SAMPLES_SIMPLE_EXAMPLE_H_
-#define COBALT_SAMPLES_SIMPLE_EXAMPLE_H_
+#ifndef COBALT_SAMPLES_SIMPLE_EXAMPLE_SIMPLE_EXAMPLE_H_
+#define COBALT_SAMPLES_SIMPLE_EXAMPLE_SIMPLE_EXAMPLE_H_
 
 namespace cobalt {
 namespace samples {
@@ -37,4 +37,4 @@
 }  // namespace samples
 }  // namespace cobalt
 
-#endif  // COBALT_SAMPLES_SIMPLE_EXAMPLE_H_
+#endif  // COBALT_SAMPLES_SIMPLE_EXAMPLE_SIMPLE_EXAMPLE_H_
diff --git a/src/cobalt/samples/simple_example_main.cc b/src/cobalt/samples/simple_example/simple_example_main.cc
similarity index 95%
rename from src/cobalt/samples/simple_example_main.cc
rename to src/cobalt/samples/simple_example/simple_example_main.cc
index 7405e68..5cdafee 100644
--- a/src/cobalt/samples/simple_example_main.cc
+++ b/src/cobalt/samples/simple_example/simple_example_main.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/samples/simple_example.h"
+#include "cobalt/samples/simple_example/simple_example.h"
 
 #include "base/logging.h"
 #include "base/stringprintf.h"
diff --git a/src/cobalt/samples/simple_example_test.cc b/src/cobalt/samples/simple_example/simple_example_test.cc
similarity index 98%
rename from src/cobalt/samples/simple_example_test.cc
rename to src/cobalt/samples/simple_example/simple_example_test.cc
index cb69e1d..377c436 100644
--- a/src/cobalt/samples/simple_example_test.cc
+++ b/src/cobalt/samples/simple_example/simple_example_test.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/samples/simple_example.h"
+#include "cobalt/samples/simple_example/simple_example.h"
 
 #include <string>
 #include <vector>
diff --git a/src/cobalt/samples/testdata/data.txt b/src/cobalt/samples/simple_example/testdata/data.txt
similarity index 100%
rename from src/cobalt/samples/testdata/data.txt
rename to src/cobalt/samples/simple_example/testdata/data.txt
diff --git a/src/cobalt/samples/testdata/test_cases/case1.txt b/src/cobalt/samples/simple_example/testdata/test_cases/case1.txt
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case1.txt
rename to src/cobalt/samples/simple_example/testdata/test_cases/case1.txt
diff --git a/src/cobalt/samples/testdata/test_cases/case1.txt.expected b/src/cobalt/samples/simple_example/testdata/test_cases/case1.txt.expected
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case1.txt.expected
rename to src/cobalt/samples/simple_example/testdata/test_cases/case1.txt.expected
diff --git a/src/cobalt/samples/testdata/test_cases/case2.txt b/src/cobalt/samples/simple_example/testdata/test_cases/case2.txt
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case2.txt
rename to src/cobalt/samples/simple_example/testdata/test_cases/case2.txt
diff --git a/src/cobalt/samples/testdata/test_cases/case2.txt.expected b/src/cobalt/samples/simple_example/testdata/test_cases/case2.txt.expected
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case2.txt.expected
rename to src/cobalt/samples/simple_example/testdata/test_cases/case2.txt.expected
diff --git a/src/cobalt/samples/testdata/test_cases/case3.txt b/src/cobalt/samples/simple_example/testdata/test_cases/case3.txt
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case3.txt
rename to src/cobalt/samples/simple_example/testdata/test_cases/case3.txt
diff --git a/src/cobalt/samples/testdata/test_cases/case3.txt.expected b/src/cobalt/samples/simple_example/testdata/test_cases/case3.txt.expected
similarity index 100%
rename from src/cobalt/samples/testdata/test_cases/case3.txt.expected
rename to src/cobalt/samples/simple_example/testdata/test_cases/case3.txt.expected
diff --git a/src/cobalt/script/mozjs-45/mozjs-45.gyp b/src/cobalt/script/mozjs-45/mozjs-45.gyp
index c26b64e..fb0b0da 100644
--- a/src/cobalt/script/mozjs-45/mozjs-45.gyp
+++ b/src/cobalt/script/mozjs-45/mozjs-45.gyp
@@ -32,6 +32,8 @@
         'referenced_object_map.cc',
         'util/algorithm_helpers.cc',
         'util/exception_helpers.cc',
+        'util/stack_trace_helpers.cc',
+        'util/stack_trace_helpers.h',
         'weak_heap_object.cc',
         'weak_heap_object.h',
         'weak_heap_object_manager.cc',
diff --git a/src/cobalt/script/mozjs-45/mozjs_callback_function.h b/src/cobalt/script/mozjs-45/mozjs_callback_function.h
index 9b9972f..a3a841a 100644
--- a/src/cobalt/script/mozjs-45/mozjs_callback_function.h
+++ b/src/cobalt/script/mozjs-45/mozjs_callback_function.h
@@ -26,6 +26,7 @@
 #include "cobalt/script/mozjs-45/conversion_helpers.h"
 #include "cobalt/script/mozjs-45/convert_callback_return_value.h"
 #include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
 #include "cobalt/script/mozjs-45/weak_heap_object.h"
 #include "nb/memory_scope.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
@@ -64,6 +65,7 @@
 
   CallbackResult<R> Run()
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -127,6 +129,7 @@
   CallbackResult<R> Run(
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -193,6 +196,7 @@
       typename base::internal::CallbackParamTraits<A1>::ForwardType a1,
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -261,6 +265,7 @@
       typename base::internal::CallbackParamTraits<A2>::ForwardType a2,
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -331,6 +336,7 @@
       typename base::internal::CallbackParamTraits<A3>::ForwardType a3,
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -404,6 +410,7 @@
       typename base::internal::CallbackParamTraits<A4>::ForwardType a4,
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -479,6 +486,7 @@
       typename base::internal::CallbackParamTraits<A5>::ForwardType a5,
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
@@ -556,6 +564,7 @@
       typename base::internal::CallbackParamTraits<A6>::ForwardType a6,
       typename base::internal::CallbackParamTraits<A7>::ForwardType a7)
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
diff --git a/src/cobalt/script/mozjs-45/mozjs_callback_function.h.pump b/src/cobalt/script/mozjs-45/mozjs_callback_function.h.pump
index 826d064..e8b0804 100644
--- a/src/cobalt/script/mozjs-45/mozjs_callback_function.h.pump
+++ b/src/cobalt/script/mozjs-45/mozjs_callback_function.h.pump
@@ -31,6 +31,7 @@
 #include "cobalt/script/mozjs-45/conversion_helpers.h"
 #include "cobalt/script/mozjs-45/convert_callback_return_value.h"
 #include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
 #include "cobalt/script/mozjs-45/weak_heap_object.h"
 #include "nb/memory_scope.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
@@ -83,6 +84,7 @@
 
       typename base::internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
       const OVERRIDE {
+    ENABLE_JS_STACK_TRACE_IN_SCOPE(context_);
     TRACK_MEMORY_SCOPE("Javascript");
     TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run");
     CallbackResult<R> callback_result;
diff --git a/src/cobalt/script/mozjs-45/mozjs_global_environment.cc b/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
index a075221..c850506 100644
--- a/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
+++ b/src/cobalt/script/mozjs-45/mozjs_global_environment.cc
@@ -28,6 +28,7 @@
 #include "cobalt/script/mozjs-45/proxy_handler.h"
 #include "cobalt/script/mozjs-45/referenced_object_map.h"
 #include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
 #include "nb/memory_scope.h"
 #include "third_party/mozjs-45/js/public/Initialization.h"
 #include "third_party/mozjs-45/js/src/jsfriendapi.h"
@@ -282,7 +283,9 @@
 
 std::vector<StackFrame> MozjsGlobalEnvironment::GetStackTrace(int max_frames) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return util::GetStackTrace(context_, max_frames);
+  nb::RewindableVector<StackFrame> stack_frames;
+  util::GetStackTrace(context_, max_frames, &stack_frames);
+  return stack_frames.InternalData();
 }
 
 void MozjsGlobalEnvironment::PreventGarbageCollection(
diff --git a/src/cobalt/script/mozjs-45/util/exception_helpers.cc b/src/cobalt/script/mozjs-45/util/exception_helpers.cc
index 21dfb8e..a9523f5 100644
--- a/src/cobalt/script/mozjs-45/util/exception_helpers.cc
+++ b/src/cobalt/script/mozjs-45/util/exception_helpers.cc
@@ -20,7 +20,12 @@
 #include "cobalt/script/mozjs-45/conversion_helpers.h"
 #include "cobalt/script/mozjs-45/mozjs_exception_state.h"
 #include "third_party/mozjs-45/js/public/UbiNode.h"
+#include "third_party/mozjs-45/js/src/builtin/ModuleObject.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
+#include "third_party/mozjs-45/js/src/jscntxt.h"
+#include "third_party/mozjs-45/js/src/jscntxtinlines.h"
+#include "third_party/mozjs-45/js/src/jsfriendapi.h"
+#include "third_party/mozjs-45/js/src/jsprf.h"
 
 namespace cobalt {
 namespace script {
@@ -45,15 +50,18 @@
   return exception_string;
 }
 
-std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames) {
+void GetStackTrace(JSContext* context, size_t max_frames,
+                   nb::RewindableVector<StackFrame>* output) {
+  DCHECK(output);
+  output->rewindAll();
+
   JS::RootedObject stack(context);
-  std::vector<StackFrame> stack_frames;
   if (!js::GetContextCompartment(context)) {
     LOG(WARNING) << "Tried to get stack trace without access to compartment.";
-    return stack_frames;
+    return;
   }
   if (!JS::CaptureCurrentStack(context, &stack, max_frames)) {
-    return stack_frames;
+    return;
   }
 
   while (stack != NULL) {
@@ -87,11 +95,44 @@
                   &sf.source_url);
     }
 
-    stack_frames.push_back(sf);
+    output->push_back(sf);
     JS::GetSavedFrameParent(context, stack, &stack);
   }
+}
 
-  return stack_frames;
+void GetStackTraceUsingInternalApi(JSContext* context, size_t max_frames,
+                                   nb::RewindableVector<StackFrame>* output) {
+  DCHECK(output);
+  output->rewindAll();
+
+  int frames_captured = 0;
+  for (js::AllFramesIter it(context);
+       !it.done() && (max_frames == 0 || frames_captured < max_frames);
+       ++it, ++frames_captured) {
+    const char* filename = JS_GetScriptFilename(it.script());
+    unsigned column = 0;
+    unsigned line = js::PCToLineNumber(it.script(), it.pc(), &column);
+
+    StackFrame sf;
+    sf.source_url = filename;
+    sf.line_number = line;
+    sf.column_number = column;
+    if (it.isFunctionFrame()) {
+      JSFunction* function = it.callee(context);
+      if (function->displayAtom()) {
+        auto* atom = function->displayAtom();
+        JS::AutoCheckCannotGC nogc;
+        auto* chars = atom->latin1Chars(nogc);
+        sf.function_name = std::string(chars, chars + atom->length());
+      } else {
+        sf.function_name = "(anonymous function)";
+      }
+    } else {
+      sf.function_name = "global code";
+    }
+
+    output->push_back(sf);
+  }
 }
 
 }  // namespace util
diff --git a/src/cobalt/script/mozjs-45/util/exception_helpers.h b/src/cobalt/script/mozjs-45/util/exception_helpers.h
index b950d67..da4aa2d 100644
--- a/src/cobalt/script/mozjs-45/util/exception_helpers.h
+++ b/src/cobalt/script/mozjs-45/util/exception_helpers.h
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "cobalt/script/stack_frame.h"
+#include "nb/rewindable_vector.h"
 #include "third_party/mozjs-45/js/public/UbiNode.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
 
@@ -29,7 +30,20 @@
 
 std::string GetExceptionString(JSContext* context, JS::HandleValue exception);
 
-std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames);
+// Retrieves the current stack frame. Values are stored in the output vector.
+// RewindableVector<> will be unconditionally rewound and after this call will
+// contain the number of frames retrieved. The output size will be less than
+// or equal to max_frames.
+void GetStackTrace(JSContext* context, size_t max_frames,
+                   nb::RewindableVector<StackFrame>* output);
+
+// The same as |GetStackTrace|, only using internal SpiderMonkey APIs instead.
+// This may be required if attempting to obtain a call stack while SpiderMonkey
+// is deep inside its internals, such as during an allocation.  If you decide to
+// call this, make sure you know what you're doing.
+void GetStackTraceUsingInternalApi(JSContext* context, size_t max_frames,
+                                   nb::RewindableVector<StackFrame>* output);
+
 }  // namespace util
 }  // namespace mozjs
 }  // namespace script
diff --git a/src/cobalt/script/mozjs-45/util/stack_trace_helpers.cc b/src/cobalt/script/mozjs-45/util/stack_trace_helpers.cc
new file mode 100644
index 0000000..79ddf70
--- /dev/null
+++ b/src/cobalt/script/mozjs-45/util/stack_trace_helpers.cc
@@ -0,0 +1,141 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/script/mozjs-45/util/stack_trace_helpers.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "cobalt/script/mozjs-45/util/exception_helpers.h"
+#include "cobalt/script/stack_frame.h"
+#include "nb/thread_local_object.h"
+#include "starboard/memory.h"
+#include "starboard/once.h"
+#include "starboard/string.h"
+#include "starboard/types.h"
+#include "third_party/mozjs-45/js/src/jsapi.h"
+
+namespace cobalt {
+namespace script {
+namespace mozjs {
+namespace util {
+
+namespace {
+
+typedef nb::ThreadLocalObject<MozjsStackTraceGenerator>
+    ThreadLocalJsStackTracer;
+
+SB_ONCE_INITIALIZE_FUNCTION(ThreadLocalJsStackTracer,
+                            s_thread_local_js_stack_tracer_singelton);
+
+void ToStringAppend(const StackFrame& sf, std::string* out) {
+  base::SStringPrintf(out, "%s(%d,%d):%s", sf.source_url.c_str(),
+                      sf.line_number, sf.column_number,
+                      sf.function_name.c_str());
+}
+
+}  // namespace
+
+void SetThreadLocalJSContext(JSContext* context) {
+  static_cast<MozjsStackTraceGenerator*>(
+      ::cobalt::script::util::GetThreadLocalStackTraceGenerator())
+      ->set_context(context);
+}
+
+JSContext* GetThreadLocalJSContext() {
+  return static_cast<MozjsStackTraceGenerator*>(
+             ::cobalt::script::util::GetThreadLocalStackTraceGenerator())
+      ->context();
+}
+
+bool MozjsStackTraceGenerator::GenerateStackTrace(
+    int depth, nb::RewindableVector<StackFrame>* out) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  out->rewindAll();
+  if (!Valid()) {
+    return false;
+  }
+  GetStackTraceUsingInternalApi(context_, depth, out);
+  return !out->empty();
+}
+
+bool MozjsStackTraceGenerator::GenerateStackTraceLines(
+    int depth, nb::RewindableVector<std::string>* out) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  out->rewindAll();
+  nb::RewindableVector<StackFrame>& stack_frames = scratch_data_.stack_frames_;
+  if (!GenerateStackTrace(depth, &stack_frames)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < stack_frames.size(); ++i) {
+    std::string& current_string = out->grow(1);
+    current_string.assign("");  // Should not deallocate memory.
+    StackFrame& sf = stack_frames[i];
+    ToStringAppend(sf, &current_string);
+  }
+  return true;
+}
+
+bool MozjsStackTraceGenerator::GenerateStackTraceString(int depth,
+                                                        std::string* out) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  out->assign("");  // Should not deallocate memory.
+
+  nb::RewindableVector<StackFrame>& stack_frames = scratch_data_.stack_frames_;
+  if (!GenerateStackTrace(depth, &stack_frames)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < stack_frames.size(); ++i) {
+    cobalt::script::StackFrame& sf = stack_frames[i];
+    ToStringAppend(sf, out);
+    if (i < stack_frames.size() - 1) {
+      base::SStringPrintf(out, "\n");
+    }
+  }
+  return true;
+}
+
+bool MozjsStackTraceGenerator::GenerateStackTraceString(int depth, char* buff,
+                                                        size_t buff_size) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  SbMemorySet(buff, 0, buff_size);
+  std::string& scratch_symbol = scratch_data_.symbol_;
+
+  if (!GenerateStackTraceString(depth, &scratch_symbol)) {
+    return false;
+  }
+
+  SbStringCopy(buff, scratch_symbol.c_str(), buff_size);
+  return true;
+}
+
+}  // namespace util
+}  // namespace mozjs
+
+namespace util {
+
+// Declared in abstract cobalt script.
+StackTraceGenerator* GetThreadLocalStackTraceGenerator() {
+  return mozjs::util::s_thread_local_js_stack_tracer_singelton()->GetOrCreate();
+}
+
+}  // namespace util
+
+}  // namespace script
+}  // namespace cobalt
diff --git a/src/cobalt/script/mozjs-45/util/stack_trace_helpers.h b/src/cobalt/script/mozjs-45/util/stack_trace_helpers.h
new file mode 100644
index 0000000..6168902
--- /dev/null
+++ b/src/cobalt/script/mozjs-45/util/stack_trace_helpers.h
@@ -0,0 +1,123 @@
+// 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_SCRIPT_MOZJS_45_UTIL_STACK_TRACE_HELPERS_H_
+#define COBALT_SCRIPT_MOZJS_45_UTIL_STACK_TRACE_HELPERS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/threading/thread_checker.h"
+#include "cobalt/script/stack_frame.h"
+#include "cobalt/script/util/stack_trace_helpers.h"
+#include "nb/rewindable_vector.h"
+#include "third_party/mozjs-45/js/src/jsapi.h"
+
+#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
+#define ENABLE_JS_STACK_TRACE_IN_SCOPE(context)                            \
+  ::cobalt::script::mozjs::util::StackTraceScope stack_trace_scope_object( \
+      context)
+#else
+#define ENABLE_JS_STACK_TRACE_IN_SCOPE(context)
+#endif
+
+namespace cobalt {
+namespace script {
+namespace mozjs {
+namespace util {
+
+// Usage:
+//   void Function(JSContext* js_ctx, ...) {
+//     ENABLE_JS_STACK_TRACE_IN_SCOPE(js_ctx);
+//     ...
+//     InvokeOtherFunctions();
+//   }
+//
+//  void InvokeOtherFunctions() {
+//    ...
+//    std::string stack_trace_str;
+//    if (GetThreadLocalStackTraceGenerator()->GenerateStackTraceString(
+//          2, &stack_trace_str)) {
+//      // Prints the stack trace from javascript.
+//      SbLogRaw(stack_trace_str.c_str());
+//    }
+//  }
+class MozjsStackTraceGenerator
+    : public ::cobalt::script::util::StackTraceGenerator {
+ public:
+  MozjsStackTraceGenerator() : context_(NULL) {}
+
+  // Returns |true| if the current StackTraceGenerator can generate information
+  // about the stack.
+  bool Valid() OVERRIDE { return context_ != NULL; }
+
+  // Generates stack traces in the raw form. Returns true if any stack
+  // frames were generated. False otherwise. Output vector will be
+  // unconditionally rewound to being empty.
+  bool GenerateStackTrace(int depth,
+                          nb::RewindableVector<StackFrame>* out) OVERRIDE;
+
+  // Returns true if any stack traces were written. The output vector will be
+  // re-wound to being empty.
+  // The first position is the most immediate stack frame.
+  bool GenerateStackTraceLines(int depth,
+                               nb::RewindableVector<std::string>* out) OVERRIDE;
+
+  // Prints stack trace. Returns true on success.
+  bool GenerateStackTraceString(int depth, std::string* out) OVERRIDE;
+
+  bool GenerateStackTraceString(int depth, char* buff,
+                                size_t buff_size) OVERRIDE;
+
+  // Gets the internal data structure used to generate stack traces.
+  JSContext* context() { return context_; }
+
+  // Internal only, do not set.
+  void set_context(JSContext* context) { context_ = context; }
+
+ private:
+  JSContext* context_;
+
+  // Recycles memory so that stack tracing is efficient.
+  struct Scratch {
+    nb::RewindableVector<StackFrame> stack_frames_;
+    nb::RewindableVector<std::string> strings_stack_frames_;
+    std::string symbol_;
+  };
+  Scratch scratch_data_;
+  // Checks that each instance can only be used within the same thread.
+  base::ThreadChecker thread_checker_;
+};
+
+// This should only be accessed by a scoped object.
+void SetThreadLocalJSContext(JSContext* context);
+JSContext* GetThreadLocalJSContext();
+
+// Useful for updating the most recent stack trace.
+struct StackTraceScope {
+  explicit StackTraceScope(JSContext* js)
+      : prev_context_(GetThreadLocalJSContext()) {
+    SetThreadLocalJSContext(js);
+  }
+  ~StackTraceScope() { SetThreadLocalJSContext(prev_context_); }
+  JSContext* prev_context_;
+};
+
+}  // namespace util
+}  // namespace mozjs
+}  // namespace script
+}  // namespace cobalt
+
+#endif  // COBALT_SCRIPT_MOZJS_45_UTIL_STACK_TRACE_HELPERS_H_
diff --git a/src/cobalt/script/mozjs-45/weak_heap_object_manager.cc b/src/cobalt/script/mozjs-45/weak_heap_object_manager.cc
index ebeb413..107d23b 100644
--- a/src/cobalt/script/mozjs-45/weak_heap_object_manager.cc
+++ b/src/cobalt/script/mozjs-45/weak_heap_object_manager.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "cobalt/script/mozjs-45/weak_heap_object.h"
 #include "nb/memory_scope.h"
+#include "starboard/system.h"
 #include "third_party/mozjs-45/js/src/jsapi.h"
 
 namespace cobalt {
@@ -39,8 +40,16 @@
 }
 
 WeakHeapObjectManager::~WeakHeapObjectManager() {
-  // If this is not empty, that means that some WeakHeapObject may outlive this
-  // class.
+  // If this is not empty, that means that some WeakHeapObject may outlive
+  // this class.
+#if COBALT_TRACK_WEAK_HEAP_OBJECT_ADDED_FROM_LOCATION
+  for (const auto& object : weak_objects_) {
+    LOG(INFO) << static_cast<void*>(object);
+    for (const auto& location : added_from_[object]) {
+      LOG(INFO) << "  " << location;
+    }
+  }
+#endif
   DCHECK(weak_objects_.empty());
 }
 
@@ -49,6 +58,13 @@
   std::pair<WeakHeapObjects::iterator, bool> pib =
       weak_objects_.insert(weak_object);
   DCHECK(pib.second) << "WeakHeapObject was already being tracked.";
+
+#if COBALT_TRACK_WEAK_HEAP_OBJECT_ADDED_FROM_LOCATION
+  const int kRequestedStackSize = 100;
+  void* stack[kRequestedStackSize];
+  int stack_size = SbSystemGetStack(stack, kRequestedStackSize);
+  added_from_[weak_object].assign(stack, stack + stack_size);
+#endif
 }
 
 void WeakHeapObjectManager::StopTracking(WeakHeapObject* weak_object) {
diff --git a/src/cobalt/script/mozjs-45/weak_heap_object_manager.h b/src/cobalt/script/mozjs-45/weak_heap_object_manager.h
index a452f3d..4b8558c 100644
--- a/src/cobalt/script/mozjs-45/weak_heap_object_manager.h
+++ b/src/cobalt/script/mozjs-45/weak_heap_object_manager.h
@@ -14,8 +14,22 @@
 #ifndef COBALT_SCRIPT_MOZJS_45_WEAK_HEAP_OBJECT_MANAGER_H_
 #define COBALT_SCRIPT_MOZJS_45_WEAK_HEAP_OBJECT_MANAGER_H_
 
+#include <vector>
+
 #include "base/hash_tables.h"
 
+// Can be locally set to 1 to track the call stack where a weak heap object
+// was tracked from, and then dump the call stacks of any remaining objects in
+// |WeakHeapObjectManager|'s destructor before hitting the DCHECK that
+// verifies that no objects are remaining.
+//
+// Example workflow for processing the output of this on linux:
+//   # Copy a dumped callstack to the clipboard.
+//   $ xclip -selection c -o |
+//       grep -o '0x[[:xdigit:]]*' |
+//       addr2line -e out/linux-x64x11_debug/cobalt
+#define COBALT_TRACK_WEAK_HEAP_OBJECT_ADDED_FROM_LOCATION 0
+
 namespace cobalt {
 namespace script {
 namespace mozjs {
@@ -44,6 +58,10 @@
   typedef base::hash_set<WeakHeapObject*> WeakHeapObjects;
   WeakHeapObjects weak_objects_;
 
+#if COBALT_TRACK_WEAK_HEAP_OBJECT_ADDED_FROM_LOCATION
+  base::hash_map<WeakHeapObject*, std::vector<void*>> added_from_;
+#endif
+
   friend class WeakHeapObject;
 };
 
diff --git a/src/cobalt/script/mozjs/util/exception_helpers.cc b/src/cobalt/script/mozjs/util/exception_helpers.cc
index fa50573..7ffdbd4 100644
--- a/src/cobalt/script/mozjs/util/exception_helpers.cc
+++ b/src/cobalt/script/mozjs/util/exception_helpers.cc
@@ -49,6 +49,7 @@
 
 void GetStackTrace(JSContext* context, size_t max_frames,
                    nb::RewindableVector<StackFrame>* output) {
+  DCHECK(output);
   output->rewindAll();
   JSAutoRequest auto_request(context);
   JS::StackDescription* stack_description =
diff --git a/src/cobalt/script/mozjs/util/stack_trace_helpers.cc b/src/cobalt/script/mozjs/util/stack_trace_helpers.cc
index ed83032..1c1fcf1 100644
--- a/src/cobalt/script/mozjs/util/stack_trace_helpers.cc
+++ b/src/cobalt/script/mozjs/util/stack_trace_helpers.cc
@@ -12,9 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef NB_SCRIPT_STACKTRACE_H_
-#define NB_SCRIPT_STACKTRACE_H_
-
 #include "cobalt/script/mozjs/util/stack_trace_helpers.h"
 
 #include <algorithm>
@@ -38,7 +35,8 @@
 namespace util {
 namespace {
 
-typedef nb::ThreadLocalObject<StackTraceGenerator> ThreadLocalJsStackTracer;
+typedef nb::ThreadLocalObject<MozjsStackTraceGenerator>
+    ThreadLocalJsStackTracer;
 
 SB_ONCE_INITIALIZE_FUNCTION(ThreadLocalJsStackTracer,
                             s_thread_local_js_stack_tracer_singelton);
@@ -52,25 +50,25 @@
 }  // namespace.
 
 void SetThreadLocalJSContext(JSContext* context) {
-  GetThreadLocalStackTraceGenerator()->set_js_context(context);
+  static_cast<MozjsStackTraceGenerator*>(
+      ::cobalt::script::util::GetThreadLocalStackTraceGenerator())
+      ->set_js_context(context);
 }
 
 JSContext* GetThreadLocalJSContext() {
-  return GetThreadLocalStackTraceGenerator()->js_context();
-}
-
-StackTraceGenerator* GetThreadLocalStackTraceGenerator() {
-  return s_thread_local_js_stack_tracer_singelton()->GetOrCreate();
+  return static_cast<MozjsStackTraceGenerator*>(
+             ::cobalt::script::util::GetThreadLocalStackTraceGenerator())
+      ->js_context();
 }
 
 //////////////////////////////////// IMPL /////////////////////////////////////
 
-StackTraceGenerator::StackTraceGenerator() : js_context_(NULL) {}
-StackTraceGenerator::~StackTraceGenerator() {}
+MozjsStackTraceGenerator::MozjsStackTraceGenerator() : js_context_(NULL) {}
+MozjsStackTraceGenerator::~MozjsStackTraceGenerator() {}
 
-bool StackTraceGenerator::Valid() { return js_context_ != NULL; }
+bool MozjsStackTraceGenerator::Valid() { return js_context_ != NULL; }
 
-bool StackTraceGenerator::GenerateStackTrace(
+bool MozjsStackTraceGenerator::GenerateStackTrace(
     int depth, nb::RewindableVector<StackFrame>* out) {
   DCHECK(thread_checker_.CalledOnValidThread());
   out->rewindAll();
@@ -81,7 +79,7 @@
   return !out->empty();
 }
 
-bool StackTraceGenerator::GenerateStackTraceLines(
+bool MozjsStackTraceGenerator::GenerateStackTraceLines(
     int depth, nb::RewindableVector<std::string>* out) {
   DCHECK(thread_checker_.CalledOnValidThread());
   out->rewindAll();
@@ -99,8 +97,8 @@
   return true;
 }
 
-bool StackTraceGenerator::GenerateStackTraceString(int depth,
-                                                   std::string* out) {
+bool MozjsStackTraceGenerator::GenerateStackTraceString(int depth,
+                                                        std::string* out) {
   DCHECK(thread_checker_.CalledOnValidThread());
   out->assign("");  // Should not deallocate memory.
 
@@ -119,8 +117,8 @@
   return true;
 }
 
-bool StackTraceGenerator::GenerateStackTraceString(int depth, char* buff,
-                                                   size_t buff_size) {
+bool MozjsStackTraceGenerator::GenerateStackTraceString(int depth, char* buff,
+                                                        size_t buff_size) {
   DCHECK(thread_checker_.CalledOnValidThread());
   SbMemorySet(buff, 0, buff_size);
   std::string& scratch_symbol = scratch_data_.symbol_;
@@ -133,15 +131,22 @@
   return true;
 }
 
-JSContext* StackTraceGenerator::js_context() { return js_context_; }
+JSContext* MozjsStackTraceGenerator::js_context() { return js_context_; }
 
-void StackTraceGenerator::set_js_context(JSContext* js_ctx) {
+void MozjsStackTraceGenerator::set_js_context(JSContext* js_ctx) {
   js_context_ = js_ctx;
 }
 
 }  // namespace util
 }  // namespace mozjs
+
+namespace util {
+
+StackTraceGenerator* GetThreadLocalStackTraceGenerator() {
+  return mozjs::util::s_thread_local_js_stack_tracer_singelton()->GetOrCreate();
+}
+
+}  // namespace util
+
 }  // namespace script
 }  // namespace cobalt
-
-#endif  // NB_SCRIPT_STACKTRACE_H_
diff --git a/src/cobalt/script/mozjs/util/stack_trace_helpers.h b/src/cobalt/script/mozjs/util/stack_trace_helpers.h
index c54a5ee..b7ccbe8 100644
--- a/src/cobalt/script/mozjs/util/stack_trace_helpers.h
+++ b/src/cobalt/script/mozjs/util/stack_trace_helpers.h
@@ -20,6 +20,7 @@
 
 #include "base/threading/thread_checker.h"
 #include "cobalt/script/stack_frame.h"
+#include "cobalt/script/util/stack_trace_helpers.h"
 #include "nb/rewindable_vector.h"
 
 struct JSContext;
@@ -47,19 +48,20 @@
 //  void InvokeOtherFunctions() {
 //    ...
 //    std::string stack_trace_str;
-//    if (GetThreadLocalStackTraceGenerator()->GenerateStackTraceString(
+//    if (GetThreadLocalMozjsStackTraceGenerator()->GenerateStackTraceString(
 //          2, &stack_trace_str)) {
 //      // Prints the stack trace from javascript.
 //      SbLogRaw(stack_trace_str.c_str());
 //    }
 //  }
-class StackTraceGenerator {
+class MozjsStackTraceGenerator
+    : public ::cobalt::script::util::StackTraceGenerator {
  public:
-  StackTraceGenerator();
-  virtual ~StackTraceGenerator();
+  MozjsStackTraceGenerator();
+  virtual ~MozjsStackTraceGenerator();
 
-  // Returns |true| if the current StackTraceGenerator can generate information
-  // about the stack.
+  // Returns |true| if the current MozjsStackTraceGenerator can generate
+  // information about the stack.
   bool Valid();
 
   // Generates stack traces in the raw form. Returns true if any stack
@@ -98,9 +100,6 @@
   base::ThreadChecker thread_checker_;
 };
 
-// Get's the thread local StackTraceGenerator.
-StackTraceGenerator* GetThreadLocalStackTraceGenerator();
-
 // This should only be accessed by a scoped object.
 void SetThreadLocalJSContext(JSContext* context);
 JSContext* GetThreadLocalJSContext();
diff --git a/src/cobalt/script/script.gyp b/src/cobalt/script/script.gyp
index 46cc245..a7e9192 100644
--- a/src/cobalt/script/script.gyp
+++ b/src/cobalt/script/script.gyp
@@ -34,6 +34,7 @@
         'source_provider.h',
         'stack_frame.cc',
         'stack_frame.h',
+        'util/stack_trace_helpers.h',
         'wrappable.h',
       ],
       'dependencies': [
diff --git a/src/cobalt/script/util/stack_trace_helpers.h b/src/cobalt/script/util/stack_trace_helpers.h
new file mode 100644
index 0000000..0cdc636
--- /dev/null
+++ b/src/cobalt/script/util/stack_trace_helpers.h
@@ -0,0 +1,62 @@
+// 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_SCRIPT_UTIL_STACK_TRACE_HELPERS_H_
+#define COBALT_SCRIPT_UTIL_STACK_TRACE_HELPERS_H_
+
+#include <string>
+
+#include "cobalt/script/stack_frame.h"
+#include "nb/rewindable_vector.h"
+
+namespace cobalt {
+namespace script {
+namespace util {
+
+class StackTraceGenerator {
+ public:
+  virtual ~StackTraceGenerator() {}
+
+  // Returns |true| if the current StackTraceGenerator can generate information
+  // about the stack.
+  virtual bool Valid() = 0;
+
+  // Generates stack traces in the raw form. Returns true if any stack
+  // frames were generated. False otherwise. Output vector will be
+  // unconditionally rewound to being empty.
+  virtual bool GenerateStackTrace(int depth,
+                                  nb::RewindableVector<StackFrame>* out) = 0;
+
+  // Returns true if any stack traces were written. The output vector will be
+  // re-wound to being empty.
+  // The first position is the most immediate stack frame.
+  virtual bool GenerateStackTraceLines(
+      int depth, nb::RewindableVector<std::string>* out) = 0;
+
+  // Prints stack trace. Returns true on success.
+  virtual bool GenerateStackTraceString(int depth, std::string* out) = 0;
+
+  virtual bool GenerateStackTraceString(int depth, char* buff,
+                                        size_t buff_size) = 0;
+};
+
+// Get's the thread local StackTraceGenerator.
+// Defined in engine specific implementation.
+StackTraceGenerator* GetThreadLocalStackTraceGenerator();
+
+}  // namespace util
+}  // namespace script
+}  // namespace cobalt
+
+#endif  // COBALT_SCRIPT_UTIL_STACK_TRACE_HELPERS_H_
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index 399e107..c7c1c6d 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -100,9 +100,21 @@
   // uses.
   int key_code = static_cast<int>(data.key);
 #if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  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;
+  }
+
   scoped_ptr<InputEvent> input_event(
-      new InputEvent(type, data.device_id, key_code, data.key_modifiers,
-                     is_repeat, math::PointF(data.position.x, data.position.y),
+      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.size.x, data.size.y),
                      math::PointF(data.tilt.x, data.tilt.y)));
diff --git a/src/cobalt/xhr/xhr_modify_headers.h b/src/cobalt/xhr/xhr_modify_headers.h
index a1a1afc..c9088a7 100644
--- a/src/cobalt/xhr/xhr_modify_headers.h
+++ b/src/cobalt/xhr/xhr_modify_headers.h
@@ -15,12 +15,14 @@
 #ifndef COBALT_XHR_XHR_MODIFY_HEADERS_H_
 #define COBALT_XHR_XHR_MODIFY_HEADERS_H_
 
-#include <net/http/http_request_headers.h>
+#include "googleurl/src/gurl.h"
+#include "net/http/http_request_headers.h"
 
 namespace cobalt {
 namespace xhr {
 
-void CobaltXhrModifyHeader(net::HttpRequestHeaders* headers);
+void CobaltXhrModifyHeader(const GURL& request_url,
+                           net::HttpRequestHeaders* headers);
 
 }  // namespace xhr
 }  // namespace cobalt
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index 7af4b99..c1774a4 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -325,7 +325,7 @@
   upload_complete_ = false;
 
 #if defined(COBALT_ENABLE_XHR_HEADER_FILTERING)
-  CobaltXhrModifyHeader(&request_headers_);
+  CobaltXhrModifyHeader(request_url_, &request_headers_);
 #endif
 
   std::string request_body_text;
diff --git a/src/net/url_request/url_request_job_manager.cc b/src/net/url_request/url_request_job_manager.cc
index 1ab6814..ed0f5db 100644
--- a/src/net/url_request/url_request_job_manager.cc
+++ b/src/net/url_request/url_request_job_manager.cc
@@ -39,9 +39,7 @@
 static const SchemeToFactory kBuiltinFactories[] = {
   {"https", URLRequestHttpJob::Factory},
   {"data", URLRequestDataJob::Factory},
-#if !defined(COBALT_FORCE_HTTPS)
   { "http", URLRequestHttpJob::Factory },
-#endif
 #if defined(COBALT_ENABLE_FILE_SCHEME)
   { "file", URLRequestFileJob::Factory },
 #endif
diff --git a/src/starboard/README.md b/src/starboard/README.md
index 16a8cda..1487a1d 100644
--- a/src/starboard/README.md
+++ b/src/starboard/README.md
@@ -5,12 +5,6 @@
 that it does not.
 
 
-## Current State
-
-Desktop Linux Cobalt is fully implemented on top of Starboard, and version 1 of
-the Starboard API is mostly locked down.
-
-
 ## Interesting Source Locations
 
 All source locations are specified relative to `src/starboard/` (this directory).
@@ -19,11 +13,9 @@
     the public headers that Starboard defines.
   * `examples/` - Example code demonstrating various aspects of Starboard API
     usage.
-  * `linux/` - The home of the Linux Starboard implementation. This contains a
+  * `stub/` - The home of the Stub Starboard implementation. This contains a
     `starboard_platform.gyp` file that defines a library with all the source
-    files needed to provide a complete Starboard Linux implementation. Source
-    files that are specific to Linux are in this directory, whereas shared
-    implementations are pulled from the shared directory.
+    files needed to provide a complete linkable Starboard implementation.
   * `nplb/` - "No Platform Left Behind," Starboard's platform verification test
     suite.
   * `shared/` - The home of all code that can be shared between Starboard
diff --git a/src/starboard/atomic.h b/src/starboard/atomic.h
index f0fa6d8..e9609c7 100644
--- a/src/starboard/atomic.h
+++ b/src/starboard/atomic.h
@@ -115,7 +115,11 @@
 
 // Pointer-sized atomic operations. Forwards to either 32-bit or 64-bit
 // functions as appropriate.
-typedef intptr_t SbAtomicPtr;
+#if SB_HAS(64_BIT_POINTERS)
+typedef SbAtomic64 SbAtomicPtr;
+#else
+typedef SbAtomic32 SbAtomicPtr;
+#endif
 
 static SB_C_FORCE_INLINE SbAtomicPtr
 SbAtomicNoBarrier_CompareAndSwapPtr(volatile SbAtomicPtr* ptr,
diff --git a/src/starboard/client_porting/eztime/eztime.cc b/src/starboard/client_porting/eztime/eztime.cc
index 10c86f4..aa4f737 100644
--- a/src/starboard/client_porting/eztime/eztime.cc
+++ b/src/starboard/client_porting/eztime/eztime.cc
@@ -115,8 +115,17 @@
   SB_DCHECK(value);
   SB_DCHECK(out_exploded);
   UErrorCode status = U_ZERO_ERROR;
+
+  // Always query the time using a gregorian calendar.  This is
+  // implied in opengroup documentation for tm struct, even though it is not
+  // specified.  E.g. in gmtime's documentation, it states that UTC time is
+  // used, and in tm struct's documentation it is specified that year should
+  // be an offset from 1900.
+
+  // See:
+  // http://pubs.opengroup.org/onlinepubs/009695399/functions/gmtime.html
   UCalendar* calendar = ucal_open(GetTimeZoneId(timezone), -1,
-                                  uloc_getDefault(), UCAL_DEFAULT, &status);
+                                  uloc_getDefault(), UCAL_GREGORIAN, &status);
   if (!calendar) {
     return false;
   }
@@ -157,8 +166,17 @@
                                EzTimeZone timezone) {
   SB_DCHECK(exploded);
   UErrorCode status = U_ZERO_ERROR;
+
+  // Always query the time using a gregorian calendar.  This is
+  // implied in opengroup documentation for tm struct, even though it is not
+  // specified.  E.g. in gmtime's documentation, it states that UTC time is
+  // used, and in tm struct's documentation it is specified that year should
+  // be an offset from 1900.
+
+  // See:
+  // http://pubs.opengroup.org/onlinepubs/009695399/functions/gmtime.html
   UCalendar* calendar = ucal_open(GetTimeZoneId(timezone), -1,
-                                  uloc_getDefault(), UCAL_DEFAULT, &status);
+                                  uloc_getDefault(), UCAL_GREGORIAN, &status);
   if (!calendar) {
     EzTimeValue zero_time = {};
     return zero_time;
diff --git a/src/starboard/common/new.cc b/src/starboard/common/new.cc
index 7e08ed0..14c0678 100644
--- a/src/starboard/common/new.cc
+++ b/src/starboard/common/new.cc
@@ -21,7 +21,7 @@
   return SbMemoryAllocate(size);
 }
 
-void operator delete(void* pointer) {
+void operator delete(void* pointer) noexcept {
   SbMemoryDeallocate(pointer);
 }
 
@@ -29,6 +29,6 @@
   return SbMemoryAllocate(size);
 }
 
-void operator delete[](void* pointer) {
+void operator delete[](void* pointer) noexcept {
   SbMemoryDeallocate(pointer);
 }
diff --git a/src/starboard/input.h b/src/starboard/input.h
index 0b2eba4..0959a00 100644
--- a/src/starboard/input.h
+++ b/src/starboard/input.h
@@ -105,11 +105,11 @@
   // only relative movements are provided.
   kSbInputEventTypeMove,
 
-  // Key or button press activation. This could be a key on a keyboard, a
-  // button on a mouse or game controller, a push from a touch screen, or
-  // a gesture. An |Unpress| event is subsequently delivered when the
-  // |Press| event terminates, such as when the key/button/finger is raised.
-  // Injecting repeat presses is up to the client.
+  // Key or button press activation. This could be a key on a keyboard, a button
+  // on a mouse or game controller, a push from a touch screen, or a gesture. An
+  // |Unpress| event is subsequently delivered when the |Press| event
+  // terminates, such as when the key/button/finger is raised. Injecting repeat
+  // presses is up to the client.
   kSbInputEventTypePress,
 
 #if SB_API_VERSION < 5
@@ -165,41 +165,38 @@
   wchar_t character;
 
   // The location of the specified key, in cases where there are multiple
-  // instances of the button on the keyboard. For example, some keyboards
-  // have more than one "shift" key.
+  // instances of the button on the keyboard. For example, some keyboards have
+  // more than one "shift" key.
   SbKeyLocation key_location;
 
   // Key modifiers (e.g. |Ctrl|, |Shift|) held down during this input event.
   unsigned int key_modifiers;
 
-  // The (x, y) coordinates of the persistent cursor controlled by this
-  // device. The value is |0| if this data is not applicable.
+  // The (x, y) coordinates of the persistent cursor controlled by this device.
+  // The value is |0| if this data is not applicable.
   SbInputVector position;
 
-  // The relative motion vector of this input. The value is |0| if this data
-  // is not applicable.
+  // The relative motion vector of this input. The value is |0| if this data is
+  // not applicable.
   SbInputVector delta;
 
 #if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
-  // The normalized pressure of the pointer input in the range of
-  // [0,1], where 0 and 1 represent the minimum and maximum pressure
-  // the hardware is capable of detecting, respectively. Use NaN for
-  // devices that do not report pressure. This value is only used for input
-  // events with device type mouse or touch screen.
+  // The normalized pressure of the pointer input in the range of [0,1], where 0
+  // and 1 represent the minimum and maximum pressure the hardware is capable of
+  // detecting, respectively. Use NaN for devices that do not report pressure.
+  // This value is used for input events with device type mouse or touch screen.
   float pressure;
 
-  // The (width, height) of the contact geometry of the pointer.
-  // This defines the limits of the values reported for the pointer
-  // coordinates in the 'position' field. If (NaN, NaN) is specified,
-  // the width and height of the window will be used. This value is only used
-  // for input events with device type mouse or touch screen.
+  // The (width, height) of the contact geometry of the pointer. This defines
+  // the size of the area under the pointer position. If (NaN, NaN) is
+  // specified, the value (0,0) will be used. This value is used for input
+  // events with device type mouse or touch screen.
   SbInputVector size;
 
-  // The (x, y) angle in degrees, in the range of [-90, 90] of the
-  // pointer, relative to the z axis. Positive values are for tilt
-  // to the right (x), and towards the user (y). Use (NaN, NaN) for
-  // devices that do not report tilt. This value is only used for input events
-  // with device type mouse or touch screen.
+  // The (x, y) angle in degrees, in the range of [-90, 90] of the pointer,
+  // relative to the z axis. Positive values are for tilt to the right (x), and
+  // towards the user (y). Use (NaN, NaN) for devices that do not report tilt.
+  // This value is used for input events with device type mouse or touch screen.
   SbInputVector tilt;
 #endif
 } SbInputData;
diff --git a/src/starboard/linux/__init__.py b/src/starboard/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/linux/__init__.py
diff --git a/src/starboard/linux/shared/__init__.py b/src/starboard/linux/shared/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/linux/shared/__init__.py
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index 14401e4..8569596 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -13,6 +13,9 @@
 # limitations under the License.
 """Starboard Linux shared platform configuration for gyp_cobalt."""
 
+import imp
+import os
+
 import config.starboard
 import gyp_utils
 
@@ -51,3 +54,10 @@
         'CXX': self.host_compiler_environment['CXX_host'],
     })
     return env_variables
+
+  def GetLauncher(self):
+    """Gets the module used to launch applications on this platform."""
+    module_path = os.path.abspath(os.path.join(
+        os.path.dirname(__file__), 'launcher.py'))
+    launcher_module = imp.load_source('launcher', module_path)
+    return launcher_module
diff --git a/src/starboard/linux/shared/launcher.py b/src/starboard/linux/shared/launcher.py
new file mode 100644
index 0000000..73e708a
--- /dev/null
+++ b/src/starboard/linux/shared/launcher.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+#
+# 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.
+"""Linux implementation of Starboard launcher abstraction."""
+
+import imp
+import os
+import signal
+import socket
+import subprocess
+
+module_path = os.path.abspath(
+    os.path.join(os.path.dirname(__file__),
+                 os.pardir, os.pardir, "tools", "abstract_launcher.py"))
+
+abstract_launcher = imp.load_source("abstract_launcher", module_path)
+
+
+class Launcher(abstract_launcher.AbstractLauncher):
+  """Class for launching Cobalt/tools on Linux."""
+
+  def __init__(self, platform, target_name, config, device_id, args):
+    super(Launcher, self).__init__(platform, target_name, config, device_id,
+                                   args)
+    if not self.device_id:
+      if socket.has_ipv6:  #  If the device supports IPv6:
+        self.device_id = "::1"  #  Use the only IPv6 loopback address
+      else:
+        self.device_id = socket.gethostbyname("localhost")
+
+    executable_dir = "{}_{}".format(self.platform, self.config)
+    self.executable = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                                   os.pardir, os.pardir,
+                                                   os.pardir, "out",
+                                                   executable_dir,
+                                                   target_name))
+
+    self.pid = None
+
+  def Run(self):
+    """Runs launcher's executable."""
+
+    signal.signal(signal.SIGTERM, lambda signum, frame: self.Kill())
+    signal.signal(signal.SIGINT, lambda signum, frame: self.Kill())
+    proc = subprocess.Popen([self.executable] + self.target_command_line_params)
+    self.pid = proc.pid
+    proc.wait()
+
+  def Kill(self):
+    print "\n***Killing Launcher***\n"
+    if self.pid:
+      try:
+        os.kill(self.pid, signal.SIGTERM)
+      except OSError:
+        raise OSError("Process already closed.")
diff --git a/src/starboard/linux/shared/system_get_path.cc b/src/starboard/linux/shared/system_get_path.cc
index 6621841..4ae443b 100644
--- a/src/starboard/linux/shared/system_get_path.cc
+++ b/src/starboard/linux/shared/system_get_path.cc
@@ -16,6 +16,7 @@
 
 #include <linux/limits.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include <cstring>
@@ -23,8 +24,28 @@
 #include "starboard/directory.h"
 #include "starboard/log.h"
 #include "starboard/string.h"
+#include "starboard/user.h"
 
 namespace {
+const int kMaxPathSize = SB_FILE_MAX_PATH;
+
+// Gets the path to the cache directory, using the user's home directory.
+bool GetCacheDirectory(char* out_path, int path_size) {
+  char home_path[kMaxPathSize + 1];
+  if (!SbUserGetProperty(SbUserGetCurrent(), kSbUserPropertyHomeDirectory,
+                         home_path, kMaxPathSize)) {
+    return false;
+  }
+  int result =
+      SbStringFormatF(out_path, path_size, "%s/.cache/cobalt", home_path);
+  if (result < 0 || result >= path_size) {
+    out_path[0] = '\0';
+    return false;
+  }
+
+  return true;
+}
+
 // Places up to |path_size| - 1 characters of the path to the current
 // executable in |out_path|, ensuring it is NULL-terminated. Returns success
 // status. The result being greater than |path_size| - 1 characters is a
@@ -34,8 +55,8 @@
     return false;
   }
 
-  char path[PATH_MAX + 1];
-  size_t bytes_read = readlink("/proc/self/exe", path, PATH_MAX);
+  char path[kMaxPathSize + 1];
+  size_t bytes_read = readlink("/proc/self/exe", path, kMaxPathSize);
   if (bytes_read < 1) {
     return false;
   }
@@ -68,6 +89,37 @@
   *last_slash = '\0';
   return true;
 }
+
+// Gets only the name portion of the current executable.
+bool GetExecutableName(char* out_path, int path_size) {
+  char path[kMaxPathSize] = {0};
+  if (!GetExecutablePath(path, kMaxPathSize)) {
+    return false;
+  }
+
+  const char* last_slash = SbStringFindLastCharacter(path, '/');
+  if (SbStringCopy(out_path, last_slash + 1, path_size) >= path_size) {
+    return false;
+  }
+  return true;
+}
+
+// Gets the path to a temporary directory that is unique to this process.
+bool GetTemporaryDirectory(char* out_path, int path_size) {
+  char binary_name[kMaxPathSize] = {0};
+  if (!GetExecutableName(binary_name, kMaxPathSize)) {
+    return false;
+  }
+
+  int result = SbStringFormatF(out_path, path_size, "/tmp/%s-%d", binary_name,
+                               static_cast<int>(getpid()));
+  if (result < 0 || result >= path_size) {
+    out_path[0] = '\0';
+    return false;
+  }
+
+  return true;
+}
 }  // namespace
 
 bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) {
@@ -89,13 +141,9 @@
       }
       break;
     case kSbSystemPathCacheDirectory:
-      if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) {
+      if (!GetCacheDirectory(path, kPathSize)) {
         return false;
       }
-      if (SbStringConcat(path, "/cache", kPathSize) >= kPathSize) {
-        return false;
-      }
-
       SbDirectoryCreate(path);
       break;
     case kSbSystemPathDebugOutputDirectory:
@@ -118,7 +166,7 @@
       }
       break;
     case kSbSystemPathTempDirectory:
-      if (SbStringCopy(path, "/tmp/cobalt", kPathSize) >= kPathSize) {
+      if (!GetTemporaryDirectory(path, kPathSize)) {
         return false;
       }
 
diff --git a/src/starboard/linux/x64x11/gyp_configuration.py b/src/starboard/linux/x64x11/gyp_configuration.py
index 2574baa..6685818 100644
--- a/src/starboard/linux/x64x11/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/gyp_configuration.py
@@ -14,19 +14,45 @@
 """Starboard Linux X64 X11 platform configuration for gyp_cobalt."""
 
 import logging
-import os
-import sys
 
-# Import the shared Linux platform configuration.
-sys.path.append(
-    os.path.realpath(
-        os.path.join(os.path.dirname(__file__), os.pardir, 'shared')))
-import gyp_configuration
+from starboard.linux.shared import gyp_configuration
+from starboard.tools.toolchain import ar
+from starboard.tools.toolchain import bash
+from starboard.tools.toolchain import clang
+from starboard.tools.toolchain import clangxx
+from starboard.tools.toolchain import cp
+from starboard.tools.toolchain import touch
+
+
+class PlatformConfig(gyp_configuration.PlatformConfig):
+
+  def __init__(self):
+    super(PlatformConfig, self).__init__('linux-x64x11')
+
+  def GetTargetToolchain(self):
+    return self.GetHostToolchain()
+
+  def GetHostToolchain(self):
+    environment_variables = self.GetEnvironmentVariables()
+    cc_path = environment_variables['CC']
+    cxx_path = environment_variables['CXX']
+
+    return [
+        clang.CCompiler(path=cc_path),
+        clang.CxxCompiler(path=cxx_path),
+        clang.AssemblerWithCPreprocessor(path=cc_path),
+        ar.StaticThinLinker(),
+        ar.StaticLinker(),
+        clangxx.ExecutableLinker(path=cxx_path),
+        cp.Copy(),
+        touch.Stamp(),
+        bash.Shell(),
+    ]
 
 
 def CreatePlatformConfig():
   try:
-    return gyp_configuration.PlatformConfig('linux-x64x11')
+    return PlatformConfig()
   except RuntimeError as e:
     logging.critical(e)
     return None
diff --git a/src/starboard/linux/x64x11/main.cc b/src/starboard/linux/x64x11/main.cc
index 12cbce1..7924f55 100644
--- a/src/starboard/linux/x64x11/main.cc
+++ b/src/starboard/linux/x64x11/main.cc
@@ -17,6 +17,7 @@
 #include "starboard/configuration.h"
 #include "starboard/shared/signal/crash_signals.h"
 #include "starboard/shared/signal/suspend_signals.h"
+#include "starboard/shared/starboard/link_receiver.h"
 #include "starboard/shared/x11/application_x11.h"
 
 extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) {
@@ -24,7 +25,11 @@
   starboard::shared::signal::InstallCrashSignalHandlers();
   starboard::shared::signal::InstallSuspendSignalHandlers();
   starboard::shared::x11::ApplicationX11 application;
-  int result = application.Run(argc, argv);
+  int result = 0;
+  {
+    starboard::shared::starboard::LinkReceiver receiver(&application);
+    result = application.Run(argc, argv);
+  }
   starboard::shared::signal::UninstallSuspendSignalHandlers();
   starboard::shared::signal::UninstallCrashSignalHandlers();
   return result;
diff --git a/src/starboard/linux/x64x11/starboard_platform.gypi b/src/starboard/linux/x64x11/starboard_platform.gypi
index 05f1e12..6e54ebf 100644
--- a/src/starboard/linux/x64x11/starboard_platform.gypi
+++ b/src/starboard/linux/x64x11/starboard_platform.gypi
@@ -19,6 +19,7 @@
       '<(DEPTH)/starboard/linux/x64x11/main.cc',
       '<(DEPTH)/starboard/linux/x64x11/sanitizer_options.cc',
       '<(DEPTH)/starboard/linux/x64x11/system_get_property.cc',
+      '<(DEPTH)/starboard/shared/starboard/link_receiver.cc',
       '<(DEPTH)/starboard/shared/x11/application_x11.cc',
       '<(DEPTH)/starboard/shared/x11/window_create.cc',
       '<(DEPTH)/starboard/shared/x11/window_destroy.cc',
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index ef605f5..6c0c76f 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -176,6 +176,7 @@
         'socket_waiter_wait_test.cc',
         'socket_waiter_wait_timed_test.cc',
         'socket_waiter_wake_up_test.cc',
+        'socket_wrapper_test.cc',
         'speech_recognizer_cancel_test.cc',
         'speech_recognizer_create_test.cc',
         'speech_recognizer_destroy_test.cc',
diff --git a/src/starboard/nplb/socket_helpers.cc b/src/starboard/nplb/socket_helpers.cc
index c78a21a..115f08f 100644
--- a/src/starboard/nplb/socket_helpers.cc
+++ b/src/starboard/nplb/socket_helpers.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/nplb/socket_helpers.h"
 
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/once.h"
 #include "starboard/socket.h"
 #include "starboard/socket_waiter.h"
@@ -125,6 +126,23 @@
   return server_socket;
 }
 
+scoped_ptr<Socket> CreateServerTcpSocketWrapped(
+    SbSocketAddressType address_type) {
+  scoped_ptr<Socket> server_socket =
+      make_scoped_ptr(new Socket(address_type, kSbSocketProtocolTcp));
+  if (!server_socket->IsValid()) {
+    ADD_FAILURE() << "SbSocketCreate failed";
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  if (!server_socket->SetReuseAddress(true)) {
+    ADD_FAILURE() << "SbSocketSetReuseAddress failed";
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return server_socket.Pass();
+}
+
 SbSocket CreateBoundTcpSocket(SbSocketAddressType address_type, int port) {
   SbSocket server_socket = CreateServerTcpSocket(address_type);
   if (!SbSocketIsValid(server_socket)) {
@@ -142,6 +160,23 @@
   return server_socket;
 }
 
+scoped_ptr<Socket> CreateBoundTcpSocketWrapped(SbSocketAddressType address_type,
+                                               int port) {
+  scoped_ptr<Socket> server_socket = CreateServerTcpSocketWrapped(address_type);
+  if (!server_socket) {
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  SbSocketAddress address = GetUnspecifiedAddress(address_type, port);
+  SbSocketError result = server_socket->Bind(&address);
+  if (result != kSbSocketOk) {
+    ADD_FAILURE() << "SbSocketBind to " << port << " failed: " << result;
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return server_socket.Pass();
+}
+
 SbSocket CreateListeningTcpSocket(SbSocketAddressType address_type, int port) {
   SbSocket server_socket = CreateBoundTcpSocket(address_type, port);
   if (!SbSocketIsValid(server_socket)) {
@@ -158,6 +193,24 @@
   return server_socket;
 }
 
+scoped_ptr<Socket> CreateListeningTcpSocketWrapped(
+    SbSocketAddressType address_type,
+    int port) {
+  scoped_ptr<Socket> server_socket =
+      CreateBoundTcpSocketWrapped(address_type, port);
+  if (!server_socket) {
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  SbSocketError result = server_socket->Listen();
+  if (result != kSbSocketOk) {
+    ADD_FAILURE() << "SbSocketListen failed: " << result;
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return server_socket.Pass();
+}
+
 namespace {
 SbSocket CreateConnectingTcpSocket(SbSocketAddressType address_type, int port) {
   SbSocket client_socket = SbSocketCreate(address_type, kSbSocketProtocolTcp);
@@ -180,6 +233,30 @@
 
   return client_socket;
 }
+
+scoped_ptr<Socket> CreateConnectingTcpSocketWrapped(
+    SbSocketAddressType address_type,
+    int port) {
+  scoped_ptr<Socket> client_socket =
+      make_scoped_ptr(new Socket(address_type, kSbSocketProtocolTcp));
+  if (!client_socket->IsValid()) {
+    ADD_FAILURE() << "SbSocketCreate failed";
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  // Connect to localhost:<port>.
+  SbSocketAddress address = GetLocalhostAddress(address_type, port);
+
+  // This connect will probably return pending, but we'll assume it will connect
+  // eventually.
+  SbSocketError result = client_socket->Connect(&address);
+  if (result != kSbSocketOk && result != kSbSocketPending) {
+    ADD_FAILURE() << "SbSocketConnect failed: " << result;
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return client_socket.Pass();
+}
 }  // namespace
 
 SbSocket AcceptBySpinning(SbSocket server_socket, SbTime timeout) {
@@ -205,6 +282,29 @@
   return kSbSocketInvalid;
 }
 
+scoped_ptr<Socket> AcceptBySpinning(Socket* server_socket, SbTime timeout) {
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  while (true) {
+    Socket* accepted_socket = server_socket->Accept();
+    if (accepted_socket && accepted_socket->IsValid()) {
+      return make_scoped_ptr(accepted_socket);
+    }
+
+    // If we didn't get a socket, it should be pending.
+    EXPECT_TRUE(server_socket->IsPending());
+
+    // Check if we have passed our timeout.
+    if (SbTimeGetMonotonicNow() - start >= timeout) {
+      break;
+    }
+
+    // Just being polite.
+    SbThreadYield();
+  }
+
+  return scoped_ptr<Socket>().Pass();
+}
+
 bool WriteBySpinning(SbSocket socket,
                      const char* data,
                      int data_size,
@@ -232,6 +332,33 @@
   return true;
 }
 
+bool WriteBySpinning(Socket* socket,
+                     const char* data,
+                     int data_size,
+                     SbTime timeout) {
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  int total = 0;
+  while (total < data_size) {
+    int sent = socket->SendTo(data + total, data_size - total, NULL);
+    if (sent >= 0) {
+      total += sent;
+      continue;
+    }
+
+    if (!socket->IsPending()) {
+      return false;
+    }
+
+    if (SbTimeGetMonotonicNow() - start >= timeout) {
+      return false;
+    }
+
+    SbThreadYield();
+  }
+
+  return true;
+}
+
 bool ReadBySpinning(SbSocket socket,
                     char* out_data,
                     int data_size,
@@ -260,6 +387,106 @@
   return true;
 }
 
+bool ReadBySpinning(Socket* socket,
+                    char* out_data,
+                    int data_size,
+                    SbTime timeout) {
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  int total = 0;
+  while (total < data_size) {
+    int received =
+        socket->ReceiveFrom(out_data + total, data_size - total, NULL);
+    if (received >= 0) {
+      total += received;
+      continue;
+    }
+
+    if (!socket->IsPending()) {
+      return false;
+    }
+
+    if (SbTimeGetMonotonicNow() - start >= timeout) {
+      return false;
+    }
+
+    SbThreadYield();
+  }
+
+  return true;
+}
+
+int Transfer(SbSocket receive_socket,
+             char* out_data,
+             SbSocket send_socket,
+             const char* send_data,
+             int size) {
+  int send_total = 0;
+  int receive_total = 0;
+  while (receive_total < size) {
+    if (send_total < size) {
+      int bytes_sent = SbSocketSendTo(send_socket, send_data + send_total,
+                                      size - send_total, NULL);
+      if (bytes_sent < 0) {
+        if (SbSocketGetLastError(send_socket) != kSbSocketPending) {
+          return -1;
+        }
+        bytes_sent = 0;
+      }
+
+      send_total += bytes_sent;
+    }
+
+    int bytes_received = SbSocketReceiveFrom(
+        receive_socket, out_data + receive_total, size - receive_total, NULL);
+    if (bytes_received < 0) {
+      if (SbSocketGetLastError(receive_socket) != kSbSocketPending) {
+        return -1;
+      }
+      bytes_received = 0;
+    }
+
+    receive_total += bytes_received;
+  }
+
+  return size;
+}
+
+int Transfer(Socket* receive_socket,
+             char* out_data,
+             Socket* send_socket,
+             const char* send_data,
+             int size) {
+  int send_total = 0;
+  int receive_total = 0;
+  while (receive_total < size) {
+    if (send_total < size) {
+      int bytes_sent =
+          send_socket->SendTo(send_data + send_total, size - send_total, NULL);
+      if (bytes_sent < 0) {
+        if (!send_socket->IsPending()) {
+          return -1;
+        }
+        bytes_sent = 0;
+      }
+
+      send_total += bytes_sent;
+    }
+
+    int bytes_received = receive_socket->ReceiveFrom(
+        out_data + receive_total, size - receive_total, NULL);
+    if (bytes_received < 0) {
+      if (!receive_socket->IsPending()) {
+        return -1;
+      }
+      bytes_received = 0;
+    }
+
+    receive_total += bytes_received;
+  }
+
+  return size;
+}
+
 ConnectedTrio CreateAndConnect(SbSocketAddressType server_address_type,
                                SbSocketAddressType client_address_type,
                                int port,
@@ -292,6 +519,40 @@
   return ConnectedTrio(listen_socket, client_socket, server_socket);
 }
 
+scoped_ptr<ConnectedTrioWrapped> CreateAndConnectWrapped(
+    SbSocketAddressType server_address_type,
+    SbSocketAddressType client_address_type,
+    int port,
+    SbTime timeout) {
+  // Verify the listening socket.
+  scoped_ptr<Socket> listen_socket =
+      CreateListeningTcpSocketWrapped(server_address_type, port);
+  if (!listen_socket || !listen_socket->IsValid()) {
+    ADD_FAILURE() << "Could not create listen socket.";
+    return scoped_ptr<ConnectedTrioWrapped>().Pass();
+  }
+
+  // Verify the socket to connect to the listening socket.
+  scoped_ptr<Socket> client_socket =
+      CreateConnectingTcpSocketWrapped(client_address_type, port);
+  if (!client_socket || !client_socket->IsValid()) {
+    ADD_FAILURE() << "Could not create client socket.";
+    return scoped_ptr<ConnectedTrioWrapped>().Pass();
+  }
+
+  // Spin until the accept happens (or we get impatient).
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  scoped_ptr<Socket> server_socket =
+      AcceptBySpinning(listen_socket.get(), timeout);
+  if (!server_socket || !server_socket->IsValid()) {
+    ADD_FAILURE() << "Failed to accept within " << timeout;
+    return scoped_ptr<ConnectedTrioWrapped>().Pass();
+  }
+
+  return make_scoped_ptr(new ConnectedTrioWrapped(
+      listen_socket.Pass(), client_socket.Pass(), server_socket.Pass()));
+}
+
 SbTimeMonotonic TimedWait(SbSocketWaiter waiter) {
   SbTimeMonotonic start = SbTimeGetMonotonicNow();
   SbSocketWaiterWait(waiter);
diff --git a/src/starboard/nplb/socket_helpers.h b/src/starboard/nplb/socket_helpers.h
index 8cb8922..38086d4 100644
--- a/src/starboard/nplb/socket_helpers.h
+++ b/src/starboard/nplb/socket_helpers.h
@@ -15,6 +15,7 @@
 #ifndef STARBOARD_NPLB_SOCKET_HELPERS_H_
 #define STARBOARD_NPLB_SOCKET_HELPERS_H_
 
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/socket.h"
 #include "starboard/socket_waiter.h"
 #include "starboard/time.h"
@@ -46,31 +47,60 @@
 
 // Creates a TCP/IP server socket (sets Reuse Address option).
 SbSocket CreateServerTcpSocket(SbSocketAddressType address_type);
+scoped_ptr<Socket> CreateServerTcpSocketWrapped(
+    SbSocketAddressType address_type);
 
 // Creates a TCP/IP socket bound to all interfaces on the given port.
 SbSocket CreateBoundTcpSocket(SbSocketAddressType address_type, int port);
+scoped_ptr<Socket> CreateBoundTcpSocketWrapped(SbSocketAddressType address_type,
+                                               int port);
 
 // Creates a TCP/IP socket listening on all interfaces on the given port.
 SbSocket CreateListeningTcpSocket(SbSocketAddressType address_type, int port);
+scoped_ptr<Socket> CreateListeningTcpSocketWrapped(
+    SbSocketAddressType address_type,
+    int port);
 
 // Tries to accept a new connection from the given listening socket by checking,
 // yielding, and retrying for up to timeout. Returns kSbSocketInvalid if no
 // socket has been accepted in the given time.
 SbSocket AcceptBySpinning(SbSocket listen_socket, SbTime timeout);
+scoped_ptr<Socket> AcceptBySpinning(Socket* listen_socket, SbTime timeout);
 
 // Writes the given data to socket, spinning until success or error.
 bool WriteBySpinning(SbSocket socket,
                      const char* data,
                      int data_size,
                      SbTime timeout);
+bool WriteBySpinning(Socket* socket,
+                     const char* data,
+                     int data_size,
+                     SbTime timeout);
 
 // Reads the given amount of data from socket, spinning until success or error.
 bool ReadBySpinning(SbSocket socket,
                     char* out_data,
                     int data_size,
                     SbTime timeout);
+bool ReadBySpinning(Socket* socket,
+                    char* out_data,
+                    int data_size,
+                    SbTime timeout);
 
-typedef struct ConnectedTrio {
+// Transfers data between the two connected local sockets, spinning until |size|
+// has been transfered, or an error occurs.
+int Transfer(SbSocket receive_socket,
+             char* out_data,
+             SbSocket send_socket,
+             const char* send_data,
+             int size);
+int Transfer(Socket* receive_socket,
+             char* out_data,
+             Socket* send_socket,
+             const char* send_data,
+             int size);
+
+struct ConnectedTrio {
   ConnectedTrio()
       : listen_socket(kSbSocketInvalid),
         client_socket(kSbSocketInvalid),
@@ -84,7 +114,20 @@
   SbSocket listen_socket;
   SbSocket client_socket;
   SbSocket server_socket;
-} ConnectedTrio;
+};
+
+struct ConnectedTrioWrapped {
+  ConnectedTrioWrapped() {}
+  ConnectedTrioWrapped(scoped_ptr<Socket> listen_socket,
+                       scoped_ptr<Socket> client_socket,
+                       scoped_ptr<Socket> server_socket)
+      : listen_socket(listen_socket.Pass()),
+        client_socket(client_socket.Pass()),
+        server_socket(server_socket.Pass()) {}
+  scoped_ptr<Socket> listen_socket;
+  scoped_ptr<Socket> client_socket;
+  scoped_ptr<Socket> server_socket;
+};
 
 // Creates and returns 3 TCP/IP sockets, a connected client and server, and a
 // listener on the given port. If anything fails, adds a failure and returns
@@ -93,6 +136,11 @@
                                SbSocketAddressType client_address_type,
                                int port,
                                SbTime timeout);
+scoped_ptr<ConnectedTrioWrapped> CreateAndConnectWrapped(
+    SbSocketAddressType server_address_type,
+    SbSocketAddressType client_address_type,
+    int port,
+    SbTime timeout);
 
 // Waits on the given waiter, and returns the elapsed time.
 SbTimeMonotonic TimedWait(SbSocketWaiter waiter);
diff --git a/src/starboard/nplb/socket_wrapper_test.cc b/src/starboard/nplb/socket_wrapper_test.cc
new file mode 100644
index 0000000..1ec2ca4
--- /dev/null
+++ b/src/starboard/nplb/socket_wrapper_test.cc
@@ -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.
+
+#include <utility>
+
+#include "starboard/nplb/socket_helpers.h"
+#include "starboard/socket.h"
+#include "starboard/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+class PairSbSocketWrapperTest
+    : public ::testing::TestWithParam<
+          std::pair<SbSocketAddressType, SbSocketAddressType> > {
+ public:
+  SbSocketAddressType GetServerAddressType() { return GetParam().first; }
+  SbSocketAddressType GetClientAddressType() { return GetParam().second; }
+};
+
+TEST_P(PairSbSocketWrapperTest, SunnyDay) {
+  const int kBufSize = 256 * 1024;
+  const int kSockBufSize = kBufSize / 8;
+
+  scoped_ptr<ConnectedTrioWrapped> trio =
+      CreateAndConnectWrapped(GetServerAddressType(), GetClientAddressType(),
+                              GetPortNumberForTests(), kSocketTimeout);
+  ASSERT_TRUE(trio->server_socket);
+  ASSERT_TRUE(trio->server_socket->IsValid());
+
+  // Let's set the buffers small to create partial reads and writes.
+  trio->client_socket->SetReceiveBufferSize(kSockBufSize);
+  trio->server_socket->SetReceiveBufferSize(kSockBufSize);
+  trio->client_socket->SetSendBufferSize(kSockBufSize);
+  trio->server_socket->SetSendBufferSize(kSockBufSize);
+
+  // Create the buffers and fill the send buffer with a pattern, the receive
+  // buffer with zeros.
+  scoped_array<char> pattern(new char[kBufSize]);
+  scoped_array<char> send_buf(new char[kBufSize]);
+  scoped_array<char> receive_buf(new char[kBufSize]);
+  for (int i = 0; i < kBufSize; ++i) {
+    pattern[i] = static_cast<char>(i);
+    send_buf[i] = static_cast<char>(i);
+    receive_buf[i] = 0;
+  }
+
+  // Send from server to client and verify the pattern.
+  int transferred =
+      Transfer(trio->client_socket.get(), receive_buf.get(),
+               trio->server_socket.get(), send_buf.get(), kBufSize);
+  EXPECT_EQ(kBufSize, transferred);
+  for (int i = 0; i < kBufSize; ++i) {
+    EXPECT_EQ(pattern[i], send_buf[i]) << "Position " << i;
+    EXPECT_EQ(pattern[i], receive_buf[i]) << "Position " << i;
+  }
+
+  // Try the other way now, first clearing the target buffer again.
+  for (int i = 0; i < kBufSize; ++i) {
+    receive_buf[i] = 0;
+  }
+  transferred = Transfer(trio->server_socket.get(), receive_buf.get(),
+                         trio->client_socket.get(), send_buf.get(), kBufSize);
+  EXPECT_EQ(kBufSize, transferred);
+  for (int i = 0; i < kBufSize; ++i) {
+    EXPECT_EQ(pattern[i], send_buf[i]) << "Position " << i;
+    EXPECT_EQ(pattern[i], receive_buf[i]) << "Position " << i;
+  }
+}
+
+#if SB_HAS(IPV6)
+INSTANTIATE_TEST_CASE_P(
+    SbSocketAddressTypes,
+    PairSbSocketWrapperTest,
+    ::testing::Values(
+        std::make_pair(kSbSocketAddressTypeIpv4, kSbSocketAddressTypeIpv4),
+        std::make_pair(kSbSocketAddressTypeIpv6, kSbSocketAddressTypeIpv6),
+        std::make_pair(kSbSocketAddressTypeIpv6, kSbSocketAddressTypeIpv4)));
+#else
+INSTANTIATE_TEST_CASE_P(
+    SbSocketAddressTypes,
+    PairSbSocketWrapperTest,
+    ::testing::Values(std::make_pair(kSbSocketAddressTypeIpv4,
+                                     kSbSocketAddressTypeIpv4)));
+#endif
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/raspi/shared/gyp_configuration.gypi b/src/starboard/raspi/shared/gyp_configuration.gypi
index 5123449..2954044 100644
--- a/src/starboard/raspi/shared/gyp_configuration.gypi
+++ b/src/starboard/raspi/shared/gyp_configuration.gypi
@@ -50,6 +50,9 @@
       # Force char to be signed.
       '-fsigned-char',
 
+      # To support large files
+      '-D_FILE_OFFSET_BITS=64',
+
       # Suppress some warnings that will be hard to fix.
       '-Wno-unused-local-typedefs',
       '-Wno-unused-result',
diff --git a/src/starboard/shared/lib/exported/starboard_main.h b/src/starboard/shared/lib/exported/starboard_main.h
new file mode 100644
index 0000000..3a49888
--- /dev/null
+++ b/src/starboard/shared/lib/exported/starboard_main.h
@@ -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.
+
+// This file provides a simple API for exposing Starboard as a
+// library to another app.
+
+#ifndef STARBOARD_SHARED_LIB_EXPORTED_STARBOARD_MAIN_H_
+#define STARBOARD_SHARED_LIB_EXPORTED_STARBOARD_MAIN_H_
+
+#include "starboard/export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SB_EXPORT_PLATFORM int StarboardMain(int argc, char** argv);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_SHARED_LIB_EXPORTED_STARBOARD_MAIN_H_
diff --git a/src/starboard/shared/msvc/uwp/toolchain.py b/src/starboard/shared/msvc/uwp/toolchain.py
index e0d9d3d..7c74f32 100644
--- a/src/starboard/shared/msvc/uwp/toolchain.py
+++ b/src/starboard/shared/msvc/uwp/toolchain.py
@@ -14,9 +14,9 @@
 
 import gyp.MSVSVersion  # pylint: disable=g-import-not-at-top
 # This tool_chain is starboard/build/toolchain.py
-from starboard.tools.toolchain import CompilerSettings
-from starboard.tools.toolchain import PrecompiledHeader
-from starboard.tools.toolchain import Toolchain
+from starboard.tools.toolchain_deprecated import CompilerSettings
+from starboard.tools.toolchain_deprecated import PrecompiledHeader
+from starboard.tools.toolchain_deprecated import Toolchain
 
 def _LanguageMatchesForPch(source_ext, pch_source_ext):
   c_exts = ('.c',)
diff --git a/src/starboard/shared/starboard/link_receiver.cc b/src/starboard/shared/starboard/link_receiver.cc
new file mode 100644
index 0000000..e94db87
--- /dev/null
+++ b/src/starboard/shared/starboard/link_receiver.cc
@@ -0,0 +1,431 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/link_receiver.h"
+
+#include <string>
+#include <unordered_map>
+
+#include "starboard/atomic.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/common/semaphore.h"
+#include "starboard/file.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/socket.h"
+#include "starboard/socket_waiter.h"
+#include "starboard/string.h"
+#include "starboard/system.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+
+namespace {
+// Returns an address that means bind to any interface on the given |port|. When
+// |port| is zero, it means the system should choose the port.
+SbSocketAddress GetUnspecifiedAddress(SbSocketAddressType address_type,
+                                      int port) {
+  SbSocketAddress address = {0};
+  address.type = address_type;
+  address.port = port;
+  return address;
+}
+
+// Returns an address that means bind to the loopback interface on the given
+// |port|. When |port| is zero, it means the system should choose the port.
+SbSocketAddress GetLocalhostAddress(SbSocketAddressType address_type,
+                                    int port) {
+  SbSocketAddress address = GetUnspecifiedAddress(address_type, port);
+  switch (address_type) {
+    case kSbSocketAddressTypeIpv4: {
+      address.address[0] = 127;
+      address.address[3] = 1;
+      return address;
+    }
+    case kSbSocketAddressTypeIpv6: {
+      address.address[15] = 1;
+      return address;
+    }
+  }
+  SB_LOG(ERROR) << __FUNCTION__ << ": unknown address type: " << address_type;
+  return address;
+}
+
+// Creates a socket that is appropriate for binding and listening, but is not
+// bound and hasn't started listening yet.
+scoped_ptr<Socket> CreateServerSocket(SbSocketAddressType address_type) {
+  scoped_ptr<Socket> socket(new Socket(address_type));
+  if (!socket->IsValid()) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketCreate failed";
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  if (!socket->SetReuseAddress(true)) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketSetReuseAddress failed";
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return socket.Pass();
+}
+
+// Creates a server socket that is bound to the loopback interface.
+scoped_ptr<Socket> CreateLocallyBoundSocket(SbSocketAddressType address_type,
+                                            int port) {
+  scoped_ptr<Socket> socket = CreateServerSocket(address_type);
+  if (!socket) {
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  SbSocketAddress address = GetLocalhostAddress(address_type, port);
+  SbSocketError result = socket->Bind(&address);
+  if (result != kSbSocketOk) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketBind to " << port << " failed: " << result;
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return socket.Pass();
+}
+
+// Creates a server socket that is bound and listening to the loopback interface
+// on the given port.
+scoped_ptr<Socket> CreateListeningSocket(SbSocketAddressType address_type,
+                                         int port) {
+  scoped_ptr<Socket> socket = CreateLocallyBoundSocket(address_type, port);
+  if (!socket) {
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  SbSocketError result = socket->Listen();
+  if (result != kSbSocketOk) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketListen failed: " << result;
+    return scoped_ptr<Socket>().Pass();
+  }
+
+  return socket.Pass();
+}
+
+// Gets the port socket is bound to.
+bool GetBoundPort(Socket* socket, int* out_port) {
+  SB_DCHECK(out_port);
+  SB_DCHECK(socket);
+
+  SbSocketAddress socket_address = {0};
+  bool result = socket->GetLocalAddress(&socket_address);
+  if (!result) {
+    return false;
+  }
+
+  *out_port = socket_address.port;
+  return true;
+}
+
+std::string GetTemporaryDirectory() {
+  const int kMaxPathLength = SB_FILE_MAX_PATH;
+  scoped_array<char> temp_path(new char[kMaxPathLength]);
+  bool has_temp = SbSystemGetPath(kSbSystemPathTempDirectory, temp_path.get(),
+                                  kMaxPathLength);
+  if (!has_temp) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "No temporary directory.";
+    return "";
+  }
+
+  return std::string(temp_path.get());
+}
+
+// Writes |size| bytes of |contents| to the file at |name|.
+void CreateTemporaryFile(const char* name, const char* contents, int size) {
+  std::string path = GetTemporaryDirectory();
+  if (path.empty()) {
+    return;
+  }
+
+  path += SB_FILE_SEP_STRING;
+  path += name;
+  ScopedFile file(path.c_str(), kSbFileCreateAlways | kSbFileWrite);
+  if (!file.IsValid()) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "Unable to create: " << path;
+    return;
+  }
+
+  file.WriteAll(contents, size);
+  file.Flush();
+}
+}  // namespace
+
+// PImpl of LinkReceiver.
+class LinkReceiver::Impl {
+ public:
+  Impl(Application* application, int port);
+  ~Impl();
+
+ private:
+  // Encapsulates connection state.
+  struct Connection {
+    explicit Connection(scoped_ptr<Socket> socket) : socket(socket.Pass()) {}
+    ~Connection() {}
+    void FlushLink(Application* application) {
+      if (!data.empty()) {
+        application->Link(data.c_str());
+        data.clear();
+      }
+    }
+
+    scoped_ptr<Socket> socket;
+    std::string data;
+  };
+
+  // Runs the server, waiting on an SbSocketWaiter, and blocking until shut
+  // down.
+  void Run();
+
+  // Adds |socket| to the SbSocketWaiter to wait until ready for accepting a new
+  // connection.
+  bool AddForAccept(Socket* socket);
+
+  // Adds the |connection| to the SbSocketWaiter to wait until ready to read
+  // more data.
+  bool AddForRead(Connection* connection);
+
+  // Called when the listening socket has a connection available to accept.
+  void OnAcceptReady();
+
+  // Called when the waiter reports that a socket has more data to read.
+  void OnReadReady(SbSocket sb_socket);
+
+  // Called when the waiter reports that a connection has more data to read.
+  void OnReadReady(Connection* connection);
+
+  // Thread entry point.
+  static void* RunThread(void* context);
+
+  // SbSocketWaiter entry points.
+  static void HandleAccept(SbSocketWaiter waiter,
+                           SbSocket socket,
+                           void* context,
+                           int ready_interests);
+  static void HandleRead(SbSocketWaiter waiter,
+                         SbSocket socket,
+                         void* context,
+                         int ready_interests);
+
+  // The application to dispatch Link() calls to.
+  Application* application_;
+
+  // The port that was specified by the constructor.
+  const int specified_port_;
+
+  // The port that was queried off of the bound socket.
+  int actual_port_;
+
+  // The thread owned by this server.
+  SbThread thread_;
+
+  // An atomic flag that indicates whether to quit to the server thread.
+  atomic_bool quit_;
+
+  // The waiter to register sockets with and block on.
+  SbSocketWaiter waiter_;
+
+  // A semaphore that will be signaled by the internal thread once the waiter
+  // has been initialized, so the external thread can safely wake up the waiter.
+  Semaphore waiter_initialized_;
+
+  // A semaphore that will be signaled by the external thread indicating that it
+  // will no longer reference the waiter, so that the internal thread can safely
+  // destroy the waiter.
+  Semaphore destroy_waiter_;
+
+  // The server socket listening for new connections.
+  scoped_ptr<Socket> listen_socket_;
+
+  // A map of raw SbSockets to Connection objects.
+  std::unordered_map<SbSocket, Connection*> connections_;
+};
+
+LinkReceiver::Impl::Impl(Application* application, int port)
+    : application_(application),
+      specified_port_(port),
+      thread_(kSbThreadInvalid),
+      waiter_(kSbSocketWaiterInvalid) {
+  thread_ =
+      SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true,
+                     "LinkReceiver", &LinkReceiver::Impl::RunThread, this);
+
+  // Block until waiter is set.
+  waiter_initialized_.Take();
+}
+
+LinkReceiver::Impl::~Impl() {
+  SB_DCHECK(!SbThreadIsEqual(thread_, SbThreadGetCurrent()));
+  quit_.store(true);
+  SbSocketWaiterWakeUp(waiter_);
+  destroy_waiter_.Put();
+  SbThreadJoin(thread_, NULL);
+}
+
+void LinkReceiver::Impl::Run() {
+  waiter_ = SbSocketWaiterCreate();
+  SB_DCHECK(SbSocketWaiterIsValid(waiter_));
+  listen_socket_ =
+      CreateListeningSocket(kSbSocketAddressTypeIpv4, specified_port_);
+  SB_DCHECK(listen_socket_);
+  actual_port_ = 0;
+  bool result = GetBoundPort(listen_socket_.get(), &actual_port_);
+  SB_DCHECK(result);
+  SB_LOG(INFO) << "LinkReceiver port: " << actual_port_;
+
+  char port_string[32] = {0};
+  SbStringFormatF(port_string, SB_ARRAY_SIZE(port_string), "%d", actual_port_);
+  CreateTemporaryFile("link_receiver_port", port_string,
+                      SbStringGetLength(port_string));
+
+  if (!AddForAccept(listen_socket_.get())) {
+    quit_.store(true);
+  }
+
+  waiter_initialized_.Put();
+  while (!quit_.load()) {
+    SbSocketWaiterWait(waiter_);
+  }
+
+  for (auto& entry : connections_) {
+    SbSocketWaiterRemove(waiter_, entry.first);
+    delete entry.second;
+  }
+  connections_.clear();
+
+  SbSocketWaiterRemove(waiter_, listen_socket_->socket());
+
+  // Block until destroying thread will no longer reference waiter.
+  destroy_waiter_.Take();
+  SbSocketWaiterDestroy(waiter_);
+}
+
+bool LinkReceiver::Impl::AddForAccept(Socket* socket) {
+  if (!SbSocketWaiterAdd(waiter_, socket->socket(), this,
+                         &LinkReceiver::Impl::HandleAccept,
+                         kSbSocketWaiterInterestRead, true)) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketWaiterAdd failed.";
+    return false;
+  }
+  return true;
+}
+
+bool LinkReceiver::Impl::AddForRead(Connection* connection) {
+  if (!SbSocketWaiterAdd(waiter_, connection->socket->socket(), this,
+                         &LinkReceiver::Impl::HandleRead,
+                         kSbSocketWaiterInterestRead, false)) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": "
+                  << "SbSocketWaiterAdd failed.";
+    return false;
+  }
+  return true;
+}
+
+void LinkReceiver::Impl::OnAcceptReady() {
+  scoped_ptr<Socket> accepted_socket =
+      make_scoped_ptr(listen_socket_->Accept());
+  SB_DCHECK(accepted_socket);
+  Connection* connection = new Connection(accepted_socket.Pass());
+  connections_.emplace(connection->socket->socket(), connection);
+  AddForRead(connection);
+}
+
+void LinkReceiver::Impl::OnReadReady(SbSocket sb_socket) {
+  auto iter = connections_.find(sb_socket);
+  SB_DCHECK(iter != connections_.end());
+  OnReadReady(iter->second);
+}
+
+void LinkReceiver::Impl::OnReadReady(Connection* connection) {
+  auto socket = connection->socket.get();
+
+  char data[64] = {0};
+  int read = socket->ReceiveFrom(data, SB_ARRAY_SIZE_INT(data), NULL);
+  int last_null = 0;
+  for (int position = 0; position < read; ++position) {
+    if (data[position] == '\0' || data[position] == '\n' ||
+        data[position] == '\r') {
+      int length = position - last_null;
+      if (length) {
+        connection->data.append(&data[last_null], length);
+        connection->FlushLink(application_);
+      }
+      last_null = position + 1;
+      continue;
+    }
+  }
+
+  int remainder = read - last_null;
+  if (remainder > 0) {
+    connection->data.append(&data[last_null], remainder);
+  }
+
+  if (read == 0) {
+    // Terminate connection.
+    connection->FlushLink(application_);
+    connections_.erase(socket->socket());
+    delete connection;
+    return;
+  }
+
+  AddForRead(connection);
+}
+
+// static
+void* LinkReceiver::Impl::RunThread(void* context) {
+  SB_DCHECK(context);
+  reinterpret_cast<LinkReceiver::Impl*>(context)->Run();
+  return NULL;
+}
+
+// static
+void LinkReceiver::Impl::HandleAccept(SbSocketWaiter /*waiter*/,
+                                      SbSocket /*socket*/,
+                                      void* context,
+                                      int ready_interests) {
+  SB_DCHECK(context);
+  reinterpret_cast<LinkReceiver::Impl*>(context)->OnAcceptReady();
+}
+
+// static
+void LinkReceiver::Impl::HandleRead(SbSocketWaiter /*waiter*/,
+                                    SbSocket socket,
+                                    void* context,
+                                    int /*ready_interests*/) {
+  SB_DCHECK(context);
+  reinterpret_cast<LinkReceiver::Impl*>(context)->OnReadReady(socket);
+}
+
+LinkReceiver::LinkReceiver(Application* application)
+    : impl_(new Impl(application, 0)) {}
+
+LinkReceiver::LinkReceiver(Application* application, int port)
+    : impl_(new Impl(application, port)) {}
+
+LinkReceiver::~LinkReceiver() {
+  delete impl_;
+}
+
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/starboard/link_receiver.h b/src/starboard/shared/starboard/link_receiver.h
new file mode 100644
index 0000000..b2cc68d
--- /dev/null
+++ b/src/starboard/shared/starboard/link_receiver.h
@@ -0,0 +1,55 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_LINK_RECEIVER_H_
+#define STARBOARD_SHARED_STARBOARD_LINK_RECEIVER_H_
+
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/application.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+
+// A loopback-only server that listens to receive null-terminated strings that
+// will be dispatched to the Link() method on the passed in |application|. This
+// will result in kSbEventTypeLink events beind dispatched on the main Starboard
+// dispatch thread.
+//
+// This server Runs on its own thread, joining it on destruction. It must be
+// destroyed before the associated Application is destroyed.
+//
+// When the server is started, it attempts to write a file to the temporary
+// directory with the port that it is listening on. Other programs can then look
+// for this file to find the port to connect to to send links.
+//
+// The script starboard/tools/send_link.py can dispatch links to this server, if
+// running.
+class LinkReceiver {
+ public:
+  explicit LinkReceiver(Application* application);
+  LinkReceiver(Application* application, int port);
+  ~LinkReceiver();
+
+ private:
+  class Impl;
+
+  Impl* impl_;
+};
+
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_LINK_RECEIVER_H_
diff --git a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
index 0a96110..bdf679d 100644
--- a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
+++ b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -257,6 +257,14 @@
       }
     }
 
+    std::string cryptoblockformat =
+        mime_type.GetParamStringValue("cryptoblockformat", "");
+    if (!cryptoblockformat.empty()) {
+      if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") {
+        return kSbMediaSupportTypeNotSupported;
+      }
+    }
+
     int width = 0;
     int height = 0;
     int fps = 0;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
index 598f725..dbaa49e 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -379,8 +379,11 @@
     scoped_refptr<DecodedAudio> resampled_audio;
     scoped_refptr<DecodedAudio> decoded_audio = decoder_->Read();
 
-    SB_DCHECK(decoded_audio);
     --pending_decoder_outputs_;
+    SB_DCHECK(decoded_audio);
+    if (!decoded_audio) {
+      continue;
+    }
 
     if (decoded_audio->is_end_of_stream()) {
       SB_DCHECK(eos_state_.load() == kEOSWrittenToDecoder) << eos_state_.load();
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index a57c6ac..76b6e53 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -87,6 +87,14 @@
   bounds_ = PlayerWorker::Bounds();
 }
 
+bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {

+#if SB_API_VERSION >= 4

+  return (output_mode_ == kSbPlayerOutputModePunchOut);

+#else

+  return true;

+#endif  // SB_API_VERSION >= 4

+}
+
 bool FilterBasedPlayerWorkerHandler::Init(
     PlayerWorker* player_worker,
     JobQueue* job_queue,
@@ -320,12 +328,9 @@
     player_worker_->UpdateDroppedVideoFrames(
         video_renderer_->GetDroppedFrames());
 
-#if SB_API_VERSION >= 4
-    if (output_mode_ == kSbPlayerOutputModePunchOut)
-#endif  // SB_API_VERSION >= 4
-    {
-      shared::starboard::Application::Get()->HandleFrame(
-          player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);
+   if (IsPunchoutMode()) {

+      shared::starboard::Application::Get()->HandleFrame(

+          player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);

     }
 
     (*player_worker_.*update_media_time_cb_)(audio_renderer_->GetCurrentTime());
@@ -350,13 +355,10 @@
   }
   video_renderer.reset();
 
-#if SB_API_VERSION >= 4
-  if (output_mode_ == kSbPlayerOutputModePunchOut)
-#endif  // SB_API_VERSION >= 4
-  {
-    // Clear the video frame as we terminate.
-    shared::starboard::Application::Get()->HandleFrame(
-        player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);
+  if (IsPunchoutMode()) {

+    // Clear the video frame as we terminate.

+    shared::starboard::Application::Get()->HandleFrame(

+        player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);

   }
 }
 
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
index 0ec1a3d..8715ab9 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -53,6 +53,7 @@
                                  );  // NOLINT(whitespace/parens)
 
  private:
+  bool IsPunchoutMode() const;
   bool Init(PlayerWorker* player_worker,
             JobQueue* job_queue,
             SbPlayer player,
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
index ffd6aca..685927f 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc
@@ -81,6 +81,7 @@
   seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0);
   seeking_ = true;
   end_of_stream_written_ = false;
+  need_more_input_ = true;
 
   frames_.clear();
 }
@@ -158,8 +159,15 @@
     return kSbDecodeTargetInvalid;
   }
 }
+
 #endif  // SB_API_VERSION >= 4
 
+::starboard::scoped_refptr<VideoFrame>
+VideoRendererImpl::GetLastDisplayedFrame() {
+  ScopedLock lock(mutex_);
+  return last_displayed_frame_;
+}
+
 }  // namespace filter
 }  // namespace player
 }  // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
index b40365e..77a1d31 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_impl_internal.h
@@ -62,6 +62,8 @@
   SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
 #endif  // SB_API_VERSION >= 4
 
+  scoped_refptr<VideoFrame> GetLastDisplayedFrame();
+
  private:
   typedef std::list<scoped_refptr<VideoFrame> > Frames;
 
diff --git a/src/starboard/shared/uwp/application_uwp.cc b/src/starboard/shared/uwp/application_uwp.cc
index 6aa337e..718abd8 100644
--- a/src/starboard/shared/uwp/application_uwp.cc
+++ b/src/starboard/shared/uwp/application_uwp.cc
@@ -77,7 +77,7 @@
 const int kWinSockVersionMajor = 2;
 const int kWinSockVersionMinor = 2;
 
-const char kYouTubeTVurl[] = "--url=https://www.youtube.com/tv/?";
+const char kDialParamPrefix[] = "cobalt-dial:?";
 
 int main_return_value = 0;
 
@@ -300,22 +300,19 @@
       if (uri->SchemeName->Equals("youtube") ||
           uri->SchemeName->Equals("ms-xbl-07459769")) {
         std::string uri_string = sbwin32::platformStringToString(uri->RawUri);
-        if (previously_activated_) {
-          std::unique_ptr<Application::Event> event =
-            MakeDeepLinkEvent(uri_string);
-          SB_DCHECK(event);
-          ApplicationUwp::Get()->Inject(event.release());
-        } else {
-          SB_DCHECK(!uri_string.empty());
-          ApplicationUwp::Get()->SetStartLink(uri_string.c_str());
-        }
+        ProcessDeepLinkUri(&uri_string);
       }
     } else if (args->Kind == ActivationKind::DialReceiver) {
-      if (!previously_activated_) {
-        DialReceiverActivatedEventArgs^ dial_args =
-            dynamic_cast<DialReceiverActivatedEventArgs^>(args);
-        SB_CHECK(dial_args);
-        Platform::String^ arguments = dial_args->Arguments;
+      DialReceiverActivatedEventArgs^ dial_args =
+          dynamic_cast<DialReceiverActivatedEventArgs^>(args);
+      SB_CHECK(dial_args);
+      Platform::String^ arguments = dial_args->Arguments;
+      if (previously_activated_) {
+        std::string uri_string =
+          kDialParamPrefix + sbwin32::platformStringToString(arguments);
+        ProcessDeepLinkUri(&uri_string);
+      } else {
+        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;
@@ -348,6 +345,19 @@
     previously_activated_ = true;
   }
  private:
+  void ProcessDeepLinkUri(std::string *uri_string) {
+    SB_DCHECK(uri_string);
+    if (previously_activated_) {
+      std::unique_ptr<Application::Event> event =
+        MakeDeepLinkEvent(*uri_string);
+      SB_DCHECK(event);
+      ApplicationUwp::Get()->Inject(event.release());
+    } else {
+      SB_DCHECK(!uri_string->empty());
+      ApplicationUwp::Get()->SetStartLink(uri_string->c_str());
+    }
+  }
+
   bool previously_activated_;
   // Only valid if previously_activated_ is true
   ActivationKind previous_activation_kind_;
diff --git a/src/starboard/shared/uwp/application_uwp.h b/src/starboard/shared/uwp/application_uwp.h
index b326d1e..fda18e2 100644
--- a/src/starboard/shared/uwp/application_uwp.h
+++ b/src/starboard/shared/uwp/application_uwp.h
@@ -16,7 +16,6 @@
 #define STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
 
 #include <agile.h>
-
 #include <string>
 #include <unordered_map>
 
@@ -26,7 +25,6 @@
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/command_line.h"
 #include "starboard/shared/starboard/localized_strings.h"
-#include "starboard/shared/uwp/winrt_workaround.h"
 #include "starboard/types.h"
 #include "starboard/window.h"
 
diff --git a/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp b/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp
index e7206c0..9765ab9 100644
--- a/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp
+++ b/src/starboard/shared/uwp/cobalt/cobalt_platform.gyp
@@ -36,7 +36,7 @@
           'AdditionalOptions': [
           '/ZW',           # Windows Runtime
           '/ZW:nostdlib',  # Windows Runtime, no default #using
-          '/EHsx',         # C++ exceptions (required with /ZW)
+          '/EHsc',         # C++ exceptions (required with /ZW)
           '/FU"<(visual_studio_install_path)/lib/x86/store/references/platform.winmd"',
           '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.FoundationContract/3.0.0.0/Windows.Foundation.FoundationContract.winmd"',
           '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.UniversalApiContract/4.0.0.0/Windows.Foundation.UniversalApiContract.winmd"',
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
index 9ec452d..eaf8bd3 100644
--- a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
@@ -19,7 +19,6 @@
 
 #include "starboard/mutex.h"
 #include "starboard/shared/uwp/async_utils.h"
-#include "starboard/shared/uwp/winrt_workaround.h"
 #include "starboard/shared/win32/wchar_utils.h"
 
 using Windows::Security::Authentication::Web::Core::
@@ -163,12 +162,30 @@
   return false;
 }
 
+void AppendUrlPath(const std::string& path, std::string* url_parameter) {
+  DCHECK(url_parameter);
+
+  if (path.empty()) {
+    return;
+  }
+
+  std::string& url(*url_parameter);
+
+  // if path starts with a '/' and url ends with a '/', remove trailing slash
+  // from url before appending the path
+  if (!url.empty() && *(url.rbegin()) == '/' && path[0] == '/') {
+    url.resize(url.size() - 1);
+  }
+  url.append(path);
+}
+
 }  // namespace
 
 namespace cobalt {
 namespace xhr {
 
-void CobaltXhrModifyHeader(net::HttpRequestHeaders* headers) {
+void CobaltXhrModifyHeader(const GURL& request_url,
+                           net::HttpRequestHeaders* headers) {
   DCHECK(headers);
 
   std::string relying_party;
@@ -178,6 +195,12 @@
   if (!trigger_header_found) {
     return;
   }
+
+  if (request_url.has_path()) {
+    std::string request_url_path = request_url.path();
+    AppendUrlPath(request_url_path, &relying_party);
+  }
+
   std::string out_string;
   if (!PopulateToken(relying_party, &out_string)) {
     return;
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc
index 138579a..88aa32c 100644
--- a/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers_test.cc
@@ -23,17 +23,20 @@
 
 using ::cobalt::xhr::CobaltXhrModifyHeader;
 using net::HttpRequestHeaders;
+const std::string kUrlString = "https://example.com/abc/xyz";
 
 TEST(XHRModificationTest, EmptyHeaders) {
   HttpRequestHeaders headers;
-  CobaltXhrModifyHeader(&headers);
+  GURL url(kUrlString);
+  CobaltXhrModifyHeader(url, &headers);
   EXPECT_TRUE(headers.IsEmpty());
 }
 
 TEST(XHRModificationTest, HeaderNotFound) {
   HttpRequestHeaders headers;
   headers.SetHeader("Authorization", "ABC");
-  CobaltXhrModifyHeader(&headers);
+  GURL url(kUrlString);
+  CobaltXhrModifyHeader(url, &headers);
   EXPECT_FALSE(!headers.IsEmpty());
   std::string headers_serialized = headers.ToString();
   EXPECT_STREQ(headers_serialized.c_str(), "Authorization: ABC\r\n\r\n");
@@ -44,7 +47,8 @@
   static const char* kXauthTriggerHeaderName = "X-STS-RelyingPartyId";
   headers.SetHeader(kXauthTriggerHeaderName, "ABC");
   EXPECT_TRUE(headers.HasHeader(kXauthTriggerHeaderName));
-  CobaltXhrModifyHeader(&headers);
+  GURL url(kUrlString);
+  CobaltXhrModifyHeader(url, &headers);
   EXPECT_FALSE(headers.HasHeader(kXauthTriggerHeaderName));
   std::string headers_serialized = headers.ToString();
   EXPECT_TRUE(headers_serialized.find("Authorization: XBL3.0 x=") !=
@@ -58,7 +62,8 @@
   headers.SetHeader("H2", "h2");
   headers.SetHeader("H3", "h3");
   EXPECT_TRUE(headers.HasHeader(kXauthTriggerHeaderName));
-  CobaltXhrModifyHeader(&headers);
+  GURL url(kUrlString);
+  CobaltXhrModifyHeader(url, &headers);
   EXPECT_TRUE(headers.HasHeader("H1"));
   EXPECT_TRUE(headers.HasHeader("H2"));
   EXPECT_TRUE(headers.HasHeader("H3"));
diff --git a/src/starboard/shared/uwp/get_home_directory.cc b/src/starboard/shared/uwp/get_home_directory.cc
new file mode 100644
index 0000000..6c08a3b
--- /dev/null
+++ b/src/starboard/shared/uwp/get_home_directory.cc
@@ -0,0 +1,42 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "starboard/log.h"
+#include "starboard/shared/nouser/user_internal.h"
+#include "starboard/shared/win32/wchar_utils.h"
+#include "starboard/string.h"
+#include "starboard/system.h"
+
+using Windows::Storage::ApplicationData;
+
+namespace sbwin32 = starboard::shared::win32;
+
+namespace starboard {
+namespace shared {
+namespace nouser {
+
+bool GetHomeDirectory(SbUser user, char* out_path, int path_size) {
+  if (user != SbUserGetCurrent()) {
+    return false;
+  }
+  std::string home_directory = sbwin32::platformStringToString(
+      ApplicationData::Current->LocalFolder->Path);
+  return SbStringCopy(out_path, home_directory.c_str(), path_size);
+}
+
+}  // namespace nouser
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi
index 5370972..ee1b5bd 100644
--- a/src/starboard/shared/uwp/starboard_platform.gypi
+++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -14,20 +14,22 @@
 {
   'variables': {
     'starboard_platform_dependent_files': [
+      'application_uwp_key_event.cc',
       'application_uwp.cc',
       'application_uwp.h',
-      'application_uwp_key_event.cc',
       'async_utils.h',
-      'system_get_property.cc',
+      'get_home_directory.cc',
       'system_clear_platform_error.cc',
+      'system_get_device_type.cc',
+      'system_get_property.cc',
       'system_raise_platform_error.cc',
       'window_create.cc',
       'window_destroy.cc',
       'window_get_platform_handle.cc',
       'window_get_size.cc',
-      'window_set_default_options.cc',
       'window_internal.cc',
       'window_internal.h',
+      'window_set_default_options.cc',
       'winrt_workaround.h',
       '<(DEPTH)/starboard/shared/starboard/localized_strings.cc',
       '<(DEPTH)/starboard/shared/starboard/system_request_pause.cc',
diff --git a/src/starboard/shared/uwp/system_get_device_type.cc b/src/starboard/shared/uwp/system_get_device_type.cc
new file mode 100644
index 0000000..81179ca
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_device_type.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include <string>
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/wchar_utils.h"
+
+using Windows::System::Profile::AnalyticsInfo;
+using Windows::System::Profile::AnalyticsVersionInfo;
+
+SbSystemDeviceType SbSystemGetDeviceType() {
+  AnalyticsVersionInfo ^ version_info = AnalyticsInfo::VersionInfo;
+  std::string family = starboard::shared::win32::platformStringToString(
+      version_info->DeviceFamily);
+
+  if (family.compare("Windows.Desktop") == 0) {
+    return kSbSystemDeviceTypeDesktopPC;
+  }
+  if (family.compare("Windows.Xbox") == 0) {
+    return kSbSystemDeviceTypeGameConsole;
+  }
+  SB_NOTREACHED();
+  return kSbSystemDeviceTypeUnknown;
+}
diff --git a/src/starboard/shared/uwp/system_get_property.cc b/src/starboard/shared/uwp/system_get_property.cc
index 85e85c5..0c48971 100644
--- a/src/starboard/shared/uwp/system_get_property.cc
+++ b/src/starboard/shared/uwp/system_get_property.cc
@@ -37,6 +37,37 @@
   SbStringCopy(out_value, from_value, value_length);

   return true;

 }

+

+const std::size_t kOsVersionSize = 128;

+

+struct WindowsVersion {

+  uint16_t major_version;

+  uint16_t minor_version;

+  uint16_t build_version;

+  uint16_t revision;

+};

+

+bool GetWindowsVersion(WindowsVersion* version) {

+  SB_DCHECK(version);

+  AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;

+  std::string device_family_version =

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

+          version_info->DeviceFamilyVersion);

+  if (device_family_version.empty()) {

+    return false;

+  }

+  uint64_t version_info_all =

+      SbStringParseUInt64(device_family_version.c_str(), nullptr, 10);

+  if (version_info_all == 0) {

+    return false;

+  }

+  version->major_version = (version_info_all >> 48) & 0xFFFF;

+  version->minor_version = (version_info_all >> 32) & 0xFFFF;

+  version->build_version = (version_info_all >> 16) & 0xFFFF;

+  version->revision = version_info_all & 0xFFFF;

+  return true;

+}

+

 }  // namespace

 

 bool SbSystemGetProperty(SbSystemPropertyId property_id,

@@ -53,6 +84,7 @@
     case kSbSystemPropertyModelYear:

     case kSbSystemPropertyNetworkOperatorName:

     case kSbSystemPropertySpeechApiKey:

+    case kSbSystemPropertyUserAgentAuxField:

       return false;

     case kSbSystemPropertyBrandName: {

       EasClientDeviceInformation^ current_device_info =

@@ -66,19 +98,19 @@
                                         brand_name.c_str());

     }

     case kSbSystemPropertyFirmwareVersion: {

-      EasClientDeviceInformation ^ current_device_info =

-          ref new EasClientDeviceInformation();

-      std::string firmware_version =

-          platformStringToString(current_device_info->SystemFirmwareVersion);

-      if (firmware_version.empty()) {

+      WindowsVersion version = {0};

+      if (!GetWindowsVersion(&version)) {

         return false;

       }

-      return CopyStringAndTestIfSuccess(out_value, value_length,

-                                        firmware_version.c_str());

+      int return_value = SbStringFormatF(

+          out_value, value_length, "%u.%u.%u.%u", version.major_version,

+          version.minor_version, version.build_version, version.revision);

+      return ((return_value > 0) && (return_value < value_length));

     }

     case kSbSystemPropertyModelName: {

-      EasClientDeviceInformation ^ current_device_info =

+      EasClientDeviceInformation^ current_device_info =

           ref new EasClientDeviceInformation();

+      // TODO: Use SystemSku and map to friendly names instead.

       std::string product_name =

           platformStringToString(current_device_info->SystemProductName);

       product_name.erase(

@@ -99,17 +131,40 @@
       return CopyStringAndTestIfSuccess(out_value, value_length,

                                         friendly_name.c_str());

     }

-

     case kSbSystemPropertyPlatformName: {

+      EasClientDeviceInformation^ current_device_info =

+          ref new EasClientDeviceInformation();

+      std::string operating_system =

+          platformStringToString(current_device_info->OperatingSystem);

+

       AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;

-      std::string platform_str =

+      std::string os_name_and_version =

           starboard::shared::win32::platformStringToString(

-            version_info->DeviceFamily);

-      if (platform_str.empty()) {

+              current_device_info->OperatingSystem);

+      if (os_name_and_version.empty()) {

         return false;

       }

+

+      WindowsVersion os_version;

+      if (!GetWindowsVersion(&os_version)) {

+        return false;

+      }

+

+      os_name_and_version += " ";

+      char os_version_buffer[kOsVersionSize];

+      os_version_buffer[0] = '\0';

+

+      int return_value =

+          SbStringFormatF(os_version_buffer, value_length, "%u.%u",

+                          os_version.major_version, os_version.minor_version);

+      if ((return_value < 0) || (return_value >= value_length)) {

+        return false;

+      }

+

+      os_name_and_version.append(os_version_buffer);

+

       return CopyStringAndTestIfSuccess(out_value, value_length,

-                                        platform_str.c_str());

+                                        os_name_and_version.c_str());

     }

     case kSbSystemPropertyPlatformUuid: {

       SB_NOTIMPLEMENTED();

diff --git a/src/starboard/shared/uwp/winrt_workaround.h b/src/starboard/shared/uwp/winrt_workaround.h
deleted file mode 100644
index e75b766..0000000
--- a/src/starboard/shared/uwp/winrt_workaround.h
+++ /dev/null
@@ -1,31 +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_UWP_WINRT_WORKAROUND_H_
-#define STARBOARD_SHARED_UWP_WINRT_WORKAROUND_H_
-
-namespace __winRT {
-// TODO: without this, we get the following error at CoreApplication::Run:
-// 'long __winRT::__getActivationFactoryByPCWSTR(i
-//  void *,Platform::Guid &,void **)':
-//  cannot convert argument 1 from 'const wchar_t [46]' to 'void *'
-inline long __getActivationFactoryByPCWSTR(const wchar_t* a,
-                                           Platform::Guid& b,
-                                           void** c) {
-  return __getActivationFactoryByPCWSTR(
-      static_cast<void*>(const_cast<wchar_t*>(a)), b, c);
-}
-}  // namespace __winRT
-
-#endif  // STARBOARD_SHARED_UWP_WINRT_WORKAROUND_H_
diff --git a/src/starboard/shared/win32/application_win32.cc b/src/starboard/shared/win32/application_win32.cc
new file mode 100644
index 0000000..956d88b
--- /dev/null
+++ b/src/starboard/shared/win32/application_win32.cc
@@ -0,0 +1,249 @@
+// 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/application_win32.h"
+
+#include <windows.h>  // NOLINT(build/include_order)
+
+#include <cstdio>
+#include <string>
+
+#include "starboard/shared/starboard/application.h"
+#include "starboard/shared/win32/dialog.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/thread_private.h"
+#include "starboard/shared/win32/wchar_utils.h"
+#include "starboard/shared/win32/window_internal.h"
+
+using starboard::shared::starboard::Application;
+using starboard::shared::win32::ApplicationWin32;
+using starboard::shared::win32::CStringToWString;
+using starboard::shared::win32::DebugLogWinError;
+
+namespace {
+
+static const TCHAR kWindowClassName[] = L"window_class_name";
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM w_param, LPARAM l_param) {
+  return ApplicationWin32::Get()->WindowProcess(hWnd, msg, w_param, l_param);
+}
+
+bool RegisterWindowClass() {
+  WNDCLASSEX window_class;
+  window_class.cbSize = sizeof(WNDCLASSEX);
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
+  window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+  window_class.lpfnWndProc = WndProc;
+  window_class.cbClsExtra = 0;
+  window_class.cbWndExtra = 0;
+  window_class.hInstance = GetModuleHandle(nullptr);
+  // TODO: Add YouTube icon.
+  window_class.hIcon = LoadIcon(window_class.hInstance, IDI_APPLICATION);
+  window_class.hIconSm = LoadIcon(window_class.hInstance, IDI_APPLICATION);
+  window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
+  window_class.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+  window_class.lpszMenuName = NULL;
+  window_class.lpszClassName = kWindowClassName;
+
+  if (!::RegisterClassEx(&window_class)) {
+    SB_LOG(ERROR) << "Failed to register window";
+    DebugLogWinError();
+    return false;
+  }
+  return true;
+}
+
+// Create a Windows window.
+HWND CreateWindowInstance(const SbWindowOptions& options) {
+  DWORD dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_CLIPSIBLINGS |
+                  WS_CLIPCHILDREN | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
+  if (options.windowed) {
+    dwStyle |= WS_MAXIMIZE;
+  }
+  const std::wstring wide_window_name = CStringToWString(options.name);
+  const HWND window = CreateWindow(
+      kWindowClassName, wide_window_name.c_str(), dwStyle, CW_USEDEFAULT,
+      CW_USEDEFAULT, options.size.width, options.size.height, nullptr, nullptr,
+      GetModuleHandle(nullptr), nullptr);
+  SetForegroundWindow(window);
+  if (window == nullptr) {
+    SB_LOG(ERROR) << "Failed to create window.";
+    DebugLogWinError();
+  }
+
+  return window;
+}
+
+}  // namespace
+
+// Note that this is a "struct" and not a "class" because
+// that's how it's defined in starboard/system.h
+struct SbSystemPlatformErrorPrivate {
+  SbSystemPlatformErrorPrivate(const SbSystemPlatformErrorPrivate&) = delete;
+  SbSystemPlatformErrorPrivate& operator=(const SbSystemPlatformErrorPrivate&) =
+      delete;
+
+  SbSystemPlatformErrorPrivate(SbSystemPlatformErrorType type,
+                               SbSystemPlatformErrorCallback callback,
+                               void* user_data)
+      : callback_(callback), user_data_(user_data) {
+    if (type != kSbSystemPlatformErrorTypeConnectionError)
+      SB_NOTREACHED();
+
+    ApplicationWin32* app = ApplicationWin32::Get();
+    const bool created_dialog = starboard::shared::win32::ShowOkCancelDialog(
+        app->GetCoreWindow()->GetWindowHandle(),
+        "",  // No title.
+        app->GetLocalizedString("UNABLE_TO_CONTACT_YOUTUBE_1",
+                                "Sorry, could not connect to YouTube."),
+        app->GetLocalizedString("RETRY_BUTTON", "Retry"),
+        [this, callback, user_data]() {
+          callback(kSbSystemPlatformErrorResponsePositive, user_data);
+        },
+        app->GetLocalizedString("EXIT_BUTTON", "Exit"),
+        [this, callback, user_data]() {
+          callback(kSbSystemPlatformErrorResponseNegative, user_data);
+        });
+    SB_DCHECK(!created_dialog);
+    if (!created_dialog) {
+      SB_LOG(ERROR) << "Failed to create dialog!";
+    }
+  }
+
+  void Clear() { starboard::shared::win32::CancelDialog(); }
+
+ private:
+  SbSystemPlatformErrorCallback callback_;
+  void* user_data_;
+};
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+ApplicationWin32::ApplicationWin32()
+    : localized_strings_(SbSystemGetLocaleId()) {}
+ApplicationWin32::~ApplicationWin32() {}
+
+SbWindow ApplicationWin32::CreateWindowForWin32(
+    const SbWindowOptions* options) {
+  if (SbWindowIsValid(window_.get())) {
+    SB_LOG(WARNING) << "Returning existing window instance.";
+    return window_.get();
+  }
+
+  RegisterWindowClass();
+  HWND window;
+  if (options) {
+    window = CreateWindowInstance(*options);
+    window_.reset(new SbWindowPrivate(options, window));
+  } else {
+    SbWindowOptions default_options;
+    SbWindowSetDefaultOptions(&default_options);
+    window = CreateWindowInstance(default_options);
+    window_.reset(new SbWindowPrivate(&default_options, window));
+  }
+  ShowWindow(window, SW_SHOW);
+  UpdateWindow(window);
+  return window_.get();
+}
+
+bool ApplicationWin32::DestroyWindow(SbWindow window) {
+  if (!SbWindowIsValid(window) || window != window_.get()) {
+    return false;
+  }
+  window_.reset();
+  return true;
+}
+
+SbSystemPlatformError ApplicationWin32::OnSbSystemRaisePlatformError(
+    SbSystemPlatformErrorType type,
+    SbSystemPlatformErrorCallback callback,
+    void* user_data) {
+  return new SbSystemPlatformErrorPrivate(type, callback, user_data);
+}
+
+void ApplicationWin32::OnSbSystemClearPlatformError(
+    SbSystemPlatformError handle) {
+  if (handle == kSbSystemPlatformErrorInvalid) {
+    return;
+  }
+  static_cast<SbSystemPlatformErrorPrivate*>(handle)->Clear();
+  // TODO: Determine if this should actually be deleted and if so, delete or
+  // don't consistently across platforms.
+  delete handle;
+}
+
+Application::Event* ApplicationWin32::WaitForSystemEventWithTimeout(
+    SbTime time) {
+  ProcessNextSystemMessage();
+  if (pending_event_) {
+    Event* out = pending_event_;
+    pending_event_ = nullptr;
+    return out;
+  }
+
+  ScopedLock lock(stop_waiting_for_system_events_mutex_);
+  if (time <= SbTimeGetMonotonicNow() || stop_waiting_for_system_events_) {
+    stop_waiting_for_system_events_ = false;
+    return nullptr;
+  }
+
+  return WaitForSystemEventWithTimeout(time);
+}
+
+LRESULT ApplicationWin32::WindowProcess(HWND hWnd,
+                                        UINT msg,
+                                        WPARAM w_param,
+                                        LPARAM l_param) {
+  switch (msg) {
+    // Input message handling.
+    case WM_KEYDOWN:
+    case WM_SYSKEYDOWN:
+    case WM_KEYUP:
+    case WM_SYSKEYUP:
+      // TODO: Listen for mouse events as well.
+      pending_event_ =
+          ProcessWinKeyEvent(GetCoreWindow(), msg, w_param, l_param);
+      break;
+    case WM_DESTROY:
+      SB_LOG(INFO) << "Received destroy message; exiting.";
+      PostQuitMessage(0);
+      break;
+    default:
+      return DefWindowProcW(hWnd, msg, w_param, l_param);
+  }
+  return 0;
+}
+
+bool ApplicationWin32::ProcessNextSystemMessage() {
+  MSG msg;
+  BOOL get_message_return = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
+  if (get_message_return == -1) {
+    SB_LOG(INFO) << "Error while getting messages";
+    return false;
+  }
+  if (!DialogHandleMessage(&msg)) {
+    TranslateMessage(&msg);
+    DispatchMessage(&msg);
+  }
+  if (get_message_return == 0) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/application_win32.h b/src/starboard/shared/win32/application_win32.h
new file mode 100644
index 0000000..18b7af2
--- /dev/null
+++ b/src/starboard/shared/win32/application_win32.h
@@ -0,0 +1,119 @@
+// 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_APPLICATION_WIN32_H_
+#define STARBOARD_SHARED_WIN32_APPLICATION_WIN32_H_
+
+// Windows headers.
+#include <windows.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/localized_strings.h"
+#include "starboard/shared/starboard/queue_application.h"
+#include "starboard/shared/win32/window_internal.h"
+#include "starboard/system.h"
+#include "starboard/window.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class ApplicationWin32 : public starboard::QueueApplication {
+ public:
+  ApplicationWin32();
+  ~ApplicationWin32() SB_OVERRIDE;
+
+  static ApplicationWin32* Get() {
+    return static_cast<ApplicationWin32*>(
+        ::starboard::shared::starboard::Application::Get());
+  }
+
+  SbWindow CreateWindowForWin32(const SbWindowOptions* options);
+
+  bool DestroyWindow(SbWindow window);
+
+  void DispatchStart() { starboard::Application::DispatchStart(); }
+
+  SbWindow GetCoreWindow() {
+    return SbWindowIsValid(window_.get()) ? window_.get() : kSbWindowInvalid;
+  }
+
+  SbSystemPlatformError OnSbSystemRaisePlatformError(
+      SbSystemPlatformErrorType type,
+      SbSystemPlatformErrorCallback callback,
+      void* user_data);
+
+  void OnSbSystemClearPlatformError(SbSystemPlatformError handle);
+
+  std::string GetLocalizedString(const char* id, const char* fallback) const {
+    return localized_strings_.GetString(id, fallback);
+  }
+
+  // Returns true if it is valid to poll/query for system events.
+  bool MayHaveSystemEvents() SB_OVERRIDE { return true; }
+
+  // Waits for an event until the timeout |time| runs out.  If an event occurs
+  // in this time, it is returned, otherwise NULL is returned. If |time| is zero
+  // or negative, then this should function effectively like a no-wait poll.
+  Event* WaitForSystemEventWithTimeout(SbTime time) SB_OVERRIDE;
+
+  // Wakes up any thread waiting within a call to
+  // WaitForSystemEventWithTimeout().
+  void WakeSystemEventWait() SB_OVERRIDE {
+    ScopedLock lock(stop_waiting_for_system_events_mutex_);
+    stop_waiting_for_system_events_ = true;
+  }
+
+  LRESULT WindowProcess(HWND hWnd, UINT msg, WPARAM w_param, LPARAM l_param);
+  VOID TimedEventCallback(PVOID lp, BOOLEAN timer_or_wait_fired);
+
+ private:
+  // --- Application overrides ---
+  bool IsStartImmediate() SB_OVERRIDE { return true; }
+  void Initialize() SB_OVERRIDE {}
+  void Teardown() SB_OVERRIDE {}
+
+  bool ProcessNextSystemMessage();
+  SbTimeMonotonic GetNextTimedEventTargetTime() SB_OVERRIDE {
+    return SbTimeGetMonotonicNow();
+  }
+
+  // Processes window key events, returning a corresponding Event instance.
+  // This transfers ownership of the returned Event.
+  Event* ProcessWinKeyEvent(SbWindow window,
+                            UINT msg,
+                            WPARAM w_param,
+                            LPARAM l_param);
+
+  Event* pending_event_ = nullptr;
+
+  // The single open window, if any.
+  std::unique_ptr<SbWindowPrivate> window_;
+
+  starboard::LocalizedStrings localized_strings_;
+
+  Mutex stop_waiting_for_system_events_mutex_;
+  bool stop_waiting_for_system_events_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_APPLICATION_WIN32_H_
diff --git a/src/starboard/shared/win32/application_win32_key_event.cc b/src/starboard/shared/win32/application_win32_key_event.cc
new file mode 100644
index 0000000..4e02faf
--- /dev/null
+++ b/src/starboard/shared/win32/application_win32_key_event.cc
@@ -0,0 +1,238 @@
+// 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/application_win32.h"
+
+#include <windows.h>
+
+#include "starboard/event.h"
+#include "starboard/input.h"
+#include "starboard/key.h"
+#include "starboard/shared/starboard/application.h"
+
+using starboard::shared::starboard::Application;
+
+namespace {
+
+const int kSbKeyboardDeviceId = 1;
+
+SbKey VirtualKeyCodeToSbKey(WPARAM virtual_key_code) {
+  // Keyboard code reference:
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
+  switch (virtual_key_code) {
+    case VK_CANCEL: return kSbKeyCancel;
+    case VK_BACK: return kSbKeyBack;
+    case VK_TAB: return kSbKeyTab;
+    case VK_CLEAR: return kSbKeyClear;
+    case VK_RETURN: return kSbKeyReturn;
+    case VK_SHIFT: return kSbKeyShift;
+    case VK_CONTROL: return kSbKeyControl;
+    case VK_MENU: return kSbKeyMenu;
+    case VK_PAUSE: return kSbKeyPause;
+    case VK_CAPITAL: return kSbKeyCapital;
+    // Hangul and Kana have the same VirtualKey constant
+    case VK_KANA: return kSbKeyKana;
+    case VK_JUNJA: return kSbKeyJunja;
+    case VK_FINAL: return kSbKeyFinal;
+    // Hanja and Kanji have the same VirtualKey constant
+    case VK_HANJA: return kSbKeyHanja;
+    case VK_ESCAPE: return kSbKeyEscape;
+    case VK_CONVERT: return kSbKeyConvert;
+    case VK_NONCONVERT: return kSbKeyNonconvert;
+    case VK_ACCEPT: return kSbKeyAccept;
+    case VK_MODECHANGE: return kSbKeyModechange;
+    case VK_SPACE: return kSbKeySpace;
+    case VK_PRIOR: return kSbKeyPrior;
+    case VK_NEXT: return kSbKeyNext;
+    case VK_END: return kSbKeyEnd;
+    case VK_HOME: return kSbKeyHome;
+    case VK_LEFT: return kSbKeyLeft;
+    case VK_UP: return kSbKeyUp;
+    case VK_RIGHT: return kSbKeyRight;
+    case VK_DOWN: return kSbKeyDown;
+    case VK_SELECT: return kSbKeySelect;
+    case VK_PRINT: return kSbKeyPrint;
+    case VK_EXECUTE: return kSbKeyExecute;
+    case VK_SNAPSHOT: return kSbKeySnapshot;
+    case VK_INSERT: return kSbKeyInsert;
+    case VK_DELETE: return kSbKeyDelete;
+    case 0x30: return kSbKey0;
+    case 0x31: return kSbKey1;
+    case 0x32: return kSbKey2;
+    case 0x33: return kSbKey3;
+    case 0x34: return kSbKey4;
+    case 0x35: return kSbKey5;
+    case 0x36: return kSbKey6;
+    case 0x37: return kSbKey7;
+    case 0x38: return kSbKey8;
+    case 0x39: return kSbKey9;
+    case 0x41: return kSbKeyA;
+    case 0x42: return kSbKeyB;
+    case 0x43: return kSbKeyC;
+    case 0x44: return kSbKeyD;
+    case 0x45: return kSbKeyE;
+    case 0x46: return kSbKeyF;
+    case 0x47: return kSbKeyG;
+    case 0x48: return kSbKeyH;
+    case 0x49: return kSbKeyI;
+    case 0x4A: return kSbKeyJ;
+    case 0x4B: return kSbKeyK;
+    case 0x4C: return kSbKeyL;
+    case 0x4D: return kSbKeyM;
+    case 0x4E: return kSbKeyN;
+    case 0x4F: return kSbKeyO;
+    case 0x50: return kSbKeyP;
+    case 0x51: return kSbKeyQ;
+    case 0x52: return kSbKeyR;
+    case 0x53: return kSbKeyS;
+    case 0x54: return kSbKeyT;
+    case 0x55: return kSbKeyU;
+    case 0x56: return kSbKeyV;
+    case 0x57: return kSbKeyW;
+    case 0x58: return kSbKeyX;
+    case 0x59: return kSbKeyY;
+    case 0x5A: return kSbKeyZ;
+    case VK_LWIN: return kSbKeyLwin;
+    case VK_RWIN: return kSbKeyRwin;
+    case VK_APPS: return kSbKeyApps;
+    case VK_SLEEP: return kSbKeySleep;
+    case VK_NUMPAD0: return kSbKeyNumpad0;
+    case VK_NUMPAD1: return kSbKeyNumpad1;
+    case VK_NUMPAD2: return kSbKeyNumpad2;
+    case VK_NUMPAD3: return kSbKeyNumpad3;
+    case VK_NUMPAD4: return kSbKeyNumpad4;
+    case VK_NUMPAD5: return kSbKeyNumpad5;
+    case VK_NUMPAD6: return kSbKeyNumpad6;
+    case VK_NUMPAD7: return kSbKeyNumpad7;
+    case VK_NUMPAD8: return kSbKeyNumpad8;
+    case VK_NUMPAD9: return kSbKeyNumpad9;
+    case VK_MULTIPLY: return kSbKeyMultiply;
+    case VK_ADD: return kSbKeyAdd;
+    case VK_SEPARATOR: return kSbKeySeparator;
+    case VK_SUBTRACT: return kSbKeySubtract;
+    case VK_DECIMAL: return kSbKeyDecimal;
+    case VK_DIVIDE: return kSbKeyDivide;
+    case VK_F1: return kSbKeyF1;
+    case VK_F2: return kSbKeyF2;
+    case VK_F3: return kSbKeyF3;
+    case VK_F4: return kSbKeyF4;
+    case VK_F5: return kSbKeyF5;
+    case VK_F6: return kSbKeyF6;
+    case VK_F7: return kSbKeyF7;
+    case VK_F8: return kSbKeyF8;
+    case VK_F9: return kSbKeyF9;
+    case VK_F10: return kSbKeyF10;
+    case VK_F11: return kSbKeyF11;
+    case VK_F12: return kSbKeyF12;
+    case VK_F13: return kSbKeyF13;
+    case VK_F14: return kSbKeyF14;
+    case VK_F15: return kSbKeyF15;
+    case VK_F16: return kSbKeyF16;
+    case VK_F17: return kSbKeyF17;
+    case VK_F18: return kSbKeyF18;
+    case VK_F19: return kSbKeyF19;
+    case VK_F20: return kSbKeyF20;
+    case VK_F21: return kSbKeyF21;
+    case VK_F22: return kSbKeyF22;
+    case VK_F23: return kSbKeyF23;
+    case VK_F24: return kSbKeyF24;
+    case VK_NUMLOCK: return kSbKeyNumlock;
+    case VK_SCROLL: return kSbKeyScroll;
+    case VK_LSHIFT: return kSbKeyLshift;
+    case VK_RSHIFT: return kSbKeyRshift;
+    case VK_LCONTROL: return kSbKeyLcontrol;
+    case VK_RCONTROL: return kSbKeyRcontrol;
+    case VK_LMENU: return kSbKeyLmenu;
+    case VK_RMENU: return kSbKeyRmenu;
+    case VK_BROWSER_BACK: return kSbKeyBrowserBack;
+    case VK_BROWSER_FORWARD: return kSbKeyBrowserForward;
+    case VK_BROWSER_REFRESH: return kSbKeyBrowserRefresh;
+    case VK_BROWSER_STOP: return kSbKeyBrowserStop;
+    case VK_BROWSER_SEARCH: return kSbKeyBrowserSearch;
+    case VK_BROWSER_FAVORITES: return kSbKeyBrowserFavorites;
+    case VK_BROWSER_HOME: return kSbKeyBrowserHome;
+    case VK_LBUTTON: return kSbKeyMouse1;
+    case VK_RBUTTON: return kSbKeyMouse2;
+    case VK_MBUTTON: return kSbKeyMouse3;
+    case VK_XBUTTON1: return kSbKeyMouse4;
+    case VK_XBUTTON2: return kSbKeyMouse5;
+    default:
+      SB_LOG(WARNING) << "Unrecognized key hit.";
+      return kSbKeyUnknown;
+  }
+}
+
+}  // namespace
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// TODO: Plug into XInput APIs for Xbox controller input?
+Application::Event* ApplicationWin32::ProcessWinKeyEvent(SbWindow window,
+                                                         UINT msg,
+                                                         WPARAM w_param,
+                                                         LPARAM l_param) {
+  SbInputData* data = new SbInputData();
+  SbMemorySet(data, 0, sizeof(*data));
+
+  data->window = window;
+  data->device_type = kSbInputDeviceTypeKeyboard;
+  // TODO: Do some more intelligent handling logic here to determine
+  // a unique device ID.
+  data->device_id = kSbKeyboardDeviceId;
+  data->key = VirtualKeyCodeToSbKey(w_param);
+
+  const bool was_down = ((l_param & (1 << 30)) != 0);
+  const bool up = msg != WM_KEYDOWN && msg == WM_SYSKEYDOWN;
+
+  data->type = up ? kSbInputEventTypeUnpress : kSbInputEventTypePress;
+
+  if (data->key == kSbKeyShift || data->key == kSbKeyRshift ||
+      data->key == kSbKeyLshift) {
+    data->key_modifiers |= kSbKeyModifiersShift;
+  } else if (data->key == kSbKeyMenu || data->key == kSbKeyRmenu ||
+             data->key == kSbKeyLmenu) {
+    data->key_modifiers |= kSbKeyModifiersAlt;
+  } else if (data->key == kSbKeyControl || data->key == kSbKeyRcontrol ||
+             data->key == kSbKeyLcontrol) {
+    data->key_modifiers |= kSbKeyModifiersCtrl;
+  } else if (data->key == kSbKeyRwin || data->key == kSbKeyLwin) {
+    data->key_modifiers |= kSbKeyModifiersMeta;
+  }
+
+  switch (data->key) {
+    case kSbKeyLshift:
+    case kSbKeyLmenu:
+    case kSbKeyLcontrol:
+    case kSbKeyLwin:
+      data->key_location = kSbKeyLocationLeft;
+      break;
+    case kSbKeyRshift:
+    case kSbKeyRmenu:
+    case kSbKeyRcontrol:
+    case kSbKeyRwin:
+      data->key_location = kSbKeyLocationRight;
+      break;
+    default:
+      break;
+  }
+
+  return new Application::Event(kSbEventTypeInput, data,
+                                &Application::DeleteDestructor<SbInputData>);
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/atomic_queue.h b/src/starboard/shared/win32/atomic_queue.h
new file mode 100644
index 0000000..676813a
--- /dev/null
+++ b/src/starboard/shared/win32/atomic_queue.h
@@ -0,0 +1,75 @@
+// 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_ATOMIC_QUEUE_H_
+#define STARBOARD_SHARED_WIN32_ATOMIC_QUEUE_H_
+
+#include <deque>
+
+#include "starboard/mutex.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// A simple thread-safe producer / consumer queue. Elements are added via
+// PopBack() and removed via PopFront().
+template <typename Data>
+class AtomicQueue {
+ public:
+  size_t PushBack(Data data_ptr) {
+    ScopedLock lock(mutex_);
+    data_queue_.push_back(data_ptr);
+    return data_queue_.size();
+  }
+
+  Data PopFront() {
+    ScopedLock lock(mutex_);
+    if (data_queue_.empty()) {
+      Data empty = Data();
+      return empty;
+    }
+    Data data_ptr = data_queue_.front();
+    data_queue_.pop_front();
+    return data_ptr;
+  }
+
+  bool IsEmpty() const {
+    ScopedLock lock(mutex_);
+    return data_queue_.empty();
+  }
+
+  size_t Size() const {
+    ScopedLock lock(mutex_);
+    return data_queue_.size();
+  }
+
+  void Clear() {
+    ScopedLock lock(mutex_);
+    std::deque<Data> empty;
+    data_queue_.swap(empty);
+  }
+
+ private:
+  using Mutex = ::starboard::Mutex;
+  using ScopedLock = ::starboard::ScopedLock;
+  std::deque<Data> data_queue_;
+  ::starboard::Mutex mutex_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_ATOMIC_QUEUE_H_
diff --git a/src/starboard/shared/win32/audio_decoder.cc b/src/starboard/shared/win32/audio_decoder.cc
new file mode 100644
index 0000000..d6749f2
--- /dev/null
+++ b/src/starboard/shared/win32/audio_decoder.cc
@@ -0,0 +1,140 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/win32/audio_decoder.h"
+
+#include "starboard/atomic.h"
+#include "starboard/audio_sink.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/shared/win32/media_common.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class AudioDecoder::CallbackScheduler : private JobOwner {
+ public:
+  CallbackScheduler() : callback_signaled_(false) {}
+
+  void SetCallbackOnce(Closure cb) {
+    SB_DCHECK(cb.is_valid());
+    ::starboard::ScopedLock lock(mutex_);
+    if (!cb_.is_valid()) {
+      cb_ = cb;
+    }
+  }
+
+  void ScheduleCallbackIfNecessary() {
+    ::starboard::ScopedLock lock(mutex_);
+    if (!cb_.is_valid() || callback_signaled_) {
+      return;
+    }
+    callback_signaled_ = true;
+    JobOwner::Schedule(cb_);
+  }
+
+  void OnCallbackSignaled() {
+    ::starboard::ScopedLock lock(mutex_);
+    callback_signaled_ = false;
+  }
+
+  Closure cb_;
+  ::starboard::Mutex mutex_;
+  bool callback_signaled_;
+};
+
+AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
+                           const SbMediaAudioHeader& audio_header)
+    : sample_type_(kSbMediaAudioSampleTypeFloat32),
+      stream_ended_(false),
+      audio_codec_(audio_codec),
+      audio_header_(audio_header) {
+  SB_DCHECK(audio_codec == kSbMediaAudioCodecAac);
+  decoder_impl_ = AbstractWin32AudioDecoder::Create(
+      audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+  decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
+  callback_scheduler_.reset(new CallbackScheduler());
+}
+
+AudioDecoder::~AudioDecoder() {
+  decoder_thread_.reset(nullptr);
+  decoder_impl_.reset(nullptr);
+  callback_scheduler_.reset(nullptr);
+}
+
+void AudioDecoder::Decode(const scoped_refptr<InputBuffer>& input_buffer,
+                          const Closure& consumed_cb) {
+  SB_DCHECK(input_buffer);
+  callback_scheduler_->SetCallbackOnce(consumed_cb);
+  callback_scheduler_->OnCallbackSignaled();
+  const bool can_take_more_data = decoder_thread_->QueueInput(input_buffer);
+  if (can_take_more_data) {
+    callback_scheduler_->ScheduleCallbackIfNecessary();
+  }
+
+  if (stream_ended_) {
+    SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
+    return;
+  }
+}
+
+void AudioDecoder::WriteEndOfStream() {
+  ::starboard::ScopedLock lock(mutex_);
+  stream_ended_ = true;
+  decoder_thread_->QueueEndOfStream();
+}
+
+scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read() {
+  DecodedAudioPtr data = decoded_data_.PopFront();
+  SB_DCHECK(data);
+  return data;
+}
+
+void AudioDecoder::Reset() {
+  decoder_thread_.reset(nullptr);
+  decoder_impl_.reset(nullptr);
+  decoder_impl_ = AbstractWin32AudioDecoder::Create(
+      audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+  decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
+  decoded_data_.Clear();
+  stream_ended_ = false;
+  CancelPendingJobs();
+}
+
+SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
+  return sample_type_;
+}
+
+int AudioDecoder::GetSamplesPerSecond() const {
+  return audio_header_.samples_per_second;
+}
+
+void AudioDecoder::Initialize(const Closure& output_cb) {
+  SB_DCHECK(output_cb.is_valid());
+  SB_DCHECK(!output_cb_.is_valid());
+  output_cb_ = output_cb;
+}
+
+void AudioDecoder::OnAudioDecoded(DecodedAudioPtr data) {
+  decoded_data_.PushBack(data);
+  if (output_cb_.is_valid()) {
+    Schedule(output_cb_);
+  }
+  callback_scheduler_->ScheduleCallbackIfNecessary();
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/audio_decoder.h b/src/starboard/shared/win32/audio_decoder.h
new file mode 100644
index 0000000..f036287
--- /dev/null
+++ b/src/starboard/shared/win32/audio_decoder.h
@@ -0,0 +1,81 @@
+// 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_DECODER_H_
+#define STARBOARD_SHARED_WIN32_AUDIO_DECODER_H_
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+#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 {
+namespace win32 {
+
+using JobQueue = ::starboard::shared::starboard::player::JobQueue;
+using JobOwner = JobQueue::JobOwner;
+
+class AudioDecoder
+    : public ::starboard::shared::starboard::player::filter::AudioDecoder,
+      private JobOwner,
+      private AudioDecodedCallback {
+ public:
+  AudioDecoder(SbMediaAudioCodec audio_codec,
+               const SbMediaAudioHeader& audio_header);
+  ~AudioDecoder() SB_OVERRIDE;
+
+  void Decode(const scoped_refptr<InputBuffer>& input_buffer,
+              const Closure& consumed_cb) SB_OVERRIDE;
+  void WriteEndOfStream() SB_OVERRIDE;
+  scoped_refptr<DecodedAudio> Read() SB_OVERRIDE;
+  void Reset() SB_OVERRIDE;
+  SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE;
+  int GetSamplesPerSecond() const SB_OVERRIDE;
+
+  void Initialize(const Closure& output_cb) SB_OVERRIDE;
+  SbMediaAudioFrameStorageType GetStorageType() const SB_OVERRIDE {
+    return kSbMediaAudioFrameStorageTypeInterleaved;
+  }
+  void OnAudioDecoded(DecodedAudioPtr data) SB_OVERRIDE;
+
+ private:
+  class CallbackScheduler;
+  SbMediaAudioHeader audio_header_;
+  SbMediaAudioSampleType sample_type_;
+  SbMediaAudioCodec audio_codec_;
+  bool stream_ended_;
+
+  AtomicQueue<DecodedAudioPtr> decoded_data_;
+  scoped_ptr<AudioDecoder::CallbackScheduler> callback_scheduler_;
+  scoped_ptr<AbstractWin32AudioDecoder> decoder_impl_;
+  scoped_ptr<AudioDecoderThread> decoder_thread_;
+  Closure output_cb_;
+
+  ::starboard::Mutex mutex_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_AUDIO_DECODER_H_
diff --git a/src/starboard/shared/win32/audio_decoder_thread.cc b/src/starboard/shared/win32/audio_decoder_thread.cc
new file mode 100644
index 0000000..91d28fe
--- /dev/null
+++ b/src/starboard/shared/win32/audio_decoder_thread.cc
@@ -0,0 +1,127 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/win32/audio_decoder_thread.h"
+
+#include <deque>
+#include <vector>
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+namespace {
+
+// Size of the queue for audio units.
+const size_t kMaxProcessingElements = 64;
+
+size_t WriteAsMuchAsPossible(
+    std::deque<scoped_refptr<InputBuffer> >* data_queue,
+    AbstractWin32AudioDecoder* audio_decoder) {
+  const size_t original_size = data_queue->size();
+  while (!data_queue->empty()) {
+    scoped_refptr<InputBuffer> buff = data_queue->front();
+    data_queue->pop_front();
+
+    if (buff) {
+      const bool write_ok = audio_decoder->TryWrite(*buff);
+
+      if (!write_ok) {
+        data_queue->push_front(buff);
+        break;
+      }
+    } else {
+      audio_decoder->WriteEndOfStream();
+    }
+  }
+  return original_size - data_queue->size();
+}
+
+std::vector<DecodedAudioPtr> ReadAllDecodedAudioSamples(
+    AbstractWin32AudioDecoder* audio_decoder) {
+  std::vector<DecodedAudioPtr> decoded_audio_out;
+  while (DecodedAudioPtr decoded_datum = audio_decoder->ProcessAndRead()) {
+    decoded_audio_out.push_back(decoded_datum);
+  }
+  return decoded_audio_out;
+}
+
+}  // namespace.
+
+AudioDecoderThread::AudioDecoderThread(AbstractWin32AudioDecoder* decoder_impl,
+                                       AudioDecodedCallback* callback)
+    : SimpleThread("AudioDecoderThread"),
+      win32_audio_decoder_(decoder_impl),
+      callback_(callback) {
+  Start();
+}
+
+AudioDecoderThread::~AudioDecoderThread() {
+  Join();
+}
+
+bool AudioDecoderThread::QueueInput(const scoped_refptr<InputBuffer>& buffer) {
+  {
+    ::starboard::ScopedLock lock(input_buffer_queue_mutex_);
+    input_buffer_queue_.push_back(buffer);
+  }
+
+  // increment() returns the previous value.
+  size_t element_count = processing_elements_.increment() + 1;
+  semaphore_.Put();
+  return element_count < kMaxProcessingElements;
+}
+
+void AudioDecoderThread::QueueEndOfStream() {
+  scoped_refptr<InputBuffer> empty;
+  QueueInput(empty);
+}
+
+void AudioDecoderThread::Run() {
+  std::deque<scoped_refptr<InputBuffer> > local_queue;
+
+  while (!join_called()) {
+    TransferPendingInputTo(&local_queue);
+    bool work_done = !local_queue.empty();
+    size_t number_written =
+        WriteAsMuchAsPossible(&local_queue, win32_audio_decoder_);
+    processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
+
+    std::vector<DecodedAudioPtr> decoded_audio =
+        ReadAllDecodedAudioSamples(win32_audio_decoder_);
+
+    if (!decoded_audio.empty()) {
+      work_done = true;
+      for (auto it = decoded_audio.begin(); it != decoded_audio.end(); ++it) {
+        callback_->OnAudioDecoded(*it);
+      }
+    }
+
+    if (!work_done) {
+      semaphore_.TakeWait(kSbTimeMillisecond);
+    }
+  }
+}
+
+void AudioDecoderThread::TransferPendingInputTo(
+    std::deque<scoped_refptr<InputBuffer> >* destination) {
+  ::starboard::ScopedLock lock(input_buffer_queue_mutex_);
+  while (!input_buffer_queue_.empty()) {
+    destination->push_back(input_buffer_queue_.front());
+    input_buffer_queue_.pop_front();
+  }
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/audio_decoder_thread.h b/src/starboard/shared/win32/audio_decoder_thread.h
new file mode 100644
index 0000000..f595ec4
--- /dev/null
+++ b/src/starboard/shared/win32/audio_decoder_thread.h
@@ -0,0 +1,72 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_AUDIO_DECODER_THREAD_H_
+#define STARBOARD_SHARED_WIN32_AUDIO_DECODER_THREAD_H_
+
+#include <deque>
+#include <queue>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/common/semaphore.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#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 {
+namespace win32 {
+
+class AudioDecodedCallback {
+ public:
+  virtual ~AudioDecodedCallback() {}
+  virtual void OnAudioDecoded(DecodedAudioPtr data) = 0;
+};
+
+// This decoder thread simplifies decoding media. Data is pushed in via
+// QueueInput() and QueueEndOfStream() and output data is pushed via
+// the AudioDecodedCallback.
+class AudioDecoderThread : private SimpleThread {
+ public:
+  AudioDecoderThread(AbstractWin32AudioDecoder* decoder_impl,
+                     AudioDecodedCallback* callback);
+  ~AudioDecoderThread() SB_OVERRIDE;
+
+  // Returns true if more input can be pushed to this thread.
+  bool QueueInput(const scoped_refptr<InputBuffer>& buffer);
+  void QueueEndOfStream();
+
+ private:
+  void Run() SB_OVERRIDE;
+  void TransferPendingInputTo(
+      std::deque<scoped_refptr<InputBuffer> >* destination);
+  AbstractWin32AudioDecoder* win32_audio_decoder_;
+  AudioDecodedCallback* callback_;
+
+  std::deque<scoped_refptr<InputBuffer> > input_buffer_queue_;
+  ::starboard::Mutex input_buffer_queue_mutex_;
+  atomic_int32_t processing_elements_;
+  Semaphore semaphore_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_AUDIO_DECODER_THREAD_H_
diff --git a/src/starboard/shared/win32/audio_sink.cc b/src/starboard/shared/win32/audio_sink.cc
index cc7b530..c5d8de2 100644
--- a/src/starboard/shared/win32/audio_sink.cc
+++ b/src/starboard/shared/win32/audio_sink.cc
@@ -19,6 +19,8 @@
 #include <xaudio2.h>
 
 #include <limits>
+#include <sstream>
+#include <string>
 
 #include "starboard/configuration.h"
 #include "starboard/log.h"
@@ -35,7 +37,14 @@
 }
 
 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 {
@@ -110,16 +119,18 @@
       wfx_(wfx),
       destroying_(false),
       playback_rate_(1.0) {
-  // TODO: Check MaxFrequencyRadio
+  // TODO: Check MaxFrequencyRatio
   CHECK_HRESULT_OK(
       type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
-                                          /*MaxFrequencyRadio = */ 1.0));
+                                          /*MaxFrequencyRatio = */ 1.0));
 
-  CHECK_HRESULT_OK(source_voice_->Start(0));
+  CHECK_HRESULT_OK(source_voice_->Stop(0));
 
-  audio_out_thread_ =
-      SbThreadCreate(0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
-                     "audio_out", &XAudioAudioSink::ThreadEntryPoint, this);
+  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_));
 }
 
@@ -165,6 +176,7 @@
   int submitted_frames = 0;
   uint64_t samples_played = 0;
   int queued_buffers = 0;
+  bool was_playing = false;  // The player starts out playing by default.
   for (;;) {
     {
       ScopedLock lock(mutex_);
@@ -181,6 +193,18 @@
     }
     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
diff --git a/src/starboard/shared/win32/decode_target_internal.cc b/src/starboard/shared/win32/decode_target_internal.cc
new file mode 100644
index 0000000..70b8e32
--- /dev/null
+++ b/src/starboard/shared/win32/decode_target_internal.cc
@@ -0,0 +1,204 @@
+// 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/decode_target_internal.h"
+
+#include <D3D11.h>
+#include <Mfidl.h>
+#include <wrl\client.h>  // For ComPtr.
+
+#include "starboard/configuration.h"
+#include "starboard/decode_target.h"
+#include "starboard/memory.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/media_common.h"
+#include "third_party/angle/include/EGL/egl.h"
+#include "third_party/angle/include/EGL/eglext.h"
+#include "third_party/angle/include/GLES2/gl2.h"
+
+using Microsoft::WRL::ComPtr;
+using starboard::shared::win32::VideoFramePtr;
+using starboard::shared::win32::CheckResult;
+
+// {3C3A43AB-C69B-46C9-AA8D-B0CFFCD4596D}
+static const GUID kCobaltNv12BindChroma = {
+    0x3c3a43ab,
+    0xc69b,
+    0x46c9,
+    {0xaa, 0x8d, 0xb0, 0xcf, 0xfc, 0xd4, 0x59, 0x6d}};
+
+// {C62BF18D-B5EE-46B1-9C31-F61BD8AE3B0D}
+static const GUID kCobaltDxgiBuffer = {
+    0Xc62bf18d,
+    0Xb5ee,
+    0X46b1,
+    {0X9c, 0X31, 0Xf6, 0X1b, 0Xd8, 0Xae, 0X3b, 0X0d}};
+
+SbDecodeTargetPrivate::SbDecodeTargetPrivate(VideoFramePtr f) : frame(f) {
+  SbMemorySet(&info, 0, sizeof(info));
+  ComPtr<IMFMediaBuffer> media_buffer =
+      static_cast<IMFMediaBuffer*>(frame->native_texture());
+
+  ComPtr<IMFDXGIBuffer> dxgi_buffer;
+  HRESULT hr = media_buffer.As(&dxgi_buffer);
+  CheckResult(hr);
+  SB_DCHECK(dxgi_buffer.Get());
+
+  ComPtr<ID3D11Texture2D> d3texture;
+  hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&d3texture));
+  CheckResult(hr);
+
+  UINT array_index;
+  dxgi_buffer->GetSubresourceIndex(&array_index);
+
+  info.format = kSbDecodeTargetFormat2PlaneYUVNV12;
+  info.is_opaque = true;
+
+  D3D11_TEXTURE2D_DESC texture_desc;
+  d3texture->GetDesc(&texture_desc);
+  info.width = texture_desc.Width;
+  info.height = texture_desc.Height;
+
+  SbDecodeTargetInfoPlane* planeY = &(info.planes[kSbDecodeTargetPlaneY]);
+  SbDecodeTargetInfoPlane* planeUV = &(info.planes[kSbDecodeTargetPlaneUV]);
+
+  planeY->width = texture_desc.Width;
+  planeY->height = texture_desc.Height;
+  planeY->content_region.left = 0;
+  planeY->content_region.top = 0;
+  planeY->content_region.right = texture_desc.Width;
+  planeY->content_region.bottom = texture_desc.Height;
+
+  planeUV->width = texture_desc.Width / 2;
+  planeUV->height = texture_desc.Height / 2;
+  planeUV->content_region.left = 0;
+  planeUV->content_region.top = 0;
+  planeUV->content_region.right = texture_desc.Width / 2;
+  planeUV->content_region.bottom = texture_desc.Height / 2;
+
+  EGLint luma_texture_attributes[] = {EGL_WIDTH,
+                                      static_cast<EGLint>(texture_desc.Width),
+                                      EGL_HEIGHT,
+                                      static_cast<EGLint>(texture_desc.Height),
+                                      EGL_TEXTURE_TARGET,
+                                      EGL_TEXTURE_2D,
+                                      EGL_TEXTURE_FORMAT,
+                                      EGL_TEXTURE_RGBA,
+                                      EGL_NONE};
+
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  EGLConfig config;
+  EGLint num_configs;
+  bool ok = eglGetConfigs(display, &config, 1, &num_configs);
+  SB_DCHECK(ok);
+
+  GLuint gl_textures[2] = {0};
+  glGenTextures(2, gl_textures);
+  SB_DCHECK(glGetError() == GL_NO_ERROR);
+
+  // This tells ANGLE that the texture it creates should draw
+  // the luma channel on R8.
+  hr = d3texture->SetPrivateData(kCobaltNv12BindChroma, 0, nullptr);
+  SB_DCHECK(SUCCEEDED(hr));
+
+  // This lets ANGLE find out the subresource index / texture array index
+  // to use.
+  // Note: No AddRef here, since we clear this private data below.
+  hr = d3texture->SetPrivateData(kCobaltDxgiBuffer, sizeof(IMFDXGIBuffer*),
+                                 dxgi_buffer.GetAddressOf());
+  SB_DCHECK(SUCCEEDED(hr));
+
+  EGLSurface surface = eglCreatePbufferFromClientBuffer(
+      display, EGL_D3D_TEXTURE_ANGLE, d3texture.Get(), config,
+      luma_texture_attributes);
+
+  SB_DCHECK(surface != EGL_NO_SURFACE);
+
+  glBindTexture(GL_TEXTURE_2D, gl_textures[0]);
+  SB_DCHECK(glGetError() == GL_NO_ERROR);
+
+  ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+  SB_DCHECK(ok);
+
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  planeY->texture = gl_textures[0];
+  planeY->gl_texture_target = GL_TEXTURE_2D;
+
+  // This tells ANGLE that the texture it creates should draw
+  // the chroma channel on R8G8.
+  bool bind_chroma = true;
+  hr = d3texture->SetPrivateData(kCobaltNv12BindChroma, 1, &bind_chroma);
+  SB_DCHECK(SUCCEEDED(hr));
+
+  EGLint chroma_texture_attributes[] = {
+      EGL_WIDTH,
+      static_cast<EGLint>(texture_desc.Width) / 2,
+      EGL_HEIGHT,
+      static_cast<EGLint>(texture_desc.Height) / 2,
+      EGL_TEXTURE_TARGET,
+      EGL_TEXTURE_2D,
+      EGL_TEXTURE_FORMAT,
+      EGL_TEXTURE_RGBA,
+      EGL_NONE};
+  surface = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
+                                             d3texture.Get(), config,
+                                             chroma_texture_attributes);
+
+  SB_DCHECK(surface != EGL_NO_SURFACE);
+
+  glBindTexture(GL_TEXTURE_2D, gl_textures[1]);
+  SB_DCHECK(glGetError() == GL_NO_ERROR);
+
+  ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+  SB_DCHECK(ok);
+
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  planeUV->texture = gl_textures[1];
+  planeUV->gl_texture_target = GL_TEXTURE_2D;
+
+  hr = d3texture->SetPrivateData(kCobaltDxgiBuffer, 0, nullptr);
+  SB_DCHECK(SUCCEEDED(hr));
+}
+
+SbDecodeTargetPrivate::~SbDecodeTargetPrivate() {
+  glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneY].texture));
+  glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneUV].texture));
+}
+
+void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
+  if (SbDecodeTargetIsValid(decode_target)) {
+    delete decode_target;
+  }
+}
+
+SbDecodeTargetFormat SbDecodeTargetGetFormat(SbDecodeTarget decode_target) {
+  // Note that kSbDecodeTargetFormat2PlaneYUVNV12 represents DXGI_FORMAT_NV12.
+  SB_DCHECK(kSbDecodeTargetFormat2PlaneYUVNV12 ==
+            decode_target->info.format);
+  return decode_target->info.format;
+}
+
+bool SbDecodeTargetGetInfo(SbDecodeTarget decode_target,
+                           SbDecodeTargetInfo* out_info) {
+  if (!out_info || !SbMemoryIsZero(out_info, sizeof(*out_info))) {
+    SB_DCHECK(false) << "out_info must be zeroed out.";
+    return false;
+  }
+  SbMemoryCopy(out_info, &decode_target->info, sizeof(*out_info));
+  return true;
+}
diff --git a/src/starboard/shared/win32/decode_target_internal.h b/src/starboard/shared/win32/decode_target_internal.h
new file mode 100644
index 0000000..6204200
--- /dev/null
+++ b/src/starboard/shared/win32/decode_target_internal.h
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_DECODE_TARGET_INTERNAL_H_
+#define STARBOARD_SHARED_WIN32_DECODE_TARGET_INTERNAL_H_
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/decode_target.h"
+#include "starboard/shared/win32/media_common.h"
+
+struct SbDecodeTargetPrivate {
+  // Publicly accessible information about the decode target.
+  SbDecodeTargetInfo info;
+  ::starboard::shared::win32::VideoFramePtr frame;
+  explicit SbDecodeTargetPrivate(starboard::shared::win32::VideoFramePtr frame);
+  ~SbDecodeTargetPrivate();
+};
+
+#endif  // STARBOARD_SHARED_WIN32_DECODE_TARGET_INTERNAL_H_
diff --git a/src/starboard/shared/win32/dialog.cc b/src/starboard/shared/win32/dialog.cc
new file mode 100644
index 0000000..7ac5ae0
--- /dev/null
+++ b/src/starboard/shared/win32/dialog.cc
@@ -0,0 +1,252 @@
+// 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/dialog.h"
+
+#include <windef.h>
+#include <windows.h>
+#include <windowsx.h>
+
+#include <functional>
+#include <vector>
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/wchar_utils.h"
+
+typedef std::function<void()> DialogCallback;
+
+using starboard::shared::win32::DebugLogWinError;
+using starboard::shared::win32::CStringToWString;
+
+namespace {
+HWND g_current_dialog_handle = nullptr;
+DialogCallback g_ok_callback;
+DialogCallback g_cancel_callback;
+}  // namespace
+
+// Wraps the win32 Dialog interface for building Dialogs at runtime.
+// https://blogs.msdn.microsoft.com/oldnewthing/20050429-00/?p=35743
+class DialogTemplateBuilder {
+ public:
+  LPCDLGTEMPLATE BuildTemplate() { return (LPCDLGTEMPLATE)&v[0]; }
+  void AlignToDword() {
+    if (v.size() % 4)
+      Write(NULL, 4 - (v.size() % 4));
+  }
+  void Write(LPCVOID pvWrite, DWORD cbWrite) {
+    v.insert(v.end(), cbWrite, 0);
+    if (pvWrite)
+      CopyMemory(&v[v.size() - cbWrite], pvWrite, cbWrite);
+  }
+  template <typename T>
+  void Write(T t) {
+    Write(&t, sizeof(T));
+  }
+  void WriteString(LPCWSTR psz) {
+    Write(psz, (lstrlenW(psz) + 1) * sizeof(WCHAR));
+  }
+
+ private:
+  std::vector<BYTE> v;
+};
+
+INT_PTR CALLBACK DialogProcedureCallback(HWND dialog_handle,
+                                         UINT message,
+                                         WPARAM w_param,
+                                         LPARAM /*l_param*/) {
+  SB_CHECK(!g_current_dialog_handle || dialog_handle == g_current_dialog_handle)
+      << "Received callback on non-active dialog! Only one dialog at a time is "
+         "supported.";
+  switch (message) {
+    case WM_INITDIALOG:
+      return TRUE;
+    case WM_COMMAND:
+      auto command_id = GET_WM_COMMAND_ID(w_param, l_param);
+      if (command_id == IDCANCEL) {
+        g_cancel_callback();
+      } else if (command_id == IDOK) {
+        g_ok_callback();
+      } else {
+        return FALSE;
+      }
+      EndDialog(dialog_handle, 0);
+      g_current_dialog_handle = nullptr;
+      return TRUE;
+  }
+  return FALSE;
+}
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+bool ShowOkCancelDialog(HWND hwnd,
+                        const std::string& title,
+                        const std::string& message,
+                        const std::string& ok_message,
+                        DialogCallback ok_callback,
+                        const std::string& cancel_message,
+                        DialogCallback cancel_callback) {
+  if (g_current_dialog_handle != nullptr) {
+    SB_LOG(WARNING) << "Already showing a dialog; cancelling existing and "
+                       "replacing with new dialog";
+    CancelDialog();
+  }
+  g_ok_callback = ok_callback;
+  g_cancel_callback = cancel_callback;
+  // Get the device context (DC) and from the system so we can scale our fonts
+  // correctly.
+  HDC hdc = GetDC(NULL);
+  SB_CHECK(hdc);
+  NONCLIENTMETRICSW ncm = {sizeof(ncm)};
+  bool retrieved_system_params =
+      SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
+
+  if (!retrieved_system_params) {
+    DebugLogWinError();
+    ReleaseDC(NULL, hdc);
+    return false;
+  }
+  DialogTemplateBuilder dialog_template;
+  const int help_id = 0;
+  const int extended_style = 0;
+
+  const int window_width = 200;
+  const int window_height = 80;
+  const int window_x = 32;
+  const int window_y = 32;
+
+  const int edge_padding = 7;
+
+  const int button_height = 14;
+  const int button_width = 50;
+  const int button_y = window_height - button_height - edge_padding;
+  const int left_button_x = window_width / 2 - button_width - edge_padding / 2;
+  const int right_button_x = window_width / 2 + edge_padding / 2;
+  const int text_width = window_width - edge_padding * 2;
+  const int text_height = window_width - edge_padding * 3 - button_height;
+  const int extra_data = 0;
+
+  // Create a dialog template.
+  // The following MSDN blogposts explains how this is all laid out:
+  // https://blogs.msdn.microsoft.com/oldnewthing/20040623-00/?p=38753
+  // https://blogs.msdn.microsoft.com/oldnewthing/20050429-00/?p=35743
+  // More official documentation:
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644996(v=vs.85).aspx#modeless_box
+  dialog_template.Write<WORD>(1);       // dialog version
+  dialog_template.Write<WORD>(0xFFFF);  // extended dialog template
+  dialog_template.Write<DWORD>(help_id);
+  dialog_template.Write<DWORD>(extended_style);
+  dialog_template.Write<DWORD>(WS_CAPTION | WS_SYSMENU | DS_SETFONT |
+                               DS_MODALFRAME);
+  dialog_template.Write<WORD>(3);  // number of controls
+  dialog_template.Write<WORD>(window_x);
+  dialog_template.Write<WORD>(window_y);
+  dialog_template.Write<WORD>(window_width);
+  dialog_template.Write<WORD>(window_height);
+  dialog_template.WriteString(L"");  // no menu
+  dialog_template.WriteString(L"");  // default dialog class
+  // Title.
+  dialog_template.WriteString(
+      (LPCWSTR)starboard::shared::win32::CStringToWString(title.c_str())
+          .c_str());
+
+  // See following for info on how the font styling is calculated:
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173(v=vs.85).aspx
+  if (ncm.lfMessageFont.lfHeight < 0) {
+    ncm.lfMessageFont.lfHeight =
+        -MulDiv(ncm.lfMessageFont.lfHeight, 72, GetDeviceCaps(hdc, LOGPIXELSY));
+  }
+  dialog_template.Write<WORD>((WORD)ncm.lfMessageFont.lfHeight);
+  dialog_template.Write<WORD>((WORD)ncm.lfMessageFont.lfWeight);
+  dialog_template.Write<BYTE>(ncm.lfMessageFont.lfItalic);
+  dialog_template.Write<BYTE>(ncm.lfMessageFont.lfCharSet);
+  dialog_template.WriteString(ncm.lfMessageFont.lfFaceName);
+
+  // Message text.
+  dialog_template.AlignToDword();
+  dialog_template.Write<DWORD>(help_id);
+  dialog_template.Write<DWORD>(extended_style);
+  dialog_template.Write<DWORD>(WS_CHILD | WS_VISIBLE);
+  dialog_template.Write<WORD>(edge_padding);
+  dialog_template.Write<WORD>(edge_padding);
+  dialog_template.Write<WORD>(text_width);
+  dialog_template.Write<WORD>(text_height);
+  dialog_template.Write<DWORD>((DWORD)-1);
+  dialog_template.Write<DWORD>(0x0082FFFF);
+  dialog_template.WriteString(
+      (LPCWSTR)starboard::shared::win32::CStringToWString(message.c_str())
+          .c_str());
+  dialog_template.Write<WORD>(extra_data);
+
+  // Cancel button.
+  dialog_template.AlignToDword();
+  dialog_template.Write<DWORD>(help_id);
+  dialog_template.Write<DWORD>(extended_style);
+  dialog_template.Write<DWORD>(WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
+                               BS_DEFPUSHBUTTON);
+  dialog_template.Write<WORD>(left_button_x);
+  dialog_template.Write<WORD>(button_y);
+  dialog_template.Write<WORD>(button_width);
+  dialog_template.Write<WORD>(button_height);
+  dialog_template.Write<DWORD>(IDCANCEL);
+  dialog_template.Write<DWORD>(0x0080FFFF);
+  dialog_template.WriteString(
+      (LPCWSTR)starboard::shared::win32::CStringToWString(
+          cancel_message.c_str())
+          .c_str());
+  dialog_template.Write<WORD>(extra_data);
+
+  // Ok button.
+  dialog_template.AlignToDword();
+  dialog_template.Write<DWORD>(help_id);
+  dialog_template.Write<DWORD>(extended_style);
+  dialog_template.Write<DWORD>(WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
+                               BS_DEFPUSHBUTTON);  // style
+  dialog_template.Write<WORD>(right_button_x);
+  dialog_template.Write<WORD>(button_y);
+  dialog_template.Write<WORD>(button_width);
+  dialog_template.Write<WORD>(button_height);
+  dialog_template.Write<DWORD>(IDOK);
+  dialog_template.Write<DWORD>(0x0080FFFF);
+  dialog_template.WriteString(
+      (LPCWSTR)starboard::shared::win32::CStringToWString(ok_message.c_str())
+          .c_str());
+  dialog_template.Write<WORD>(extra_data);
+
+  ReleaseDC(NULL, hdc);
+  // Template is ready - go display it.
+  g_current_dialog_handle = CreateDialogIndirect(
+      GetModuleHandle(nullptr), dialog_template.BuildTemplate(), hwnd,
+      DialogProcedureCallback);
+  ShowWindow(g_current_dialog_handle, SW_SHOW);
+  return g_current_dialog_handle != nullptr;
+}
+
+bool DialogHandleMessage(MSG* msg) {
+  return IsWindow(g_current_dialog_handle) &&
+         IsDialogMessage(g_current_dialog_handle, msg);
+}
+
+void CancelDialog() {
+  if (g_current_dialog_handle != nullptr) {
+    EndDialog(g_current_dialog_handle, 0);
+    g_current_dialog_handle = nullptr;
+  }
+}
+
+}  //  namespace win32
+}  //  namespace shared
+}  //  namespace starboard
diff --git a/src/starboard/shared/win32/dialog.h b/src/starboard/shared/win32/dialog.h
new file mode 100644
index 0000000..74adf29
--- /dev/null
+++ b/src/starboard/shared/win32/dialog.h
@@ -0,0 +1,48 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_DIALOG_H_
+#define STARBOARD_SHARED_WIN32_DIALOG_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <string>
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+typedef std::function<void()> DialogCallback;
+
+// Shows a modeless OK/Cancel-style dialog. Only one dialog may be shown at a
+// time.
+bool ShowOkCancelDialog(HWND hwnd,
+                        const std::string& title,
+                        const std::string& message,
+                        const std::string& ok_message,
+                        DialogCallback ok_callback,
+                        const std::string& cancel_message,
+                        DialogCallback cancel_callback);
+
+// Cancels the current dialog that is showing, if there is one.
+void CancelDialog();
+
+bool DialogHandleMessage(MSG* msg);
+
+}  //  namespace win32
+}  //  namespace shared
+}  //  namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_DIALOG_H_
diff --git a/src/starboard/shared/win32/dx_context_video_decoder.cc b/src/starboard/shared/win32/dx_context_video_decoder.cc
new file mode 100644
index 0000000..677a133
--- /dev/null
+++ b/src/starboard/shared/win32/dx_context_video_decoder.cc
@@ -0,0 +1,72 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/win32/dx_context_video_decoder.h"
+
+#include <D3D11.h>
+#include <D3D11_4.h>
+#include <mfapi.h>
+
+#include "third_party/angle/include/EGL/egl.h"
+#include "third_party/angle/include/EGL/eglext.h"
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+HardwareDecoderContext GetDirectXForHardwareDecoding() {
+  HRESULT result = S_OK;
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  PFNEGLQUERYDISPLAYATTRIBEXTPROC query_display;
+
+  query_display = reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(
+      eglGetProcAddress("eglQueryDisplayAttribEXT"));
+  SB_DCHECK(query_display != nullptr);
+
+  PFNEGLQUERYDEVICEATTRIBEXTPROC query_device;
+  query_device = reinterpret_cast<PFNEGLQUERYDEVICEATTRIBEXTPROC>(
+      eglGetProcAddress("eglQueryDeviceAttribEXT"));
+  SB_DCHECK(query_display != nullptr);
+
+  intptr_t egl_device = 0;
+  query_display(display, EGL_DEVICE_EXT, &egl_device);
+  SB_DCHECK(egl_device != 0);
+
+  intptr_t device;
+  query_device(reinterpret_cast<EGLDeviceEXT>(egl_device),
+      EGL_D3D11_DEVICE_ANGLE, &device);
+
+  ID3D11Device* output_dx_device = reinterpret_cast<ID3D11Device*>(device);
+  IMFDXGIDeviceManager* dxgi_device_mgr = nullptr;
+
+  UINT token = 0;
+  result = MFCreateDXGIDeviceManager(&token, &dxgi_device_mgr);
+  SB_DCHECK(result == S_OK);
+  SB_DCHECK(dxgi_device_mgr);
+
+  result = dxgi_device_mgr->ResetDevice(output_dx_device, token);
+  SB_DCHECK(SUCCEEDED(result));
+
+  HardwareDecoderContext output = {
+    output_dx_device,
+    dxgi_device_mgr
+  };
+  return output;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/dx_context_video_decoder.h b/src/starboard/shared/win32/dx_context_video_decoder.h
new file mode 100644
index 0000000..fddf4ea
--- /dev/null
+++ b/src/starboard/shared/win32/dx_context_video_decoder.h
@@ -0,0 +1,38 @@
+// 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_DX_CONTEXT_VIDEO_DECODER_H_
+#define STARBOARD_SHARED_WIN32_DX_CONTEXT_VIDEO_DECODER_H_
+
+#include <wrl\client.h>  // For ComPtr.
+
+struct ID3D11Device;
+struct ID3D11DeviceContext;
+struct IMFDXGIDeviceManager;
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+struct HardwareDecoderContext {
+  Microsoft::WRL::ComPtr<ID3D11Device> dx_device_out;
+  Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> dxgi_device_manager_out;
+};
+
+HardwareDecoderContext GetDirectXForHardwareDecoding();
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+#endif  // STARBOARD_SHARED_WIN32_DX_CONTEXT_VIDEO_DECODER_H_
diff --git a/src/starboard/shared/win32/file_internal.cc b/src/starboard/shared/win32/file_internal.cc
index e6f7927..816483b 100644
--- a/src/starboard/shared/win32/file_internal.cc
+++ b/src/starboard/shared/win32/file_internal.cc
@@ -117,10 +117,6 @@
   }
 
   const DWORD last_error = GetLastError();
-  if (!starboard::shared::win32::IsValidHandle(file_handle)) {
-    SB_DLOG(INFO) << "CreateFile2 failed for " << path << ":"
-                  << sbwin32::Win32ErrorCode(last_error);
-  }
 
   if (out_error) {
     if (starboard::shared::win32::IsValidHandle(file_handle)) {
diff --git a/src/starboard/shared/win32/get_home_directory.cc b/src/starboard/shared/win32/get_home_directory.cc
index aa4272f..52423d5 100644
--- a/src/starboard/shared/win32/get_home_directory.cc
+++ b/src/starboard/shared/win32/get_home_directory.cc
@@ -12,19 +12,33 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <objbase.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+
+// Windows defines GetCommandLine as a macro to GetCommandLineW which
+// breaks our Application::GetCommandLine call below; thus we undefine
+// it after including our Windows headers.
+#undef GetCommandLine
+
+#include <cstdlib>
+#include <cstring>
 #include <string>
 
 #include "starboard/log.h"
 #include "starboard/shared/nouser/user_internal.h"
-#include "starboard/shared/uwp/winrt_workaround.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/command_line.h"
+#include "starboard/shared/win32/application_win32.h"
+#include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/wchar_utils.h"
 #include "starboard/string.h"
 #include "starboard/system.h"
 
-using Windows::Storage::ApplicationData;
-
 namespace sbwin32 = starboard::shared::win32;
 
+using starboard::shared::starboard::CommandLine;
+
 namespace starboard {
 namespace shared {
 namespace nouser {
@@ -33,10 +47,46 @@
   if (user != SbUserGetCurrent()) {
     return false;
   }
-  std::string home_directory =
-      sbwin32::platformStringToString(
-          ApplicationData::Current->LocalFolder->Path);
-  return SbStringCopy(out_path, home_directory.c_str(), path_size);
+
+  PWSTR local_app_data_path = nullptr;
+
+  if (S_OK == SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
+                                   &local_app_data_path)) {
+    TCHAR wide_path[MAX_PATH];
+    wcscpy(wide_path, local_app_data_path);
+    CoTaskMemFree(local_app_data_path);
+    // Instead of using the raw local AppData directory, we create a program
+    // app directory if it doesn't exist already.
+    CommandLine* command_line =
+        sbwin32::ApplicationWin32::Get()->GetCommandLine();
+    SB_DCHECK(command_line);
+    const std::string program_name = command_line->argv()[0];
+    PathAppend(
+        wide_path,
+        sbwin32::CStringToWString(program_name.c_str()).c_str());
+    if (!PathFileExists(wide_path)) {
+      SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
+                                                 NULL, TRUE};
+      const BOOL created_directory =
+          CreateDirectory(wide_path, &security_attributes);
+      if (!created_directory) {
+        SB_LOG(ERROR) << "Failed to create home directory";
+        sbwin32::DebugLogWinError();
+        return false;
+      }
+    }
+
+    const size_t actual_path_length = wcslen(wide_path);
+    if (path_size < actual_path_length) {
+      SB_LOG(ERROR) << "Home directory length exceeds max path size";
+      return false;
+    }
+    std::wcstombs(out_path, wide_path, actual_path_length + 1);
+    return true;
+  }
+  SB_LOG(ERROR) << "Unable to open local AppData as home directory.";
+  sbwin32::DebugLogWinError();
+  return false;
 }
 
 }  // namespace nouser
diff --git a/src/starboard/shared/win32/media_common.cc b/src/starboard/shared/win32/media_common.cc
new file mode 100644
index 0000000..3eca93e
--- /dev/null
+++ b/src/starboard/shared/win32/media_common.cc
@@ -0,0 +1,101 @@
+// 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_common.h"
+
+#include <Mfapi.h>
+#include <Mferror.h>
+#include <Mfidl.h>
+#include <Mfobjects.h>
+#include <Rpc.h>
+#include <comutil.h>
+#include <wrl\client.h>  // For ComPtr.
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/player/closure.h"
+#include "starboard/shared/starboard/player/filter/player_components.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// Converts 90khz to 10Mhz (100ns time).
+int64_t ConvertToWin32Time(SbMediaTime input) {
+  int64_t out = input;
+  out *= 1000;
+  out /= 9;
+  return out;
+}
+
+// Convert the other way around.
+SbMediaTime ConvertToMediaTime(int64_t input) {
+  SbMediaTime out = input;
+  out *= 9;
+  out /= 1000;
+  return out;
+}
+
+std::vector<ComPtr<IMFMediaType>> GetAllOutputMediaTypes(
+    IMFTransform* decoder) {
+  std::vector<ComPtr<IMFMediaType>> output;
+  for (int index = 0;; ++index) {
+    ComPtr<IMFMediaType> media_type;
+    HRESULT hr = decoder->GetOutputAvailableType(0, index, &media_type);
+    if (SUCCEEDED(hr)) {
+      output.push_back(media_type);
+    } else {
+      SB_DCHECK(hr == MF_E_NO_MORE_TYPES);
+      break;
+    }
+  }
+  return output;
+}
+
+std::vector<ComPtr<IMFMediaType>> FilterMediaBySubType(
+    const std::vector<ComPtr<IMFMediaType>>& input,
+    GUID sub_type_filter) {
+  std::vector<ComPtr<IMFMediaType>> output;
+  for (auto it = input.begin(); it != input.end(); ++it) {
+    ComPtr<IMFMediaType> media_type = *it;
+    GUID media_sub_type = {0};
+    media_type->GetGUID(MF_MT_SUBTYPE, &media_sub_type);
+    if (IsEqualGUID(media_sub_type, sub_type_filter)) {
+      output.push_back(media_type);
+    }
+  }
+  return output;
+}
+
+ComPtr<IMFMediaType> FindMediaType(GUID sub_type, IMFTransform* decoder) {
+  std::vector<ComPtr<IMFMediaType>> media_types =
+      GetAllOutputMediaTypes(decoder);
+  media_types = FilterMediaBySubType(media_types, sub_type);
+  if (media_types.empty()) {
+    ComPtr<IMFMediaType> empty;
+    return empty;
+  } else {
+    return media_types[0];
+  }
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/media_common.h b/src/starboard/shared/win32/media_common.h
new file mode 100644
index 0000000..59212ee
--- /dev/null
+++ b/src/starboard/shared/win32/media_common.h
@@ -0,0 +1,74 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_MEDIA_COMMON_H_
+#define STARBOARD_SHARED_WIN32_MEDIA_COMMON_H_
+
+#include <Mfapi.h>
+#include <Mferror.h>
+#include <Mfidl.h>
+#include <Mfobjects.h>
+#include <Rpc.h>
+#include <comutil.h>
+#include <wrl\client.h>  // For ComPtr.
+#include <vector>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/player/closure.h"
+#include "starboard/shared/starboard/player/filter/player_components.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+using Closure = ::starboard::shared::starboard::player::Closure;
+using DecodedAudio = ::starboard::shared::starboard::player::DecodedAudio;
+using DecodedAudioPtr = ::starboard::scoped_refptr<DecodedAudio>;
+using InputBuffer = ::starboard::shared::starboard::player::InputBuffer;
+using PlayerComponents =
+    ::starboard::shared::starboard::player::filter::PlayerComponents;
+using Status =
+    ::starboard::shared::starboard::player::filter::HostedVideoDecoder::Status;
+using VideoFrame = ::starboard::shared::starboard::player::VideoFrame;
+using VideoFramePtr = ::starboard::scoped_refptr<VideoFrame>;
+using VideoParameters =
+    ::starboard::shared::starboard::player::filter::VideoParameters;
+using Microsoft::WRL::ComPtr;
+
+// Converts 90khz to 10Mhz (100ns time).
+int64_t ConvertToWin32Time(SbMediaTime input);
+
+// Convert the other way around.
+SbMediaTime ConvertToMediaTime(int64_t input);
+
+std::vector<ComPtr<IMFMediaType>> GetAllOutputMediaTypes(IMFTransform* decoder);
+
+std::vector<ComPtr<IMFMediaType>> FilterMediaBySubType(
+    const std::vector<ComPtr<IMFMediaType>>& input,
+    GUID sub_type_filter);
+
+ComPtr<IMFMediaType> FindMediaType(GUID sub_type, IMFTransform* decoder);
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_MEDIA_COMMON_H_
diff --git a/src/starboard/shared/win32/media_foundation_utils.cc b/src/starboard/shared/win32/media_foundation_utils.cc
new file mode 100644
index 0000000..08e9f03
--- /dev/null
+++ b/src/starboard/shared/win32/media_foundation_utils.cc
@@ -0,0 +1,191 @@
+// 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_foundation_utils.h"
+
+#include <Mfapi.h>
+#include <Mferror.h>
+#include <propvarutil.h>
+
+#include <ios>
+#include <sstream>
+#include <utility>
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/error_utils.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+using ::starboard::shared::win32::CheckResult;
+
+namespace {
+
+#define MAKE_GUID_PAIR(X) std::pair<GUID, std::string>(X, #X)
+
+const std::pair<GUID, std::string> kMfMtAudio[] = {
+  MAKE_GUID_PAIR(MF_MT_AAC_PAYLOAD_TYPE),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_AVG_BYTES_PER_SECOND),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_BITS_PER_SAMPLE),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_BLOCK_ALIGNMENT),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_CHANNEL_MASK),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_FLOAT_SAMPLES_PER_SECOND),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_NUM_CHANNELS),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_SAMPLES_PER_BLOCK),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_SAMPLES_PER_SECOND),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_NUM_CHANNELS),
+  MAKE_GUID_PAIR(MF_MT_MAJOR_TYPE),
+  MAKE_GUID_PAIR(MF_MT_AUDIO_PREFER_WAVEFORMATEX),
+  MAKE_GUID_PAIR(MF_MT_USER_DATA),
+  MAKE_GUID_PAIR(MF_MT_SUBTYPE),
+  MAKE_GUID_PAIR(MFAudioFormat_AAC),
+  MAKE_GUID_PAIR(MFAudioFormat_ADTS),
+  MAKE_GUID_PAIR(MFAudioFormat_ALAC),
+  MAKE_GUID_PAIR(MFAudioFormat_AMR_NB),
+  MAKE_GUID_PAIR(MFAudioFormat_AMR_WB),
+  MAKE_GUID_PAIR(MFAudioFormat_AMR_WP),
+  MAKE_GUID_PAIR(MFAudioFormat_Dolby_AC3),
+  MAKE_GUID_PAIR(MFAudioFormat_Dolby_AC3_SPDIF),
+  MAKE_GUID_PAIR(MFAudioFormat_Dolby_DDPlus),
+  MAKE_GUID_PAIR(MFAudioFormat_DRM),
+  MAKE_GUID_PAIR(MFAudioFormat_DTS),
+  MAKE_GUID_PAIR(MFAudioFormat_FLAC),
+  MAKE_GUID_PAIR(MFAudioFormat_Float),
+  MAKE_GUID_PAIR(MFAudioFormat_Float_SpatialObjects),
+  MAKE_GUID_PAIR(MFAudioFormat_MP3),
+  MAKE_GUID_PAIR(MFAudioFormat_MPEG),
+  MAKE_GUID_PAIR(MFAudioFormat_MSP1),
+  MAKE_GUID_PAIR(MFAudioFormat_Opus),
+  MAKE_GUID_PAIR(MFAudioFormat_PCM),
+  MAKE_GUID_PAIR(MFAudioFormat_WMASPDIF),
+  MAKE_GUID_PAIR(MFAudioFormat_WMAudio_Lossless),
+  MAKE_GUID_PAIR(MFAudioFormat_WMAudioV8),
+  MAKE_GUID_PAIR(MFAudioFormat_WMAudioV9),
+  MAKE_GUID_PAIR(MFAudioFormat_WMAudioV9),
+  MAKE_GUID_PAIR(MFMediaType_Audio),
+};
+#undef MAKE_GUID_PAIR
+
+std::string GuidToFallbackString(GUID guid) {
+  std::stringstream ss;
+  wchar_t* guid_str = nullptr;
+  StringFromCLSID(guid, &guid_str);
+  ss << guid_str;
+  CoTaskMemFree(guid_str);
+  return ss.str();
+}
+
+std::string MfGuidToString(GUID guid) {
+  const size_t n = sizeof(kMfMtAudio) / sizeof(*kMfMtAudio);
+  for (auto i = 0; i < n; ++i) {
+    const auto& elems = kMfMtAudio[i];
+    if (guid == elems.first) {
+      return elems.second;
+    }
+  }
+  return GuidToFallbackString(guid);
+}
+
+std::string ImfAttributesToString(IMFAttributes* type) {
+  std::stringstream ss;
+  UINT32 n = 0;
+  HRESULT hr = type->GetCount(&n);
+  CheckResult(hr);
+  for (UINT32 i = 0; i < n; ++i) {
+    GUID key;
+    PROPVARIANT val;
+    type->GetItemByIndex(i, &key, &val);
+
+    MF_ATTRIBUTE_TYPE attrib_type;
+    hr = type->GetItemType(key, &attrib_type);
+    CheckResult(hr);
+
+    std::string key_str = MfGuidToString(key);
+    ss << key_str << ": ";
+
+    switch (attrib_type) {
+      case MF_ATTRIBUTE_GUID: {
+        GUID value_guid;
+        hr = type->GetGUID(key, &value_guid);
+        ss << MfGuidToString(value_guid) << "\n";
+        break;
+      }
+
+      case MF_ATTRIBUTE_DOUBLE: {
+        double value = 0;
+        hr = type->GetDouble(key, &value);
+        ss << value << "\n";
+        break;
+      }
+
+      case MF_ATTRIBUTE_BLOB: {
+        // Skip.
+        ss << "<BLOB>" << "\n";
+        break;
+      }
+
+      case MF_ATTRIBUTE_UINT32: {
+        UINT32 int_val = 0;
+        hr = type->GetUINT32(key, &int_val);
+        ss << int_val << "\n";
+        break;
+      }
+
+      case MF_ATTRIBUTE_UINT64: {
+        UINT64 int_val = 0;
+        hr = type->GetUINT64(key, &int_val);
+        ss << int_val << "\n";
+        break;
+      }
+
+      case MF_ATTRIBUTE_STRING: {
+        wchar_t buff[128];
+        UINT buff_size = sizeof(buff);
+        hr = type->GetString(key, buff, buff_size, NULL);
+        CheckResult(hr);
+        ss << buff << "\n";
+        break;
+      }
+      default: {
+        SB_NOTIMPLEMENTED();
+        break;
+      }
+    }
+  }
+  ss << "\n";
+  return ss.str();
+}
+
+}  // namespace.
+
+void CopyUint32Property(GUID key, const IMFMediaType* source,
+                        IMFMediaType* destination) {
+  UINT32 val = 0;
+  const_cast<IMFMediaType*>(source)->GetUINT32(key, &val);
+  HRESULT hr = destination->SetUINT32(key, val);
+  CheckResult(hr);
+}
+
+std::ostream& operator<<(std::ostream& os, const IMFMediaType& media_type) {
+  const IMFAttributes* attribs = &media_type;  // Upcast.
+  std::string output_str =
+      ImfAttributesToString(const_cast<IMFAttributes*>(attribs));
+  os << output_str;
+  return os;
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/media_foundation_utils.h b/src/starboard/shared/win32/media_foundation_utils.h
new file mode 100644
index 0000000..088cc5c
--- /dev/null
+++ b/src/starboard/shared/win32/media_foundation_utils.h
@@ -0,0 +1,38 @@
+// 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 <wrl/client.h>
+#include <Mfobjects.h>
+
+#include <string>
+
+#ifndef STARBOARD_SHARED_WIN32_MEDIA_FOUNDATION_UTILS_H_
+#define STARBOARD_SHARED_WIN32_MEDIA_FOUNDATION_UTILS_H_
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+std::ostream& operator<<(std::ostream& os, const IMFMediaType& media_type);
+
+std::string ToString(IMFAttributes* type);
+
+void CopyUint32Property(GUID key, const IMFMediaType* source,
+                        IMFMediaType* destination);
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_MEDIA_FOUNDATION_UTILS_H_
diff --git a/src/starboard/shared/win32/media_is_audio_supported.cc b/src/starboard/shared/win32/media_is_audio_supported.cc
new file mode 100644
index 0000000..1c6936d
--- /dev/null
+++ b/src/starboard/shared/win32/media_is_audio_supported.cc
@@ -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.
+
+#include "starboard/shared/starboard/media/media_support_internal.h"
+
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+
+SB_EXPORT bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
+                                       int64_t bitrate) {
+  // TODO: Add Opus.
+  if (audio_codec != kSbMediaAudioCodecAac) {
+    return false;
+  }
+  return bitrate <= SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND;
+}
diff --git a/src/starboard/shared/win32/media_is_supported.cc b/src/starboard/shared/win32/media_is_supported.cc
new file mode 100644
index 0000000..127f625
--- /dev/null
+++ b/src/starboard/shared/win32/media_is_supported.cc
@@ -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.
+
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/string.h"
+
+// TODO: Fill this in for DRM.
+SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec,
+                                  SbMediaAudioCodec audio_codec,
+                                  const char* key_system) {
+  SB_UNREFERENCED_PARAMETER(video_codec);
+  SB_UNREFERENCED_PARAMETER(audio_codec);
+  SB_UNREFERENCED_PARAMETER(key_system);
+  return 0 == SbStringCompareAll(key_system, "com.youtube.playready");
+}
diff --git a/src/starboard/shared/win32/media_is_video_supported.cc b/src/starboard/shared/win32/media_is_video_supported.cc
new file mode 100644
index 0000000..8236694
--- /dev/null
+++ b/src/starboard/shared/win32/media_is_video_supported.cc
@@ -0,0 +1,41 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/media_support_internal.h"
+
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+
+SB_EXPORT bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
+                                       int frame_width,
+                                       int frame_height,
+                                       int64_t bitrate,
+                                       int fps) {
+  // Is resolution out of range?
+  if (frame_width > 1920 || frame_height > 1080) {
+    return false;
+  }
+
+  // Is bitrate in range?
+  if (bitrate > SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND) {
+    return false;
+  }
+
+  if (fps > 60) {
+    return false;
+  }
+
+  // If this is changed then so should win32_decoder_impl.cc
+  return (video_codec == kSbMediaVideoCodecH264);
+}
diff --git a/src/starboard/shared/win32/player_components_impl.cc b/src/starboard/shared/win32/player_components_impl.cc
new file mode 100644
index 0000000..cc86d10
--- /dev/null
+++ b/src/starboard/shared/win32/player_components_impl.cc
@@ -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.
+
+#include "starboard/shared/starboard/player/filter/player_components.h"
+
+#include "starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_impl_internal.h"
+#include "starboard/shared/win32/audio_decoder.h"
+#include "starboard/shared/win32/video_decoder.h"
+#include "starboard/shared/win32/video_renderer.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// static
+scoped_ptr<PlayerComponents> PlayerComponents::Create(
+    const AudioParameters& audio_parameters,
+    const VideoParameters& video_parameters) {
+  using AudioDecoderImpl = ::starboard::shared::win32::AudioDecoder;
+  using VideoDecoderImpl = ::starboard::shared::win32::VideoDecoder;
+  using VideoRendererImpl = ::starboard::shared::win32::VideoRendererImpl;
+
+  AudioDecoderImpl* audio_decoder = new AudioDecoderImpl(
+      audio_parameters.audio_codec, audio_parameters.audio_header);
+
+  VideoDecoderImpl* video_decoder = new VideoDecoderImpl(video_parameters);
+
+  AudioRendererImpl* audio_renderer =
+      new AudioRendererImpl(scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
+                            audio_parameters.audio_header);
+
+  VideoRendererImpl* video_renderer =
+      new VideoRendererImpl(scoped_ptr<VideoDecoderImpl>(video_decoder).Pass());
+
+  return scoped_ptr<PlayerComponents>(
+      new PlayerComponents(audio_renderer, video_renderer));
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/simple_thread.cc b/src/starboard/shared/win32/simple_thread.cc
new file mode 100644
index 0000000..3d75149
--- /dev/null
+++ b/src/starboard/shared/win32/simple_thread.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "starboard/shared/win32/simple_thread.h"
+
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+SimpleThread::SimpleThread(const std::string& name)
+    : thread_(kSbThreadInvalid), name_(name), join_called_(false) {}
+
+SimpleThread::~SimpleThread() {
+  SB_DCHECK(join_called_.load()) << "Join not called on thread.";
+}
+
+void SimpleThread::Start() {
+  SbThreadEntryPoint entry_point = ThreadEntryPoint;
+
+  thread_ = SbThreadCreate(0,                    // default stack_size.
+                           kSbThreadNoPriority,  // default priority.
+                           kSbThreadNoAffinity,  // default affinity.
+                           true,                 // joinable.
+                           name_.c_str(), entry_point, this);
+
+  // SbThreadCreate() above produced an invalid thread handle.
+  SB_DCHECK(thread_ != kSbThreadInvalid);
+  return;
+}
+
+void SimpleThread::Sleep(SbTime microseconds) {
+  SbThreadSleep(microseconds);
+}
+
+void SimpleThread::SleepMilliseconds(int value) {
+  return Sleep(value * kSbTimeMillisecond);
+}
+
+void* SimpleThread::ThreadEntryPoint(void* context) {
+  SimpleThread* this_ptr = static_cast<SimpleThread*>(context);
+  this_ptr->Run();
+  return NULL;
+}
+
+void SimpleThread::Join() {
+  SB_DCHECK(join_called_.load() == false);
+  join_called_.store(true);
+  if (!SbThreadJoin(thread_, NULL)) {
+    SB_DCHECK(false) << "Could not join thread.";
+  }
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/simple_thread.h b/src/starboard/shared/win32/simple_thread.h
new file mode 100644
index 0000000..82c8bab
--- /dev/null
+++ b/src/starboard/shared/win32/simple_thread.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 STARBOARD_SHARED_WIN32_SIMPLE_THREAD_H_
+#define STARBOARD_SHARED_WIN32_SIMPLE_THREAD_H_
+
+#include <string>
+
+#include "starboard/atomic.h"
+#include "starboard/thread.h"
+#include "starboard/time.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class SimpleThread {
+ public:
+  explicit SimpleThread(const std::string& name);
+  virtual ~SimpleThread();
+
+  // Subclasses should override the Run method.
+  virtual void Run() = 0;
+
+  // Signals to the thread to break out of it's loop.
+  virtual void Cancel() {}
+
+  // Called by the main thread, this will cause Run() to be invoked
+  // on another thread.
+  void Start();
+  // Destroys the threads resources.
+  void Join();
+
+  bool join_called() const { return join_called_.load(); }
+
+ protected:
+  static void Sleep(SbTime microseconds);
+  static void SleepMilliseconds(int value);
+
+ private:
+  static void* ThreadEntryPoint(void* context);
+
+  const std::string name_;
+  SbThread thread_;
+  atomic_bool join_called_;
+
+  SB_DISALLOW_COPY_AND_ASSIGN(SimpleThread);
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_SIMPLE_THREAD_H_
diff --git a/src/starboard/shared/win32/starboard_main.cc b/src/starboard/shared/win32/starboard_main.cc
new file mode 100644
index 0000000..ccc3552
--- /dev/null
+++ b/src/starboard/shared/win32/starboard_main.cc
@@ -0,0 +1,58 @@
+// 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/starboard_main.h"
+
+#include <Objbase.h>
+#include <WinSock2.h>
+#include <mfapi.h>
+#include <windows.h>
+
+#include <cstdio>
+
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
+#include "starboard/shared/win32/application_win32.h"
+#include "starboard/shared/win32/thread_private.h"
+
+using starboard::shared::win32::ApplicationWin32;
+
+extern "C" int StarboardMain(int argc, char** argv) {
+  WSAData wsaData;
+  const int kWinSockVersionMajor = 2;
+  const int kWinSockVersionMinor = 2;
+  const int init_result = WSAStartup(
+      MAKEWORD(kWinSockVersionMajor, kWinSockVersionMajor), &wsaData);
+
+  SB_CHECK(init_result == 0);
+  // WSAStartup returns the highest version that is supported up to the version
+  // we request.
+  SB_CHECK(LOBYTE(wsaData.wVersion) == kWinSockVersionMajor &&
+           HIBYTE(wsaData.wVersion) == kWinSockVersionMinor);
+
+  // Initialize COM for XAudio2 APIs.
+  CoInitialize(nullptr);
+  SbAudioSinkPrivate::Initialize();
+  starboard::shared::win32::RegisterMainThread();
+
+  // TODO: Do this with SbOnce when media is first used instead.
+  HRESULT hr = MFStartup(MF_VERSION);
+  SB_DCHECK(SUCCEEDED(hr));
+
+  ApplicationWin32 application;
+  // This will run the message loop.
+  const int main_return_value = application.Run(argc, argv);
+  MFShutdown();
+  WSACleanup();
+  return main_return_value;
+}
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/starboard_main.h
similarity index 70%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/starboard_main.h
index 51f81a1..7d19ee0 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/starboard_main.h
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_SHARED_WIN32_STARBOARD_MAIN_H_
+#define STARBOARD_SHARED_WIN32_STARBOARD_MAIN_H_
 
-#include "starboard/shared/win32/atomic_public.h"
+#include "starboard/export.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+extern "C" SB_EXPORT_PLATFORM int StarboardMain(int argc, char** argv);
+
+#endif  // STARBOARD_SHARED_WIN32_STARBOARD_MAIN_H_
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/system_clear_platform_error.cc
similarity index 69%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/system_clear_platform_error.cc
index 51f81a1..12521bb 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/system_clear_platform_error.cc
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include "starboard/shared/win32/application_win32.h"
+#include "starboard/system.h"
 
-#include "starboard/shared/win32/atomic_public.h"
+using starboard::shared::win32::ApplicationWin32;
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+void SbSystemClearPlatformError(SbSystemPlatformError handle) {
+  ApplicationWin32::Get()->OnSbSystemClearPlatformError(handle);
+}
diff --git a/src/starboard/shared/win32/system_get_device_type.cc b/src/starboard/shared/win32/system_get_device_type.cc
index 0957c7a..fc51f58 100644
--- a/src/starboard/shared/win32/system_get_device_type.cc
+++ b/src/starboard/shared/win32/system_get_device_type.cc
@@ -17,23 +17,8 @@
 #include <string>
 
 #include "starboard/log.h"
-#include "starboard/shared/uwp/winrt_workaround.h"
 #include "starboard/shared/win32/wchar_utils.h"
 
-using Windows::System::Profile::AnalyticsInfo;
-using Windows::System::Profile::AnalyticsVersionInfo;
-
 SbSystemDeviceType SbSystemGetDeviceType() {
-  AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;
-  std::string family = starboard::shared::win32::platformStringToString(
-      version_info->DeviceFamily);
-
-  if (family.compare("Windows.Desktop") == 0) {
-    return kSbSystemDeviceTypeDesktopPC;
-  }
-  if (family.compare("Windows.Xbox") == 0) {
-    return kSbSystemDeviceTypeGameConsole;
-  }
-  SB_NOTREACHED();
-  return kSbSystemDeviceTypeUnknown;
+  return kSbSystemDeviceTypeDesktopPC;
 }
diff --git a/src/starboard/shared/win32/system_get_property.cc b/src/starboard/shared/win32/system_get_property.cc
new file mode 100644
index 0000000..30e9522
--- /dev/null
+++ b/src/starboard/shared/win32/system_get_property.cc
@@ -0,0 +1,69 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/log.h"
+#include "starboard/string.h"
+#include "starboard/system.h"
+
+namespace {
+
+const char* kFriendlyName = "Windows Desktop";
+const char* kPlatformName = "win32; Windows x86_64";
+
+bool CopyStringAndTestIfSuccess(char* out_value,
+                                int value_length,
+                                const char* from_value) {
+  if (SbStringGetLength(from_value) + 1 > value_length)
+    return false;
+  SbStringCopy(out_value, from_value, value_length);
+  return true;
+}
+
+}  // namespace
+
+bool SbSystemGetProperty(SbSystemPropertyId property_id,
+                         char* out_value,
+                         int value_length) {
+  if (!out_value || !value_length) {
+    return false;
+  }
+
+  switch (property_id) {
+    case kSbSystemPropertyBrandName:
+    case kSbSystemPropertyChipsetModelNumber:
+    case kSbSystemPropertyFirmwareVersion:
+    case kSbSystemPropertyModelName:
+    case kSbSystemPropertyModelYear:
+    case kSbSystemPropertyNetworkOperatorName:
+    case kSbSystemPropertySpeechApiKey:
+      return false;
+
+    case kSbSystemPropertyFriendlyName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
+
+    case kSbSystemPropertyPlatformName:
+      return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName);
+
+    case kSbSystemPropertyPlatformUuid:
+      SB_NOTIMPLEMENTED();
+      return CopyStringAndTestIfSuccess(out_value, value_length, "N/A");
+
+    default:
+      SB_DLOG(WARNING) << __FUNCTION__
+                       << ": Unrecognized property: " << property_id;
+      break;
+  }
+
+  return false;
+}
diff --git a/src/starboard/shared/win32/system_raise_platform_error.cc b/src/starboard/shared/win32/system_raise_platform_error.cc
new file mode 100644
index 0000000..e53b245
--- /dev/null
+++ b/src/starboard/shared/win32/system_raise_platform_error.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/win32/application_win32.h"
+#include "starboard/system.h"
+
+using starboard::shared::win32::ApplicationWin32;
+
+SbSystemPlatformError SbSystemRaisePlatformError(
+    SbSystemPlatformErrorType type,
+    SbSystemPlatformErrorCallback callback,
+    void* user_data) {
+  return ApplicationWin32::Get()->OnSbSystemRaisePlatformError(type, callback,
+                                                               user_data);
+}
diff --git a/src/starboard/shared/win32/video_decoder.cc b/src/starboard/shared/win32/video_decoder.cc
new file mode 100644
index 0000000..40fd85d
--- /dev/null
+++ b/src/starboard/shared/win32/video_decoder.cc
@@ -0,0 +1,113 @@
+// 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/video_decoder.h"
+
+#include "starboard/decode_target.h"
+#include "starboard/log.h"
+#include "starboard/shared/win32/decode_target_internal.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "starboard/shared/win32/media_common.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+VideoDecoder::VideoDecoder(const VideoParameters& params)
+    : video_codec_(params.video_codec),
+      host_(NULL),
+      output_mode_(params.output_mode),
+      decode_target_graphics_context_provider_(
+          params.decode_target_graphics_context_provider) {
+  impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+  video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
+}
+
+VideoDecoder::~VideoDecoder() {
+  video_decoder_thread_.reset(nullptr);
+  impl_.reset(nullptr);
+}
+
+void VideoDecoder::SetHost(Host* host) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(host != NULL);
+  SB_DCHECK(host_ == NULL);
+  host_ = host;
+}
+
+void VideoDecoder::WriteInputBuffer(
+    const scoped_refptr<InputBuffer>& input_buffer) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(input_buffer);
+  SB_DCHECK(host_);
+  const bool can_accept_more_input =
+      video_decoder_thread_->QueueInput(input_buffer);
+
+  if (can_accept_more_input) {
+    host_->OnDecoderStatusUpdate(kNeedMoreInput, NULL);
+  }
+}
+
+void VideoDecoder::WriteEndOfStream() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(host_);
+  scoped_refptr<InputBuffer> empty;
+  video_decoder_thread_->QueueInput(empty);
+}
+
+void VideoDecoder::Reset() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+  SB_DCHECK(host_);
+  video_decoder_thread_.reset(nullptr);
+  impl_.reset(nullptr);
+  impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+  video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
+}
+
+// When in decode-to-texture mode, this returns the current decoded video frame.
+SbDecodeTarget VideoDecoder::GetCurrentDecodeTarget() {
+  SB_NOTIMPLEMENTED()
+      << "VideoRendererImpl::GetCurrentDecodeTarget() should be used instead.";
+  return kSbDecodeTargetInvalid;
+}
+
+void VideoDecoder::OnVideoDecoded(VideoFramePtr data) {
+  Status sts = data->IsEndOfStream() ? kBufferFull : kNeedMoreInput;
+  host_->OnDecoderStatusUpdate(sts, data);
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// static
+bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
+                                       SbMediaVideoCodec codec,
+                                       SbDrmSystem drm_system) {
+  SB_UNREFERENCED_PARAMETER(codec);
+  SB_UNREFERENCED_PARAMETER(drm_system);
+  return output_mode == kSbPlayerOutputModeDecodeToTexture;
+}
+
+}  // namespace filter
+}  // namespace player
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/video_decoder.h b/src/starboard/shared/win32/video_decoder.h
new file mode 100644
index 0000000..1fd2bfa
--- /dev/null
+++ b/src/starboard/shared/win32/video_decoder.h
@@ -0,0 +1,78 @@
+// 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_VIDEO_DECODER_H_
+#define STARBOARD_SHARED_WIN32_VIDEO_DECODER_H_
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/configuration.h"
+#include "starboard/shared/starboard/player/filter/player_components.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/shared/starboard/thread_checker.h"
+#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 {
+namespace win32 {
+
+class VideoDecoder
+    : public ::starboard::shared::starboard::player::filter::HostedVideoDecoder,
+      private ::starboard::shared::starboard::player::JobQueue::JobOwner,
+      private VideoDecodedCallback {
+ public:
+  explicit VideoDecoder(const VideoParameters& params);
+  ~VideoDecoder() SB_OVERRIDE;
+
+  void SetHost(Host* host) SB_OVERRIDE;
+  void WriteInputBuffer(const scoped_refptr<InputBuffer>& input_buffer)
+      SB_OVERRIDE;
+  void WriteEndOfStream() SB_OVERRIDE;
+  void Reset() SB_OVERRIDE;
+
+  SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
+
+  // Implements class VideoDecodedCallback.
+  void OnVideoDecoded(VideoFramePtr data) SB_OVERRIDE;
+
+ private:
+  // 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_;
+  Host* host_;
+
+  // Decode-to-texture related state.
+  SbPlayerOutputMode output_mode_;
+
+  SbDecodeTargetGraphicsContextProvider*
+      decode_target_graphics_context_provider_;
+
+  scoped_ptr<AbstractWin32VideoDecoder> impl_;
+  AtomicQueue<VideoFramePtr> decoded_data_;
+  ::starboard::Mutex mutex_;
+  scoped_ptr<VideoDecoderThread> video_decoder_thread_;
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_VIDEO_DECODER_H_
diff --git a/src/starboard/shared/win32/video_decoder_thread.cc b/src/starboard/shared/win32/video_decoder_thread.cc
new file mode 100644
index 0000000..64a7b2e
--- /dev/null
+++ b/src/starboard/shared/win32/video_decoder_thread.cc
@@ -0,0 +1,128 @@
+// 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/video_decoder_thread.h"
+
+#include <deque>
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+namespace {
+const size_t kMaxSize = 16;
+
+size_t WriteAsMuchAsPossible(
+    std::deque<scoped_refptr<InputBuffer> >* data_queue,
+    AbstractWin32VideoDecoder* video_decoder,
+    bool* is_end_of_stream_reached) {
+  const size_t original_size = data_queue->size();
+  while (!data_queue->empty()) {
+    scoped_refptr<InputBuffer> buff = data_queue->front();
+    data_queue->pop_front();
+
+    if (buff) {
+      const bool write_ok = video_decoder->TryWrite(buff);
+
+      if (!write_ok) {
+        data_queue->push_front(buff);
+        break;
+      }
+    } else {
+      video_decoder->WriteEndOfStream();
+      *is_end_of_stream_reached = true;
+    }
+  }
+  return original_size - data_queue->size();
+}
+
+}  // namespace.
+
+VideoDecoderThread::VideoDecoderThread(AbstractWin32VideoDecoder* decoder_impl,
+                                       VideoDecodedCallback* callback)
+    : SimpleThread("VideoDecoderThread"),
+      win32_video_decoder_(decoder_impl),
+      callback_(callback) {
+  Start();
+}
+
+VideoDecoderThread::~VideoDecoderThread() {
+  Join();
+  SB_DCHECK(join_called());
+}
+
+bool VideoDecoderThread::QueueInput(const scoped_refptr<InputBuffer>& buffer) {
+  {
+    ::starboard::ScopedLock lock(input_buffer_queue_mutex_);
+    input_buffer_queue_.push_back(buffer);
+  }
+
+  // increment() returns the prev value.
+  size_t proc_size = processing_elements_.increment() + 1;
+  semaphore_.Put();
+  return proc_size < kMaxSize;
+}
+
+void VideoDecoderThread::QueueEndOfStream() {
+  scoped_refptr<InputBuffer> empty;
+  QueueInput(empty);
+}
+
+void VideoDecoderThread::TransferPendingInputTo(
+    std::deque<scoped_refptr<InputBuffer> >* output) {
+  // Transfer input buffer to local thread.
+  ::starboard::ScopedLock lock(input_buffer_queue_mutex_);
+  while (!input_buffer_queue_.empty()) {
+    output->push_back(input_buffer_queue_.front());
+    input_buffer_queue_.pop_front();
+  }
+}
+
+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();
+    bool is_end_of_stream = false;
+    const size_t number_written =
+        WriteAsMuchAsPossible(&local_queue, win32_video_decoder_,
+                              &is_end_of_stream);
+    if (number_written > 0) {
+      processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
+      work_done = true;
+    }
+
+    while (VideoFramePtr decoded_datum =
+             win32_video_decoder_->ProcessAndRead()) {
+      if (decoded_datum.get()) {
+        callback_->OnVideoDecoded(decoded_datum);
+      }
+      work_done = true;
+    }
+
+    if (is_end_of_stream) {
+      callback_->OnVideoDecoded(VideoFrame::CreateEOSFrame());
+      work_done = true;
+    }
+
+    if (!work_done) {
+      semaphore_.TakeWait(kSbTimeMillisecond);
+    }
+  }
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/video_decoder_thread.h b/src/starboard/shared/win32/video_decoder_thread.h
new file mode 100644
index 0000000..0077756
--- /dev/null
+++ b/src/starboard/shared/win32/video_decoder_thread.h
@@ -0,0 +1,73 @@
+// 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_VIDEO_DECODER_THREAD_H_
+#define STARBOARD_SHARED_WIN32_VIDEO_DECODER_THREAD_H_
+
+#include <deque>
+#include <queue>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/semaphore.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#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 {
+namespace shared {
+namespace win32 {
+
+// This class receives decoded video frames.
+class VideoDecodedCallback {
+ public:
+  virtual ~VideoDecodedCallback() {}
+  virtual void OnVideoDecoded(VideoFramePtr data) = 0;
+};
+
+// This decoder thread simplifies decoding media. Data is pushed in via
+// QueueInput() and QueueEndOfStream() and output data is pushed via
+// the AudioDecodedCallback.
+class VideoDecoderThread : private SimpleThread {
+ public:
+  VideoDecoderThread(AbstractWin32VideoDecoder* decoder_impl,
+                     VideoDecodedCallback* callback);
+  ~VideoDecoderThread() SB_OVERRIDE;
+
+  // Returns true if more input can be pushed to this thread.
+  bool QueueInput(const scoped_refptr<InputBuffer>& buffer);
+  void QueueEndOfStream();
+
+ private:
+  void TransferPendingInputTo(std::deque<scoped_refptr<InputBuffer> >* output);
+  void Run() SB_OVERRIDE;
+  AbstractWin32VideoDecoder* win32_video_decoder_;
+  VideoDecodedCallback* callback_;
+  std::deque<scoped_refptr<InputBuffer> > input_buffer_queue_;
+  ::starboard::Mutex input_buffer_queue_mutex_;
+  atomic_int32_t processing_elements_;
+  Semaphore semaphore_;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_VIDEO_DECODER_THREAD_H_
diff --git a/src/starboard/shared/win32/video_renderer.cc b/src/starboard/shared/win32/video_renderer.cc
new file mode 100644
index 0000000..832dd4e
--- /dev/null
+++ b/src/starboard/shared/win32/video_renderer.cc
@@ -0,0 +1,42 @@
+// 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/video_renderer.h"
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/decode_target_internal.h"
+#include "starboard/shared/win32/error_utils.h"
+#include "third_party/angle/include/EGL/egl.h"
+#include "third_party/angle/include/EGL/eglext.h"
+#include "third_party/angle/include/GLES2/gl2.h"
+
+using starboard::scoped_refptr;
+using Microsoft::WRL::ComPtr;
+using starboard::shared::win32::CheckResult;
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+SbDecodeTarget VideoRendererImpl::GetCurrentDecodeTarget() {
+  VideoFramePtr last_frame = Base::GetLastDisplayedFrame();
+  if (!last_frame || last_frame->IsEndOfStream()) {
+    return kSbDecodeTargetInvalid;
+  }
+  return new SbDecodeTargetPrivate(last_frame);
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/video_renderer.h b/src/starboard/shared/win32/video_renderer.h
new file mode 100644
index 0000000..c0e97a4
--- /dev/null
+++ b/src/starboard/shared/win32/video_renderer.h
@@ -0,0 +1,48 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_VIDEO_RENDERER_H_
+#define STARBOARD_SHARED_WIN32_VIDEO_RENDERER_H_
+
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_impl_internal.h"
+#include "starboard/shared/win32/decode_target_internal.h"
+#include "starboard/shared/win32/video_decoder.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class VideoRendererImpl
+    : public ::starboard::shared::starboard::player::filter::VideoRendererImpl {
+ public:
+  using Base =
+      ::starboard::shared::starboard::player::filter::VideoRendererImpl;
+  using HostedVideoDecoder =
+      ::starboard::shared::starboard::player::filter::HostedVideoDecoder;
+
+  using VideoDecoder = ::starboard::shared::win32::VideoDecoder;
+
+  explicit VideoRendererImpl(scoped_ptr<VideoDecoder> decoder)
+      : Base(decoder.PassAs<HostedVideoDecoder>()) {}
+
+  SbDecodeTarget GetCurrentDecodeTarget() SB_OVERRIDE;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_VIDEO_RENDERER_H_
diff --git a/src/starboard/shared/win32/win32_audio_decoder.cc b/src/starboard/shared/win32/win32_audio_decoder.cc
new file mode 100644
index 0000000..0b85f30
--- /dev/null
+++ b/src/starboard/shared/win32/win32_audio_decoder.cc
@@ -0,0 +1,293 @@
+// 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_audio_decoder.h"
+
+#include <algorithm>
+
+#include "starboard/shared/win32/atomic_queue.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;
+
+std::vector<ComPtr<IMFMediaType>> Filter(
+    GUID subtype_guid, const std::vector<ComPtr<IMFMediaType>>& input) {
+  std::vector<ComPtr<IMFMediaType>> output;
+  for (size_t i = 0; i < input.size(); ++i) {
+    ComPtr<IMFMediaType> curr = input[i];
+    GUID guid_value;
+    HRESULT hr = curr->GetGUID(MF_MT_SUBTYPE, &guid_value);
+    CheckResult(hr);
+    if (subtype_guid == guid_value) {
+      output.push_back(curr);
+    }
+  }
+
+  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) {
+    WAVEFORMATEX* wave_format = WaveFormatTexPtr();
+    wave_format->nAvgBytesPerSec = audio_header.average_bytes_per_second;
+    wave_format->nBlockAlign = audio_header.block_alignment;
+    wave_format->nChannels = audio_header.number_of_channels;
+    wave_format->nSamplesPerSec = audio_header.samples_per_second;
+    wave_format->wBitsPerSample = audio_header.bits_per_sample;
+    wave_format->wFormatTag = audio_header.format_tag;
+
+    // TODO: Investigate this more.
+    wave_format->cbSize = kAudioExtraFormatBytes;
+    std::uint8_t* audio_specific_config = AudioSpecificConfigPtr();
+
+    // These are hard-coded audio specif audio configuration.
+    // Use |SbMediaAudioHeader::audio_specific_config| instead.
+    SB_DCHECK(kAudioExtraFormatBytes == 2);
+    // TODO: What do these values do?
+    audio_specific_config[0] = 0x12;
+    audio_specific_config[1] = 0x10;
+  }
+  WAVEFORMATEX* WaveFormatTexPtr() {
+    return reinterpret_cast<WAVEFORMATEX*>(full_structure);
+  }
+  uint8_t* AudioSpecificConfigPtr() {
+    return full_structure + sizeof(WAVEFORMATEX);
+  }
+
+  UINT32 Size() const {
+    return sizeof(full_structure);
+  }
+
+ private:
+  static const UINT32 kAudioExtraFormatBytes = 2;
+  uint8_t full_structure[sizeof(WAVEFORMATEX) + kAudioExtraFormatBytes];
+};
+
+class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder,
+                                    public MediaBufferConsumerInterface {
+ public:
+  AbstractWin32AudioDecoderImpl(SbMediaAudioCodec codec,
+                              SbMediaAudioFrameStorageType audio_frame_fmt,
+                              SbMediaAudioSampleType sample_type,
+                              const SbMediaAudioHeader& audio_header)
+      : 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));
+    EnsureAudioDecoderCreated();
+  }
+
+  static GUID ConvertToWin32AudioCodec(SbMediaAudioCodec codec) {
+    switch (codec) {
+      case kSbMediaAudioCodecNone: { return MFAudioFormat_PCM; }
+      case kSbMediaAudioCodecAac: { return MFAudioFormat_AAC; }
+      case kSbMediaAudioCodecOpus: { return MFAudioFormat_Opus; }
+      case kSbMediaAudioCodecVorbis: {
+        SB_NOTIMPLEMENTED();
+      }
+    }
+    return MFAudioFormat_PCM;
+  }
+
+  virtual void Consume(ComPtr<IMFMediaBuffer> media_buffer,
+                       int64_t win32_timestamp) {
+    BYTE* buffer;
+    DWORD length;
+    HRESULT hr = media_buffer->Lock(&buffer, NULL, &length);
+    CheckResult(hr);
+    SB_DCHECK(length);
+
+    const uint8_t* data = reinterpret_cast<uint8_t*>(buffer);
+    const size_t data_size = static_cast<size_t>(length);
+
+    DecodedAudioPtr data_ptr(new DecodedAudio(
+        audio_header_.number_of_channels, sample_type_, audio_frame_fmt_,
+        ConvertToMediaTime(win32_timestamp), data_size));
+
+    std::copy(data, data + data_size, data_ptr->buffer());
+
+    output_queue_.PushBack(data_ptr);
+    media_buffer->Unlock();
+  }
+
+  ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
+    ComPtr<IMFMediaType> input_type;
+    HRESULT hr = MFCreateMediaType(&input_type);
+    CheckResult(hr);
+
+    WinAudioFormat audio_fmt(audio_header_);
+    hr = MFInitMediaTypeFromWaveFormatEx(
+        input_type.Get(),
+        audio_fmt.WaveFormatTexPtr(),
+        audio_fmt.Size());
+
+    CheckResult(hr);
+
+    hr = input_type->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0);  // raw aac
+    CheckResult(hr);
+
+    GUID subtype = ConvertToWin32AudioCodec(codec_);
+    hr = input_type->SetGUID(MF_MT_SUBTYPE, subtype);
+    CheckResult(hr);
+
+    std::vector<ComPtr<IMFMediaType>> available_types =
+        GetAvailableTypes(decoder);
+
+    GUID audio_fmt_guid = ConvertToWin32AudioCodec(codec_);
+    available_types = Filter(audio_fmt_guid, available_types);
+    SB_DCHECK(available_types.size());
+    ComPtr<IMFMediaType> selected = available_types[0];
+
+    std::vector<GUID> attribs = {
+      MF_MT_AUDIO_BLOCK_ALIGNMENT,
+      MF_MT_AUDIO_SAMPLES_PER_SECOND,
+      MF_MT_AUDIO_AVG_BYTES_PER_SECOND,
+      MF_MT_AUDIO_NUM_CHANNELS,
+    };
+
+    for (auto it = attribs.begin(); it != attribs.end(); ++it) {
+      CopyUint32Property(*it, input_type.Get(), selected.Get());
+    }
+
+    hr = decoder->SetInputType(0, selected.Get(), 0);
+
+    CheckResult(hr);
+    return selected;
+  }
+
+  void EnsureAudioDecoderCreated() SB_OVERRIDE {
+    if (impl_->has_decoder()) {
+      return;
+    }
+
+    ComPtr<IMFTransform> decoder =
+        DecoderImpl::CreateDecoder(CLSID_MSAACDecMFT);
+
+    ComPtr<IMFMediaType> media_type = Configure(decoder.Get());
+
+    SB_DCHECK(decoder);
+
+    impl_->set_decoder(decoder);
+    impl_->ActivateDecryptor(media_type);
+
+    // 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();
+
+    // These parameters are used for decryption. But these are not used right
+    // now and so remain empty.
+    std::vector<uint8_t> key_id;
+    std::vector<uint8_t> iv;
+    std::vector<Subsample> subsamples;
+
+    const std::int64_t win32_time_stamp = ConvertToWin32Time(media_timestamp);
+
+    // Adjust the offset for 7 bytes to remove the ADTS header.
+    const uint8_t* audio_start = static_cast<const uint8_t*>(data) + 7;
+    const int audio_size = size - 7;
+
+    const bool write_ok = impl_->TryWriteInputBuffer(
+        audio_start, audio_size, win32_time_stamp, key_id, iv, subsamples);
+    impl_->DeliverOutputOnAllTransforms();
+    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.
+    }
+  }
+
+  scoped_refptr<DecodedAudio> ProcessAndRead() SB_OVERRIDE {
+    impl_->DeliverOutputOnAllTransforms();
+    scoped_refptr<DecodedAudio> output = output_queue_.PopFront();
+    return output;
+  }
+
+  SbMediaAudioCodec codec_;
+  SbMediaAudioHeader audio_header_;
+  SbMediaAudioSampleType sample_type_;
+  SbMediaAudioFrameStorageType audio_frame_fmt_;
+  scoped_ptr<DecoderImpl> impl_;
+  AtomicQueue<DecodedAudioPtr> output_queue_;
+};
+}  // anonymous namespace.
+
+scoped_ptr<AbstractWin32AudioDecoder> AbstractWin32AudioDecoder::Create(
+    SbMediaAudioCodec code,
+    SbMediaAudioFrameStorageType audio_frame_fmt,
+    SbMediaAudioSampleType sample_type,
+    const SbMediaAudioHeader& audio_header) {
+  return scoped_ptr<AbstractWin32AudioDecoder>(new
+    AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
+      audio_header));
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/win32_audio_decoder.h b/src/starboard/shared/win32/win32_audio_decoder.h
new file mode 100644
index 0000000..4cc5f27
--- /dev/null
+++ b/src/starboard/shared/win32/win32_audio_decoder.h
@@ -0,0 +1,59 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_WIN32_WIN32_AUDIO_DECODER_H_
+#define STARBOARD_SHARED_WIN32_WIN32_AUDIO_DECODER_H_
+
+#include <vector>
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/shared/win32/media_common.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// AudioDecoder for Win32.
+class AbstractWin32AudioDecoder {
+ public:
+  static scoped_ptr<AbstractWin32AudioDecoder> Create(
+      SbMediaAudioCodec codec,
+      SbMediaAudioFrameStorageType audio_frame_fmt,
+      SbMediaAudioSampleType sample_type,
+      const SbMediaAudioHeader& audio_header);
+  virtual ~AbstractWin32AudioDecoder() {}
+
+  // 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 void WriteEndOfStream() = 0;
+  // OUTPUT
+  //
+  virtual DecodedAudioPtr ProcessAndRead() = 0;
+
+ private:
+  virtual void EnsureAudioDecoderCreated() = 0;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_WIN32_AUDIO_DECODER_H_
diff --git a/src/starboard/shared/win32/win32_decoder_impl.cc b/src/starboard/shared/win32/win32_decoder_impl.cc
new file mode 100644
index 0000000..b06fcdc
--- /dev/null
+++ b/src/starboard/shared/win32/win32_decoder_impl.cc
@@ -0,0 +1,469 @@
+// 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)
+    : type_(type),
+      media_buffer_consumer_(media_buffer_consumer),
+      discontinuity_(false) {
+  SB_DCHECK(media_buffer_consumer_);
+}
+
+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(ComPtr<IMFMediaType> input_type) {
+  if (!decryptor_) {
+    return;
+  }
+
+  HRESULT hr = decryptor_->SetInputType(kStreamId, input_type.Get(),
+                                        0);  // MFT_SET_TYPE_TEST_FLAGS.
+  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;
+  ComPtr<IMFMediaType> decoder_output_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);
+}
+
+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) {
+    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_);
+  bool delivered = false;
+  if (decryptor_) {
+    if (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.
+        ComPtr<IMFSample> sample_inner = DeliverOutputOnTransform(decoder_);
+        if (sample_inner) {
+          DeliverDecodedSample(sample_inner);
+          delivered = true;
+        }
+        hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
+        CheckResult(hr);
+        return delivered;
+      }
+      CheckResult(hr);
+      delivered = true;
+    }
+  }
+  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);
+
+  // Each media sample (IMFSample interface) of output data from the MFT
+  // contains complete, unbroken units of data. The definition of a unit
+  // of data depends on the media type: For uncompressed video, a video
+  // frame; for compressed data, a compressed packet; for uncompressed audio,
+  // a single audio frame.
+  //
+  // For uncompressed audio formats, this flag is always implied. (It is valid
+  // to set the flag, but not required.) An uncompressed audio frame should
+  // never span more than one media sample.
+  SB_DCHECK((output_stream_info.dwFlags & MFT_OUTPUT_STREAM_WHOLE_SAMPLES) !=
+            0);
+
+  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);
+    return NULL;
+  } else 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
new file mode 100644
index 0000000..38374ff
--- /dev/null
+++ b/src/starboard/shared/win32/win32_decoder_impl.h
@@ -0,0 +1,97 @@
+// 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/media.h"
+#include "starboard/shared/win32/media_common.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+class MediaBufferConsumerInterface {
+ public:
+  virtual void Consume(Microsoft::WRL::ComPtr<IMFMediaBuffer> media_buffer,
+                       int64_t win32_timestamp) = 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);
+  ~DecoderImpl();
+
+  static Microsoft::WRL::ComPtr<IMFTransform> CreateDecoder(CLSID clsid);
+
+  void ActivateDecryptor(Microsoft::WRL::ComPtr<IMFMediaType> input_type);
+  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_;
+  bool discontinuity_;
+
+  Microsoft::WRL::ComPtr<IMFTransform> decryptor_;
+  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
new file mode 100644
index 0000000..93bc487
--- /dev/null
+++ b/src/starboard/shared/win32/win32_video_decoder.cc
@@ -0,0 +1,284 @@
+// 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_video_decoder.h"
+
+#include <Codecapi.h>
+#include <utility>
+
+#include "starboard/shared/win32/atomic_queue.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::win32::CheckResult;
+
+namespace {
+
+// This magic number is taken directly from sample from MS.  Can be further
+// tuned in case if there is playback issues.
+const int kSampleAllocatorFramesMax = 5;
+
+// CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D}
+const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
+                                 0x28,       0x31,   0xA8,   0x7D, 0x9D};
+
+ComPtr<ID3D11Texture2D> GetTexture2D(ComPtr<IMFMediaBuffer> media_buffer) {
+  ComPtr<IMFDXGIBuffer> dxgi_buffer;
+  HRESULT hr = media_buffer.As(&dxgi_buffer);
+  CheckResult(hr);
+  SB_DCHECK(dxgi_buffer.Get());
+
+  ComPtr<ID3D11Texture2D> dx_texture;
+  hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&dx_texture));
+  CheckResult(hr);
+  return dx_texture;
+}
+
+class VideoFrameFactory {
+ public:
+  static VideoFramePtr Construct(SbMediaTime timestamp,
+                                 ComPtr<IMFMediaBuffer> media_buffer) {
+    ComPtr<ID3D11Texture2D> texture = GetTexture2D(media_buffer);
+    D3D11_TEXTURE2D_DESC tex_desc;
+    texture->GetDesc(&tex_desc);
+    VideoFramePtr out(new VideoFrame(tex_desc.Width, tex_desc.Height,
+                                     timestamp, media_buffer.Detach(),
+                                     nullptr, DeleteTextureFn));
+    frames_in_flight_.increment();
+    return out;
+  }
+  static int frames_in_flight() { return frames_in_flight_.load(); }
+
+ private:
+  static void DeleteTextureFn(void* context, void* texture) {
+    SB_UNREFERENCED_PARAMETER(context);
+    IMFMediaBuffer* data_ptr = static_cast<IMFMediaBuffer*>(texture);
+    data_ptr->Release();
+    frames_in_flight_.decrement();
+  }
+
+  static atomic_int32_t frames_in_flight_;
+};
+
+atomic_int32_t VideoFrameFactory::frames_in_flight_;
+
+class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder,
+                                    public MediaBufferConsumerInterface {
+ public:
+  explicit AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec)
+      : codec_(codec), surface_width_(0), surface_height_(0) {
+    MediaBufferConsumerInterface* media_cb = this;
+    impl_.reset(new DecoderImpl("video", media_cb));
+    EnsureVideoDecoderCreated();
+  }
+
+  virtual void Consume(ComPtr<IMFMediaBuffer> media_buffer,
+                       int64_t win32_timestamp) {
+    const SbMediaTime media_timestamp = ConvertToMediaTime(win32_timestamp);
+    VideoFramePtr frame_output =
+        VideoFrameFactory::Construct(media_timestamp, media_buffer);
+    output_queue_.PushBack(frame_output);
+  }
+
+  ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
+    ComPtr<IMFMediaType> input_type;
+    HRESULT hr = MFCreateMediaType(&input_type);
+    SB_DCHECK(SUCCEEDED(hr));
+
+    hr = input_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+    SB_DCHECK(SUCCEEDED(hr));
+
+    GUID selected_guid = {};
+
+    // If this is updated then media_is_video_supported.cc also needs to be
+    // updated.
+    switch (codec_) {
+      case kSbMediaVideoCodecH264: {
+        selected_guid = MFVideoFormat_H264;
+        break;
+      }
+      case kSbMediaVideoCodecH265: {
+        selected_guid = MFVideoFormat_H265;
+        break;
+      }
+      case kSbMediaVideoCodecVp9: {
+        selected_guid = MFVideoFormat_VP90;
+        break;
+      }
+      default: { SB_NOTREACHED(); }
+    }
+
+    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);
+
+    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);
+
+    SB_DCHECK(1 == input_stream_count);
+    SB_DCHECK(1 == output_stream_count);
+
+    impl_->ActivateDecryptor(media_type);
+
+    ComPtr<IMFMediaType> output_type =
+        FindMediaType(MFVideoFormat_YV12, decoder.Get());
+
+    SB_DCHECK(output_type);
+
+    hr = decoder->SetOutputType(0, output_type.Get(), 0);
+    CheckResult(hr);
+
+    ComPtr<IMFAttributes> attributes;
+    hr = decoder->GetAttributes(attributes.GetAddressOf());
+    CheckResult(hr);
+
+    UINT32 value = 0;
+    hr = attributes->GetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE, &value);
+    SB_DCHECK(hr == S_OK || hr == MF_E_ATTRIBUTENOTFOUND);
+
+    // TODO: handle the MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE correctly.
+    // 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);
+
+    // Only allowed to set once.
+    SB_DCHECK(0 == surface_width_);
+    SB_DCHECK(0 == surface_height_);
+
+    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));
+    // 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.
+    }
+
+    if (!output_queue_.IsEmpty()) {
+      return false;  // Wait for more data.
+    }
+
+    // This would be used for decrypting content.
+    // For now this is empty.
+    std::vector<uint8_t> key_id;
+    std::vector<uint8_t> iv;
+
+    std::vector<Subsample> empty_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, empty_subsample);
+
+    return write_ok;
+  }
+
+  void WriteEndOfStream() SB_OVERRIDE {
+    if (impl_->has_decoder()) {
+      impl_->DrainDecoder();
+      while (VideoFrameFactory::frames_in_flight() <
+                 kSampleAllocatorFramesMax &&
+             impl_->DeliverOneOutputOnAllTransforms()) {
+      }
+    } else {
+      // Don't call DrainDecoder() if input data is never queued.
+      // TODO: Send EOS.
+    }
+  }
+
+  VideoFramePtr ProcessAndRead() SB_OVERRIDE {
+    if (VideoFrameFactory::frames_in_flight() < kSampleAllocatorFramesMax) {
+      impl_->DeliverOneOutputOnAllTransforms();
+    }
+    VideoFramePtr output = output_queue_.PopFront();
+    return output;
+  }
+
+  AtomicQueue<VideoFramePtr> output_queue_;
+  SbMediaVideoCodec codec_;
+  scoped_ptr<DecoderImpl> impl_;
+
+  uint32_t surface_width_;
+  uint32_t surface_height_;
+  HardwareDecoderContext dx_decoder_ctx_;
+};
+}  // anonymous namespace.
+
+scoped_ptr<AbstractWin32VideoDecoder> AbstractWin32VideoDecoder::Create(
+    SbMediaVideoCodec codec) {
+  return scoped_ptr<AbstractWin32VideoDecoder>(
+      new AbstractWin32VideoDecoderImpl(codec));
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/win32_video_decoder.h b/src/starboard/shared/win32/win32_video_decoder.h
new file mode 100644
index 0000000..84ff7b7
--- /dev/null
+++ b/src/starboard/shared/win32/win32_video_decoder.h
@@ -0,0 +1,51 @@
+// 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_VIDEO_DECODER_H_
+#define STARBOARD_SHARED_WIN32_WIN32_VIDEO_DECODER_H_
+
+#include <deque>
+#include <vector>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/shared/win32/media_common.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// VideoDecoder for Win32.
+class AbstractWin32VideoDecoder {
+ public:
+  static scoped_ptr<AbstractWin32VideoDecoder> Create(SbMediaVideoCodec codec);
+  virtual ~AbstractWin32VideoDecoder() {}
+
+  virtual bool TryWrite(const scoped_refptr<InputBuffer>& buff) = 0;
+  virtual void WriteEndOfStream() = 0;
+  virtual VideoFramePtr ProcessAndRead() = 0;
+
+ private:
+  virtual void EnsureVideoDecoderCreated() = 0;
+};
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_WIN32_VIDEO_DECODER_H_
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/window_create.cc
similarity index 69%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/window_create.cc
index 51f81a1..2f0cbd2 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/window_create.cc
@@ -12,9 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include "starboard/window.h"
 
-#include "starboard/shared/win32/atomic_public.h"
+#include "starboard/shared/win32/application_win32.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+using ::starboard::shared::win32::ApplicationWin32;
+
+SbWindow SbWindowCreate(const SbWindowOptions* options) {
+  return ApplicationWin32::Get()->CreateWindowForWin32(options);
+}
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/window_destroy.cc
similarity index 71%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/window_destroy.cc
index 51f81a1..6de112a 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/window_destroy.cc
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include "starboard/shared/win32/application_win32.h"
+#include "starboard/window.h"
 
-#include "starboard/shared/win32/atomic_public.h"
+using ::starboard::shared::win32::ApplicationWin32;
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+bool SbWindowDestroy(SbWindow window) {
+  return ApplicationWin32::Get()->DestroyWindow(window);
+}
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/window_get_platform_handle.cc
similarity index 67%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/window_get_platform_handle.cc
index 51f81a1..e0f5919 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/window_get_platform_handle.cc
@@ -12,9 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include <EGL/egl.h>
 
-#include "starboard/shared/win32/atomic_public.h"
+#include "starboard/shared/win32/window_internal.h"
+#include "starboard/window.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+void* SbWindowGetPlatformHandle(SbWindow window) {
+  if (!SbWindowIsValid(window)) {
+    return NULL;
+  }
+
+  return reinterpret_cast<EGLNativeWindowType>(window->GetWindowHandle());
+}
diff --git a/src/starboard/shared/win32/window_get_size.cc b/src/starboard/shared/win32/window_get_size.cc
new file mode 100644
index 0000000..f100240
--- /dev/null
+++ b/src/starboard/shared/win32/window_get_size.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/log.h"
+#include "starboard/shared/win32/window_internal.h"
+#include "starboard/window.h"
+
+bool SbWindowGetSize(SbWindow window, SbWindowSize* size) {
+  if (!SbWindowIsValid(window)) {
+    SB_LOG(ERROR) << __FUNCTION__ << ": Invalid window.";
+    return false;
+  }
+
+  size->width = window->width;
+  size->height = window->height;
+  // The video resolution is the same as the graphics resolution.
+  size->video_pixel_ratio = 1.0f;
+  return true;
+}
diff --git a/src/starboard/shared/win32/window_internal.cc b/src/starboard/shared/win32/window_internal.cc
new file mode 100644
index 0000000..94d63e1
--- /dev/null
+++ b/src/starboard/shared/win32/window_internal.cc
@@ -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.
+
+#include "starboard/shared/win32/window_internal.h"
+
+// TODO: Make sure the width and height here behave well given that we want
+// 1080 video, but perhaps 4k UI where applicable.
+SbWindowPrivate::SbWindowPrivate(const SbWindowOptions* options,
+                                 HWND window_handle)
+    : width(options->size.width),
+      height(options->size.height),
+      window_handle_(window_handle) {}
+
+SbWindowPrivate::~SbWindowPrivate() {}
diff --git a/src/starboard/shared/win32/window_internal.h b/src/starboard/shared/win32/window_internal.h
new file mode 100644
index 0000000..be8a308
--- /dev/null
+++ b/src/starboard/shared/win32/window_internal.h
@@ -0,0 +1,43 @@
+// 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_WINDOW_INTERNAL_H_
+#define STARBOARD_SHARED_WIN32_WINDOW_INTERNAL_H_
+
+#include <EGL/egl.h>
+
+// Windows headers.
+#include <windows.h>
+
+#include "starboard/atomic.h"
+#include "starboard/time.h"
+#include "starboard/window.h"
+
+struct SbWindowPrivate {
+  SbWindowPrivate(const SbWindowOptions* options, HWND window_handle);
+  ~SbWindowPrivate();
+
+  HWND GetWindowHandle() { return window_handle_; }
+
+  // The width of this window.
+  int width;
+
+  // The height of this window.
+  int height;
+
+ private:
+  HWND window_handle_;
+};
+
+#endif  // STARBOARD_SHARED_WIN32_WINDOW_INTERNAL_H_
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/shared/win32/window_set_default_options.cc
similarity index 69%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/shared/win32/window_set_default_options.cc
index 51f81a1..d989f52 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/shared/win32/window_set_default_options.cc
@@ -12,9 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include "starboard/memory.h"
+#include "starboard/window.h"
 
-#include "starboard/shared/win32/atomic_public.h"
-
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+void SbWindowSetDefaultOptions(SbWindowOptions* options) {
+  SbMemorySet(options, 0, sizeof(*options));
+  options->size.width = 1920;
+  options->size.height = 1080;
+  options->name = "YouTube";
+}
diff --git a/src/starboard/socket.h b/src/starboard/socket.h
index a9f403c..ba5600d 100644
--- a/src/starboard/socket.h
+++ b/src/starboard/socket.h
@@ -429,6 +429,98 @@
 #endif
 
 #ifdef __cplusplus
+// An inline C++ wrapper to SbSocket.
+class Socket {
+ public:
+  Socket(SbSocketAddressType address_type, SbSocketProtocol protocol)
+      : socket_(SbSocketCreate(address_type, protocol)) {}
+  explicit Socket(SbSocketAddressType address_type)
+      : socket_(SbSocketCreate(address_type, kSbSocketProtocolTcp)) {}
+  explicit Socket(SbSocketProtocol protocol)
+      : socket_(SbSocketCreate(kSbSocketAddressTypeIpv4, protocol)) {}
+  Socket()
+      : socket_(
+            SbSocketCreate(kSbSocketAddressTypeIpv4, kSbSocketProtocolTcp)) {}
+  ~Socket() { SbSocketDestroy(socket_); }
+  bool IsValid() { return SbSocketIsValid(socket_); }
+
+  SbSocketError Connect(const SbSocketAddress* address) {
+    return SbSocketConnect(socket_, address);
+  }
+  SbSocketError Bind(const SbSocketAddress* local_address) {
+    return SbSocketBind(socket_, local_address);
+  }
+  SbSocketError Listen() { return SbSocketListen(socket_); }
+  Socket* Accept() {
+    SbSocket accepted_socket = SbSocketAccept(socket_);
+    if (SbSocketIsValid(accepted_socket)) {
+      return new Socket(accepted_socket);
+    }
+
+    return NULL;
+  }
+
+  bool IsConnected() { return SbSocketIsConnected(socket_); }
+  bool IsConnectedAndIdle() { return SbSocketIsConnectedAndIdle(socket_); }
+
+  bool IsPending() { return GetLastError() == kSbSocketPending; }
+  SbSocketError GetLastError() { return SbSocketGetLastError(socket_); }
+  void ClearLastError() { SbSocketClearLastError(socket_); }
+
+  int ReceiveFrom(char* out_data, int data_size, SbSocketAddress* out_source) {
+    return SbSocketReceiveFrom(socket_, out_data, data_size, out_source);
+  }
+
+  int SendTo(const char* data,
+             int data_size,
+             const SbSocketAddress* destination) {
+    return SbSocketSendTo(socket_, data, data_size, destination);
+  }
+
+  bool GetLocalAddress(SbSocketAddress* out_address) {
+    return SbSocketGetLocalAddress(socket_, out_address);
+  }
+
+  bool SetBroadcast(bool value) { return SbSocketSetBroadcast(socket_, value); }
+
+  bool SetReuseAddress(bool value) {
+    return SbSocketSetReuseAddress(socket_, value);
+  }
+
+  bool SetReceiveBufferSize(int32_t size) {
+    return SbSocketSetReceiveBufferSize(socket_, size);
+  }
+
+  bool SetSendBufferSize(int32_t size) {
+    return SbSocketSetSendBufferSize(socket_, size);
+  }
+
+  bool SetTcpKeepAlive(bool value, SbTime period) {
+    return SbSocketSetTcpKeepAlive(socket_, value, period);
+  }
+
+  bool SetTcpNoDelay(bool value) {
+    return SbSocketSetTcpNoDelay(socket_, value);
+  }
+
+  bool SetTcpWindowScaling(bool value) {
+    return SbSocketSetTcpWindowScaling(socket_, value);
+  }
+
+  bool JoinMulticastGroup(const SbSocketAddress* address) {
+    return SbSocketJoinMulticastGroup(socket_, address);
+  }
+
+  SbSocket socket() { return socket_; }
+
+ private:
+  explicit Socket(SbSocket socket) : socket_(socket) {}
+
+  SbSocket socket_;
+};
+#endif
+
+#ifdef __cplusplus
 // Let SbSocketAddresses be output to log streams.
 inline std::ostream& operator<<(std::ostream& os,
                                 const SbSocketAddress& address) {
diff --git a/src/starboard/tools/abstract_launcher.py b/src/starboard/tools/abstract_launcher.py
new file mode 100644
index 0000000..65bc1ea
--- /dev/null
+++ b/src/starboard/tools/abstract_launcher.py
@@ -0,0 +1,149 @@
+#!/usr/bin/python
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License."""
+"""Abstraction for running Cobalt development tools."""
+
+import abc
+import imp
+import os
+import re
+import sys
+
+platform_module = imp.load_source(
+    "platform", os.path.abspath(
+        os.path.join(os.path.dirname(__file__), "platform.py")))
+
+
+def _GetAllPlatforms(path):
+  """Retrieves information about all available Cobalt ports.
+
+  Args:
+    path:  Root path that will be crawled to find ports.
+
+  Returns:
+    Dict mapping available cobalt ports to their respective location in
+    the filesystem.
+  """
+  platform_dict = {}
+  for port in platform_module.PlatformInfo.EnumeratePorts(path):
+    port_name = re.search(".*starboard-(.*)", port.port_name).group(1)
+    platform_dict[port_name] = port.path
+  return platform_dict
+
+
+def _GetProjectRoot():
+  """Gets the root of this project.
+
+  Returns:
+    Path to the root of this project
+
+  Raises:
+    RuntimeError: There is no root.
+  """
+  current_path = os.path.normpath(os.path.dirname(__file__))
+  while not os.access(os.path.join(current_path, ".gclient"), os.R_OK):
+    next_path = os.path.dirname(current_path)
+    if next_path == current_path:
+      current_path = None
+      break
+    current_path = next_path
+  client_root = current_path
+
+  if not client_root:
+    raise RuntimeError("No project root declared.")
+
+  return client_root
+
+
+def _GetLauncherForPlatform(platform_path):
+  """Gets the module containing a platform's concrete launcher implementation.
+
+  Args:
+    platform_path:  Path to the location of a valid starboard platform.
+
+  Returns:
+    The module containing the platform's launcher implementation.
+  """
+
+  # Necessary because gyp_configuration modules use relative imports.
+  # "cobalt/build" needs to be in sys.path to keep the imports working
+  sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                               os.pardir, os.pardir,
+                                               "cobalt", "build")))
+
+  # Necessary because the gyp_configuration for linux-x64x11 imports
+  # directly from "starboard/".  All of this import logic will eventually be
+  # moved to a configuration system.
+  sys.path.append(os.path.abspath(os.path.join(
+      os.path.dirname(__file__), os.pardir, os.pardir)))
+
+  module_path = os.path.abspath(os.path.join(platform_path,
+                                             "gyp_configuration.py"))
+
+  gyp_module = imp.load_source("platform_module", module_path)
+  return gyp_module.CreatePlatformConfig().GetLauncher()
+
+
+def LauncherFactory(platform, target_name, config, device_id, args):
+  """Creates the proper launcher based upon command line args.
+
+  Args:
+    platform:  The platform on which the app will run
+    target_name:  The name of the executable target (ex. "cobalt")
+    config:  Type of configuration used by the launcher (ex. "qa", "devel")
+    device_id:  The identifier for the devkit being used
+    args:  Any extra arguments to be passed on a platform-specific basis
+
+  Returns:
+    An instance of the concrete launcher class for the desired platform
+
+  Raises:
+    RuntimeError: The platform does not exist, or there is no project root.
+  """
+
+  #  Creates launcher for provided platform if the platform has a valid port
+  client_root = _GetProjectRoot()
+  platform_dict = _GetAllPlatforms(client_root)
+  if platform in platform_dict:
+    platform_path = platform_dict[platform]
+    launcher_module = _GetLauncherForPlatform(platform_path)
+    return launcher_module.Launcher(platform, target_name, config, device_id,
+                                    args)
+  else:
+    raise RuntimeError("Specified platform does not exist.")
+
+
+class AbstractLauncher(object):
+  """Class that specifies all required behavior for Cobalt app launchers."""
+
+  __metaclass__ = abc.ABCMeta
+
+  def __init__(self, platform, target_name, config, device_id, args):
+    self.platform = platform
+    self.target_name = target_name
+    self.config = config
+    self.device_id = device_id
+    self.args = args
+    self.target_command_line_params = []
+    if "target_params" in args:
+      self.target_command_line_params.extend(args["target_params"])
+
+  @abc.abstractmethod
+  def Run(self):
+    """Runs the launcher's executable.  Must be implemented in subclasses."""
+    pass
+
+  @abc.abstractmethod
+  def Kill(self):
+    """Kills the launcher. Must be implemented in subclasses."""
+    pass
diff --git a/src/starboard/tools/send_link.py b/src/starboard/tools/send_link.py
new file mode 100755
index 0000000..50a47dc
--- /dev/null
+++ b/src/starboard/tools/send_link.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python2
+
+# 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.
+"""Send a link to a process running LinkReceiver.
+
+If a Starboard implementation instantiates
+starboard::shared::starboard::LinkReceiver, then it will write a file containing
+the port to the configured temporary directory. This script finds that file and
+sends the specified link to that service. It is up to the application to
+interpret that link content.
+
+Note that LinkReceiver generally runs on the loopback interface, so this script
+should be run from the same device running the application.
+
+"""
+
+import argparse
+from contextlib import closing
+import logging
+import os
+import socket
+import sys
+import tempfile
+import textwrap
+
+
+def _Uncase(text):
+  return text.upper().lower()
+
+
+def _GetPids(executable):
+  if sys.platform in ('win32', 'cygwin'):
+    raise NotImplementedError('Implement me for Windows!')
+  elif sys.platform.startswith('linux') or sys.platform == 'darwin':
+    pids = []
+    executable = _Uncase(executable)
+    for pid in os.listdir('/proc'):
+      if pid == 'curproc':
+        continue
+
+      pid_path = '/proc/' + pid
+      if not os.path.isdir(pid_path):
+        continue
+
+      try:
+        cmdline_path = os.path.join(pid_path, 'cmdline')
+        with open(cmdline_path, mode='rb') as cmdline:
+          content = cmdline.read().decode().split('\x00')
+      except IOError:
+        continue
+
+      if os.path.basename(_Uncase(content[0])).startswith(executable):
+        pids += [pid]
+    return pids
+
+
+def _FindTemporaryFile(prefix, suffix):
+  directory = tempfile.gettempdir()
+  for entry in os.listdir(directory):
+    caseless_entry = _Uncase(entry)
+    if caseless_entry.startswith(prefix) and caseless_entry.endswith(suffix):
+      return os.path.join(directory, entry)
+
+  return None
+
+
+def _SendLink(executable, link):
+  """Sends a link to the process starting with the given executable name."""
+
+  pids = _GetPids(executable)
+
+  if not pids:
+    logging.error('No PIDs found for %s', executable)
+    return 1
+
+  if len(pids) > 1:
+    logging.error('Multiple PIDs found for %s: %s', executable, pids)
+    return 1
+
+  pid = pids[0]
+  temporary_directory = _FindTemporaryFile(_Uncase(executable), _Uncase(pid))
+  if not os.path.isdir(temporary_directory):
+    logging.error('Not a directory: %s', temporary_directory)
+    return 1
+
+  port_file_path = os.path.join(temporary_directory, 'link_receiver_port')
+  if not os.path.isfile(port_file_path):
+    logging.error('Not a file: %s', port_file_path)
+    return 1
+
+  try:
+    with open(port_file_path, mode='rb') as port_file:
+      port = int(port_file.read().decode())
+  except IOError:
+    logging.exception('Could not open port file: %s', port_file_path)
+    return 1
+
+  try:
+    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+      s.connect(('localhost', port))
+      terminated_link = link + '\x00'
+      bytes_sent = 0
+      while bytes_sent < len(terminated_link):
+        sent = s.send(terminated_link[bytes_sent:])
+        if sent == 0:
+          raise RuntimeError('Connection terminated by remote host.')
+        bytes_sent += sent
+  except (RuntimeError, IOError):
+    logging.exception('Could not connect to port: %d', port)
+    return 1
+
+  logging.info('Link "%s" sent to %s at pid %s on port %d.', link, executable,
+               pid, port)
+
+  return 0
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+      description=textwrap.dedent(__doc__))
+  parser.add_argument(
+      'executable',
+      type=str,
+      help='Name of the running executable to send a link to.')
+  parser.add_argument(
+      'link', type=str, help='The link content to send to the executable.')
+  arguments = parser.parse_args()
+  return _SendLink(arguments.executable, arguments.link)
+
+
+if __name__ == '__main__':
+  logging.basicConfig(level=logging.INFO, format='[%(levelname)5s] %(message)s')
+  sys.exit(main())
diff --git a/src/starboard/tools/toolchain/__init__.py b/src/starboard/tools/toolchain/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/tools/toolchain/__init__.py
diff --git a/src/starboard/tools/toolchain/abstract.py b/src/starboard/tools/toolchain/abstract.py
new file mode 100644
index 0000000..c1d940b
--- /dev/null
+++ b/src/starboard/tools/toolchain/abstract.py
@@ -0,0 +1,414 @@
+# 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.
+"""Defines abstract build tools."""
+
+import abc
+
+
+class Tool(object):
+  """A base class for all build tools.
+
+  Build tools are used to generate Ninja files.
+  """
+
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def IsPlatformAgnostic(self):
+    """Whether an output of a tool depends on a platform.
+
+    Platform-agnostic tools are only processed once, even if they are provided
+    by both target and host toolchains.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetRuleName(self):
+    """Returns a name of a Ninja rule.
+
+    Used as a base name for several Ninja variables, such as path, flags, etc.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule.
+    """
+    pass
+
+  # TODO: Inline path in a command and get rid of this method.
+  @abc.abstractmethod
+  def GetPath(self):
+    """Returns a full path to a tool.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule.
+    """
+    pass
+
+  # TODO: Inline extra flags in a command and get rid of this method.
+  @abc.abstractmethod
+  def GetExtraFlags(self):
+    """Returns tool flags specific to a platform.
+
+    These flags are common for all tool invocations.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetMaxConcurrentProcesses(self):
+    """Restricts an amount of concurrently running tool instances.
+
+    See 'depth' at https://ninja-build.org/manual.html#ref_pool for details.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule or doesn't restrict an
+      amount of concurrently running instances.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetCommand(self, path, extra_flags, flags):
+    """Returns a command that invokes a tool.
+
+    See 'command' at https://ninja-build.org/manual.html#ref_rule for details.
+
+    Args:
+      path: A variable that contains a path to a tool.
+      extra_flags: A variable that contains tool flags specific to a platform.
+      flags: A variable that contains tool flags specific to a target.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetDescription(self):
+    """Returns a human-readable description of an action performed by a tool.
+
+    See 'description' at https://ninja-build.org/manual.html#ref_rule for
+    details.
+
+    Returns:
+      None if a tool doesn't have a corresponding rule.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetHeaderDependenciesFilePath(self):
+    """Returns a path to a Makefile that contains implicit dependencies.
+
+    See 'depfile' at https://ninja-build.org/manual.html#ref_rule for details.
+
+    Returns:
+      None if a tool doesn't generate implicit dependencies.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetHeaderDependenciesFormat(self):
+    """Specifies special dependency processing by Ninja.
+
+    See 'deps' at https://ninja-build.org/manual.html#ref_rule for details.
+
+    Returns:
+      None if a tool doesn't generate implicit dependencies.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetRspFilePath(self):
+    """Returns a full path to a response file.
+
+    See 'rspfile' at https://ninja-build.org/manual.html#ref_rule for details.
+
+    Returns:
+      None if a response file is not used.
+    """
+    pass
+
+  @abc.abstractmethod
+  def GetRspFileContent(self):
+    """Returns a content of a response file.
+
+    See 'rspfile_content' at https://ninja-build.org/manual.html#ref_rule for
+    details.
+
+    Returns:
+      None if a response file is not used.
+    """
+    pass
+
+
+class CCompiler(Tool):
+  """Compiles C sources."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'compile_c'
+
+  @abc.abstractmethod
+  def GetFlags(self, defines, include_dirs, cflags):
+    """Returns tool flags specific to a target.
+
+    This method translates platform-agnostic concepts into a command line
+    arguments understood by a tool.
+
+    Args:
+      defines: A list of preprocessor defines in "NAME=VALUE" format.
+      include_dirs: A list of header search directories.
+      cflags: A list of GCC-style command-line flags. See
+        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
+        details.
+
+    Returns:
+      A list of unquoted strings, one for each flag. It is a responsibility of a
+      caller to quote flags that contain special characters (as determined by a
+      shell) before passing to a tool.
+    """
+    pass
+
+
+class CxxCompiler(Tool):
+  """Compiles C++ sources."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'compile_cxx'
+
+  @abc.abstractmethod
+  def GetFlags(self, defines, include_dirs, cflags):
+    """Returns tool flags specific to a target.
+
+    This method translates platform-agnostic concepts into a command line
+    arguments understood by a tool.
+
+    Args:
+      defines: A list of preprocessor defines in "NAME=VALUE" format.
+      include_dirs: A list of header search directories.
+      cflags: A list of GCC-style command-line flags. See
+        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
+        details.
+
+    Returns:
+      A list of unquoted strings, one for each flag. It is a responsibility of a
+      caller to quote flags that contain special characters (as determined by a
+      shell) before passing to a tool.
+    """
+    pass
+
+
+class AssemblerWithCPreprocessor(Tool):
+  """Compiles assembler sources that contain C preprocessor directives."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'assemble'
+
+  @abc.abstractmethod
+  def GetFlags(self, defines, include_dirs, cflags):
+    """Returns tool flags specific to a target.
+
+    This method translates platform-agnostic concepts into a command line
+    arguments understood by a tool.
+
+    Args:
+      defines: A list of preprocessor defines in "NAME=VALUE" format.
+      include_dirs: A list of header search directories.
+      cflags: A list of GCC-style command-line flags. See
+        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
+        details.
+
+    Returns:
+      A list of unquoted strings, one for each flag. It is a responsibility of a
+      caller to quote flags that contain special characters (as determined by a
+      shell) before passing to a tool.
+    """
+    pass
+
+
+class StaticLinker(Tool):
+  """Creates self-contained archives."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'link_static'
+
+  def GetHeaderDependenciesFilePath(self):
+    # Only applicable to C family compilers.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Only applicable to C family compilers.
+    return None
+
+
+class StaticThinLinker(Tool):
+  """Creates thin archives using GNU ar."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'link_static_thin'
+
+  def GetHeaderDependenciesFilePath(self):
+    # Only applicable to C family compilers.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Only applicable to C family compilers.
+    return None
+
+
+class ExecutableLinker(Tool):
+  """Links executables."""
+
+  def IsPlatformAgnostic(self):
+    return False
+
+  def GetRuleName(self):
+    return 'link_executable'
+
+  def GetHeaderDependenciesFilePath(self):
+    # Only applicable to C family compilers.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Only applicable to C family compilers.
+    return None
+
+  @abc.abstractmethod
+  def GetFlags(self, ldflags):
+    """Returns tool flags specific to a target.
+
+    This method translates platform-agnostic concepts into a command line
+    arguments understood by a tool.
+
+    Args:
+      ldflags: A list of GCC-style command-line flags. See
+        https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options for
+        details.
+
+    Returns:
+      A list of unquoted strings, one for each flag. It is a responsibility of a
+      caller to quote flags that contain special characters (as determined by a
+      shell) before passing to a tool.
+    """
+    pass
+
+
+class Stamp(Tool):
+  """Updates the access and modification times of a file to the current time."""
+
+  def IsPlatformAgnostic(self):
+    return True
+
+  def GetRuleName(self):
+    return 'stamp'
+
+  def GetHeaderDependenciesFilePath(self):
+    # Only applicable to C family compilers.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Only applicable to C family compilers.
+    return None
+
+
+class Copy(Tool):
+  """Copies individual files."""
+
+  def IsPlatformAgnostic(self):
+    return True
+
+  def GetRuleName(self):
+    return 'copy'
+
+  def GetHeaderDependenciesFilePath(self):
+    # Only applicable to C family compilers.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Only applicable to C family compilers.
+    return None
+
+
+class Shell(Tool):
+  """Constructs command lines."""
+
+  def IsPlatformAgnostic(self):
+    return True
+
+  def GetRuleName(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetPath(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetExtraFlags(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetMaxConcurrentProcesses(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetCommand(self, path, extra_flags, flags):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetDescription(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetHeaderDependenciesFilePath(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetHeaderDependenciesFormat(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetRspFilePath(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  def GetRspFileContent(self):
+    # Shell should not be used by Ninja, only by other tools.
+    return None
+
+  @abc.abstractmethod
+  def MaybeQuoteArgument(self, argument):
+    """Quotes a string so that shell could interpret it as a single argument.
+
+    Args:
+      argument: A string that can contain arbitrary characters.
+
+    Returns:
+      A quoted and appropriately escaped string. Returns the original string
+      if no processing is necessary.
+    """
+    pass
diff --git a/src/starboard/tools/toolchain/ar.py b/src/starboard/tools/toolchain/ar.py
new file mode 100644
index 0000000..08e60ff
--- /dev/null
+++ b/src/starboard/tools/toolchain/ar.py
@@ -0,0 +1,64 @@
+# 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.
+"""Allows to use GNU ar as a static linker."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class StaticLinkerBase(object):
+  """A base class for GNU ar-based static linkers."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('ar', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    # Run as much concurrent processes as possible.
+    return None
+
+  def GetDescription(self):
+    return 'AR $out'
+
+  def GetRspFilePath(self):
+    return '$out.rsp'
+
+  def GetRspFileContent(self):
+    return '$in_newline'
+
+
+class StaticLinker(StaticLinkerBase, abstract.StaticLinker):
+  """Creates self-contained archives using GNU ar."""
+
+  def __init__(self, **kwargs):
+    super(StaticLinker, self).__init__(**kwargs)
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} rcs {1} $out @$rspfile'.format(path, extra_flags)
+
+
+class StaticThinLinker(StaticLinkerBase, abstract.StaticThinLinker):
+  """Creates thin archives using GNU ar."""
+
+  def __init__(self, **kwargs):
+    super(StaticThinLinker, self).__init__(**kwargs)
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} rcsT {1} $out @$rspfile'.format(path, extra_flags)
diff --git a/src/starboard/tools/toolchain/bash.py b/src/starboard/tools/toolchain/bash.py
new file mode 100644
index 0000000..543553b
--- /dev/null
+++ b/src/starboard/tools/toolchain/bash.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Allows to use Bash as a shell."""
+
+import re
+
+from starboard.tools.toolchain import abstract
+
+
+class Shell(abstract.Shell):
+  """Constructs command lines using Bash syntax."""
+
+  def MaybeQuoteArgument(self, argument):
+    # Rather than attempting to enumerate the bad shell characters, just
+    # whitelist common OK ones and quote anything else.
+    if re.match(r"^[a-zA-Z0-9_=.,\\/+-]+$", argument):
+      return argument  # No quoting necessary.
+    return "'" + argument.replace("'", "'\"'\"'") + "'"
diff --git a/src/starboard/tools/toolchain/clang.py b/src/starboard/tools/toolchain/clang.py
new file mode 100644
index 0000000..68bedf1
--- /dev/null
+++ b/src/starboard/tools/toolchain/clang.py
@@ -0,0 +1,113 @@
+# 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.
+"""Allows to use Clang as a compiler and assembler."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class CompilerBase(object):
+  """A base class for Clang-based compilers and assemblers."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('clang', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    # Run as much concurrent processes as possible.
+    return None
+
+  def GetHeaderDependenciesFilePath(self):
+    return '$out.d'
+
+  def GetHeaderDependenciesFormat(self):
+    return 'gcc'
+
+  def GetRspFilePath(self):
+    # A command line only contains one input and one output file.
+    pass
+
+  def GetRspFileContent(self):
+    # A command line only contains one input and one output file.
+    pass
+
+
+class CCompiler(CompilerBase, abstract.CCompiler):
+  """Compiles C sources using Clang."""
+
+  def __init__(self, **kwargs):
+    super(CCompiler, self).__init__(**kwargs)
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} -x c -MMD -MF $out.d {1} {2} -c $in -o $out'.format(
+        path, extra_flags, flags)
+
+  def GetDescription(self):
+    return 'CC $out'
+
+  def GetFlags(self, defines, include_dirs, cflags):
+    define_flags = ['-D{0}'.format(define) for define in defines]
+    include_dir_flags = [
+        '-I{0}'.format(include_dir) for include_dir in include_dirs
+    ]
+    return define_flags + include_dir_flags + cflags
+
+
+class CxxCompiler(CompilerBase, abstract.CxxCompiler):
+  """Compiles C++ sources using Clang."""
+
+  def __init__(self, **kwargs):
+    super(CxxCompiler, self).__init__(**kwargs)
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} -x c++ -MMD -MF $out.d {1} {2} -c $in -o $out'.format(
+        path, extra_flags, flags)
+
+  def GetDescription(self):
+    return 'CXX $out'
+
+  def GetFlags(self, defines, include_dirs, cflags):
+    define_flags = ['-D{0}'.format(define) for define in defines]
+    include_dir_flags = [
+        '-I{0}'.format(include_dir) for include_dir in include_dirs
+    ]
+    return define_flags + include_dir_flags + cflags
+
+
+class AssemblerWithCPreprocessor(CompilerBase,
+                                 abstract.AssemblerWithCPreprocessor):
+  """Compiles assembler sources that contain C preprocessor directives."""
+
+  def __init__(self, **kwargs):
+    super(AssemblerWithCPreprocessor, self).__init__(**kwargs)
+
+  def GetCommand(self, path, extra_flags, flags):
+    return ('{0} -x assembler-with-cpp -MMD -MF $out.d {1} {2} -c $in -o $out'.
+            format(path, extra_flags, flags))
+
+  def GetDescription(self):
+    return 'ASM $out'
+
+  def GetFlags(self, defines, include_dirs, cflags):
+    define_flags = ['-D{0}'.format(define) for define in defines]
+    include_dir_flags = [
+        '-I{0}'.format(include_dir) for include_dir in include_dirs
+    ]
+    return define_flags + include_dir_flags + cflags
diff --git a/src/starboard/tools/toolchain/clangxx.py b/src/starboard/tools/toolchain/clangxx.py
new file mode 100644
index 0000000..1266ea5
--- /dev/null
+++ b/src/starboard/tools/toolchain/clangxx.py
@@ -0,0 +1,51 @@
+# 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.
+"""Allows to use Clang (invoked as clang++) as a dynamic linker."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class ExecutableLinker(abstract.ExecutableLinker):
+  """Links executables using Clang (invoked as clang++)."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('clang++', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+    self._max_concurrent_processes = kwargs.get(
+        'max_concurrent_processes', common.EstimateMaxConcurrentLinkers())
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    return self._max_concurrent_processes
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} {1} {2} @$rspfile -o $out'.format(path, extra_flags, flags)
+
+  def GetDescription(self):
+    return 'LINK $out'
+
+  def GetRspFilePath(self):
+    return '$out.rsp'
+
+  def GetRspFileContent(self):
+    return '$in_newline'
+
+  def GetFlags(self, ldflags):
+    return ldflags
diff --git a/src/starboard/tools/toolchain/common.py b/src/starboard/tools/toolchain/common.py
new file mode 100644
index 0000000..2e4faa0
--- /dev/null
+++ b/src/starboard/tools/toolchain/common.py
@@ -0,0 +1,106 @@
+# 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.
+"""Provides functionality common for all tools."""
+
+import ctypes
+import os
+import re
+import subprocess
+import sys
+
+
+def GetPath(name, **kwargs):
+  """Computes a path to a tool.
+
+  All tools understand the same 4 path-related arguments: 'path', 'dir',
+  'prefix', and 'name'. When the 'path' argument is provided, it overrides the
+  path to a tool. Otherwise, 'dir', 'prefix', and 'name' arguments, all of which
+  are optional, are joined as {dir}/{prefix}{name} to compute the path.
+
+  Args:
+    name: A default name of a tool.
+    **kwargs: A dictionary that optionally contains 'path', 'dir', 'prefix', and
+        'name' arguments.
+
+  Returns:
+    The computed path.
+  """
+  if 'path' in kwargs:
+    return kwargs['path']
+
+  path = kwargs.get('prefix', '') + kwargs.get('name', name)
+  if 'dir' in kwargs:
+    path = os.path.join(kwargs['dir'], path)
+  return path
+
+
+def GetRuleName(rule_name_base, toolset):
+  """Computes a Ninja name for target and host rules."""
+  suffix = '' if toolset == 'target' else '_{0}'.format(toolset)
+  return rule_name_base + suffix
+
+
+def EstimateMaxConcurrentLinkers():
+  """Estimates a number of dynamic linkers to run concurrently.
+
+  The estimate takes into account available RAM and conservatively assumes that
+  Chromium is being built.
+
+  Returns:
+    An estimated number of processes.
+  """
+  # TODO: Introduce _TryGetPhysicalMemoryInBytes().
+  if sys.platform in ('win32', 'cygwin'):
+
+    class MEMORYSTATUSEX(ctypes.Structure):
+      _fields_ = [
+          ('dwLength', ctypes.c_ulong),
+          ('dwMemoryLoad', ctypes.c_ulong),
+          ('ullTotalPhys', ctypes.c_ulonglong),
+          ('ullAvailPhys', ctypes.c_ulonglong),
+          ('ullTotalPageFile', ctypes.c_ulonglong),
+          ('ullAvailPageFile', ctypes.c_ulonglong),
+          ('ullTotalVirtual', ctypes.c_ulonglong),
+          ('ullAvailVirtual', ctypes.c_ulonglong),
+          ('sullAvailExtendedVirtual', ctypes.c_ulonglong),
+      ]  # pylint: disable=invalid-name
+
+    stat = MEMORYSTATUSEX()
+    stat.dwLength = ctypes.sizeof(stat)
+    ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
+
+    # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
+    # on a 64 GB machine.
+    return max(1, stat.ullTotalPhys / (5 * (2**30)))  # total / 5GB
+  elif sys.platform.startswith('linux'):
+    if os.path.exists('/proc/meminfo'):
+      with open('/proc/meminfo') as meminfo:
+        memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
+        for line in meminfo:
+          match = memtotal_re.match(line)
+          if not match:
+            continue
+          # Allow 6Gb per link on Linux because Gold is quite memory hungry
+          return max(1, int(match.group(1)) / (6 * (2**20)))
+    return 1
+  elif sys.platform == 'darwin':
+    try:
+      avail_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
+      # A static library debug build of Chromium's unit_tests takes ~2.7GB, so
+      # 4GB per ld process allows for some more bloat.
+      return max(1, avail_bytes / (4 * (2**30)))  # total / 4GB
+    except subprocess.CalledProcessError:
+      return 1
+  else:
+    return 1
diff --git a/src/starboard/tools/toolchain/cp.py b/src/starboard/tools/toolchain/cp.py
new file mode 100644
index 0000000..e961bcd
--- /dev/null
+++ b/src/starboard/tools/toolchain/cp.py
@@ -0,0 +1,49 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Allows to use GNU cp as a copy tool."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class Copy(abstract.Copy):
+  """Copies individual files using GNU cp."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('cp', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    # Run as much concurrent processes as possible.
+    return None
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} -f -p {1} $in $out'.format(path, extra_flags)
+
+  def GetDescription(self):
+    return 'COPY $in $out'
+
+  def GetRspFilePath(self):
+    # A command line only contains one input and one output file.
+    pass
+
+  def GetRspFileContent(self):
+    # A command line only contains one input and one output file.
+    pass
diff --git a/src/starboard/tools/toolchain/libtool_darwin.py b/src/starboard/tools/toolchain/libtool_darwin.py
new file mode 100644
index 0000000..07148d2
--- /dev/null
+++ b/src/starboard/tools/toolchain/libtool_darwin.py
@@ -0,0 +1,48 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Allows to use Darwin (not GNU!) libtool as a static linker."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class StaticLinker(abstract.StaticLinker):
+  """Creates self-contained archives using Darwin (not GNU!) libtool."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('libtool', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    # Run as much concurrent processes as possible.
+    return None
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} -static {1} -filelist $rspfile -o $out'.format(
+        path, extra_flags)
+
+  def GetDescription(self):
+    return 'LIBTOOL $out'
+
+  def GetRspFilePath(self):
+    return '$out.rsp'
+
+  def GetRspFileContent(self):
+    return '$in_newline'
diff --git a/src/starboard/tools/toolchain/touch.py b/src/starboard/tools/toolchain/touch.py
new file mode 100644
index 0000000..7008840
--- /dev/null
+++ b/src/starboard/tools/toolchain/touch.py
@@ -0,0 +1,49 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Allows to use POSIX touch as a stamp tool."""
+
+from starboard.tools.toolchain import abstract
+from starboard.tools.toolchain import common
+
+
+class Stamp(abstract.Stamp):
+  """Updates the access and modification times of a file to the current time."""
+
+  def __init__(self, **kwargs):
+    self._path = common.GetPath('touch', **kwargs)
+    self._extra_flags = kwargs.get('extra_flags', [])
+
+  def GetPath(self):
+    return self._path
+
+  def GetExtraFlags(self):
+    return self._extra_flags
+
+  def GetMaxConcurrentProcesses(self):
+    # Run as much concurrent processes as possible.
+    return None
+
+  def GetCommand(self, path, extra_flags, flags):
+    return '{0} {1} $out'.format(path, extra_flags)
+
+  def GetDescription(self):
+    return 'STAMP $out'
+
+  def GetRspFilePath(self):
+    # A command line only contains one input file.
+    pass
+
+  def GetRspFileContent(self):
+    # A command line only contains one input file.
+    pass
diff --git a/src/starboard/tools/toolchain.py b/src/starboard/tools/toolchain_deprecated.py
similarity index 100%
rename from src/starboard/tools/toolchain.py
rename to src/starboard/tools/toolchain_deprecated.py
diff --git a/src/starboard/win/console/main.cc b/src/starboard/win/console/main.cc
deleted file mode 100644
index a129991..0000000
--- a/src/starboard/win/console/main.cc
+++ /dev/null
@@ -1,69 +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 <windows.h>
-
-#include <WinSock2.h>
-
-#include <string>
-#include <vector>
-
-#include "starboard/configuration.h"
-#include "starboard/shared/win32/thread_private.h"
-#include "starboard/shared/win32/wchar_utils.h"
-#include "starboard/stub/application_stub.h"
-
-using starboard::shared::win32::wchar_tToUTF8;
-
-int main(Platform::Array<Platform::String^>^ args) {
-  if (!IsDebuggerPresent()) {
-    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
-    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
-    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
-    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
-  }
-
-  const int kWinSockVersionMajor = 2;
-  const int kWinSockVersionMinor = 2;
-  WSAData wsaData;
-  int init_result = WSAStartup(
-      MAKEWORD(kWinSockVersionMajor, kWinSockVersionMajor), &wsaData);
-
-  SB_CHECK(init_result == 0);
-  // WSAStartup returns the highest version that is supported up to the version
-  // we request.
-  SB_CHECK(LOBYTE(wsaData.wVersion) == kWinSockVersionMajor &&
-           HIBYTE(wsaData.wVersion) == kWinSockVersionMinor);
-
-  starboard::shared::win32::RegisterMainThread();
-
-  std::vector<std::string> string_args;
-  for (auto it = args->begin(); it != args->end(); ++it) {
-    Platform::String^ s = *it;
-    string_args.push_back(wchar_tToUTF8(s->Data(), s->Length()));
-  }
-
-  std::vector<const char*> utf8_args;
-  for (auto it = string_args.begin(); it != string_args.end(); ++it) {
-    utf8_args.push_back(it->data());
-  }
-
-  starboard::stub::ApplicationStub application;
-  int return_value = application.Run(static_cast<int>(utf8_args.size()),
-                                     const_cast<char**>(utf8_args.data()));
-
-  WSACleanup();
-
-  return return_value;
-}
diff --git a/src/starboard/win/console/starboard_platform.gyp b/src/starboard/win/console/starboard_platform.gyp
deleted file mode 100644
index 343eec3..0000000
--- a/src/starboard/win/console/starboard_platform.gyp
+++ /dev/null
@@ -1,42 +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.
-{
-  'includes': [ '../shared/starboard_platform.gypi' ],
-  'variables': {
-    'starboard_platform_dependent_files': [
-      'atomic_public.h',
-      'configuration_public.h',
-      'main.cc',
-      'thread_types_public.h',
-      '<@(stub_media_player)',
-      '../shared/system_get_path.cc',
-      '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
-      '<(DEPTH)/starboard/shared/starboard/queue_application.h',
-      '<(DEPTH)/starboard/shared/starboard/system_request_pause.cc',
-      '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
-      '<(DEPTH)/starboard/shared/starboard/system_request_suspend.cc',
-      '<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
-      '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc',
-      '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc',
-      '<(DEPTH)/starboard/shared/stub/window_create.cc',
-      '<(DEPTH)/starboard/shared/stub/window_destroy.cc',
-      '<(DEPTH)/starboard/shared/stub/window_get_platform_handle.cc',
-      '<(DEPTH)/starboard/shared/stub/window_get_size.cc',
-      '<(DEPTH)/starboard/shared/stub/window_set_default_options.cc',
-      '<(DEPTH)/starboard/shared/uwp/system_get_property.cc',
-      '<(DEPTH)/starboard/stub/application_stub.cc',
-      '<(DEPTH)/starboard/stub/application_stub.h',
-    ],
-  },
-}
diff --git a/src/starboard/win/shared/configuration_public.h b/src/starboard/win/shared/configuration_public.h
index 98fecdd..5bcf077 100644
--- a/src/starboard/win/shared/configuration_public.h
+++ b/src/starboard/win/shared/configuration_public.h
@@ -56,7 +56,7 @@
 
 // Whether the current platform is expected to have many cores (> 6), or a
 // wildly varying number of cores.
-#define SB_HAS_MANY_CORES 0
+#define SB_HAS_MANY_CORES 1
 
 // Whether the current platform is expected to have exactly 1 core.
 #define SB_HAS_1_CORE 0
@@ -65,7 +65,7 @@
 #define SB_HAS_2_CORES 0
 
 // Whether the current platform is expected to have exactly 4 cores.
-#define SB_HAS_4_CORES 1
+#define SB_HAS_4_CORES 0
 
 // Whether the current platform is expected to have exactly 6 cores.
 #define SB_HAS_6_CORES 0
@@ -162,7 +162,7 @@
 
 // The platform's annotation for marking a symbol as exported outside of the
 // current shared library.
-#define SB_EXPORT_PLATFORM __attribute__((visibility("default")))
+#define SB_EXPORT_PLATFORM __declspec(dllexport)
 
 // The platform's annotation for marking a symbol as imported from outside of
 // the current linking unit.
diff --git a/src/starboard/win/shared/gyp_configuration.gypi b/src/starboard/win/shared/gyp_configuration.gypi
index 32823bc..6b18f49 100644
--- a/src/starboard/win/shared/gyp_configuration.gypi
+++ b/src/starboard/win/shared/gyp_configuration.gypi
@@ -24,6 +24,11 @@
 
     'cobalt_media_source_2016': 1,
 
+    # Platform-specific implementations to compile into cobalt.
+    'cobalt_platform_dependencies': [
+      '<(DEPTH)/starboard/egl_and_gles/egl_and_gles.gyp:egl_and_gles',
+    ],
+
     'conditions': [
       ['cobalt_fastbuild==0', {
         'msvs_settings': {
@@ -40,6 +45,46 @@
 
   'target_defaults': {
     'configurations': {
+      'winrt_base': {
+        'msvs_settings': {
+          'VCCLCompilerTool': {
+            'AdditionalOptions': [
+              '/ZW',           # Windows Runtime
+              '/ZW:nostdlib',  # Windows Runtime, no default #using
+            ]
+          }
+        },
+        'defines': [
+          # VS2017 always defines this for UWP apps
+           'WINAPI_FAMILY=WINAPI_FAMILY_APP',
+          # VS2017 always defines this for UWP apps
+           '__WRL_NO_DEFAULT_LIB__',
+        ]
+      }, # winrt_base
+      'win32_base': {
+          'abstract': 1,
+          'msvs_settings': {
+            'VCLinkerTool': {
+              'SubSystem': 1, # Build a console application by default.
+              'AdditionalDependencies': [
+                'shell32.lib',
+                'winmm.lib',
+                'gdi32.lib',
+                'dbghelp.lib',
+                'user32.lib',
+                'shlwapi.lib'
+              ],
+            }
+          },
+          'msvs_target_platform': 'x64',
+          'defines': [
+            '_WIN32',
+            'WIN32',
+            'WINDOWS',
+            '_UNICODE',
+            'UNICODE',
+          ],
+      }, # win32_base
       'msvs_base': {
         'abstract': 1,
         'msvs_configuration_attributes': {
@@ -208,13 +253,13 @@
       '_HAS_EXCEPTIONS=0',
       '__STDC_FORMAT_MACROS', # so that we get PRI*
       # Enable GNU extensions to get prototypes like ffsl.
-      #'_GNU_SOURCE=1',
+      '_GNU_SOURCE=1',
       'WIN32_LEAN_AND_MEAN',
       # By defining this, M_PI will get #defined.
       '_USE_MATH_DEFINES',
       # min and max collide with std::min and std::max
       'NOMINMAX',
-      # Conform with C99 spec.
+       # Conform with C99 spec.
       '_CRT_STDIO_ISO_WIDE_SPECIFIERS',
     ],
     'msvs_settings': {
@@ -248,7 +293,6 @@
 
         'AdditionalOptions': [
           '/errorReport:none', # Don't send error reports to MS.
-          '/permissive-', # Visual C++ conformance mode.
           '/FS', # Force sync PDB updates for parallel compile.
         ],
       },
@@ -322,6 +366,11 @@
       # Cannot be used because Microsoft uses _ASSERTE(("message", 0)) trick
       # in malloc.h which is included pretty much everywhere.
       4548,
+      # Use of noexcept in targets compiled without /EHsc triggers a warning
+      # "termination on exception is not guaranteed" which we consider benign
+      # because we don't expect exceptions to cross the boundary of modules
+      # compiled with /EHsc.
+      4577,
       # Copy constructor could not be generated because a base class copy
       # constructor is inaccessible.
       # This is an expected consequence of using DISALLOW_COPY_AND_ASSIGN().
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi
index 7307c9a..7134bee 100644
--- a/src/starboard/win/shared/starboard_platform.gypi
+++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -14,32 +14,113 @@
 {
   'variables': {
     'sb_pedantic_warnings': 1,
+    'winrt%': 1,
     'stub_media_player': [
-      '<(DEPTH)/starboard/shared/stub/player_create.cc',
-      '<(DEPTH)/starboard/shared/stub/player_destroy.cc',
-      '<(DEPTH)/starboard/shared/stub/player_get_current_frame.cc',
-      '<(DEPTH)/starboard/shared/stub/player_get_info.cc',
-      '<(DEPTH)/starboard/shared/stub/player_output_mode_supported.cc',
-      '<(DEPTH)/starboard/shared/stub/player_seek.cc',
-      '<(DEPTH)/starboard/shared/stub/player_set_bounds.cc',
-      '<(DEPTH)/starboard/shared/stub/player_set_pause.cc',
-      '<(DEPTH)/starboard/shared/stub/player_set_playback_rate.cc',
-      '<(DEPTH)/starboard/shared/stub/player_set_volume.cc',
-      '<(DEPTH)/starboard/shared/stub/player_write_end_of_stream.cc',
-      '<(DEPTH)/starboard/shared/stub/player_write_sample.cc',
-      '<(DEPTH)/starboard/shared/stub/media_can_play_mime_and_key_system.cc',
-      '<(DEPTH)/starboard/shared/stub/media_get_audio_configuration.cc',
-      '<(DEPTH)/starboard/shared/stub/media_get_audio_output_count.cc',
-      '<(DEPTH)/starboard/shared/stub/media_is_audio_supported.cc',
-      '<(DEPTH)/starboard/shared/stub/media_is_output_protected.cc',
-      '<(DEPTH)/starboard/shared/stub/media_is_supported.cc',
-      '<(DEPTH)/starboard/shared/stub/media_is_video_supported.cc',
     ],
+    # TODO: Move this and the win32 dependencies below to a shared/win32/starboard_platform.gypi?
+    'uwp_incompatible_win32': [
+      '<(DEPTH)/starboard/shared/win32/application_win32_key_event.cc',
+      '<(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/starboard_main.cc',
+      '<(DEPTH)/starboard/shared/win32/system_clear_platform_error.cc',
+      '<(DEPTH)/starboard/shared/win32/system_get_device_type.cc',
+      '<(DEPTH)/starboard/shared/win32/system_get_property.cc',
+      '<(DEPTH)/starboard/shared/win32/system_raise_platform_error.cc',
+      '<(DEPTH)/starboard/shared/win32/window_create.cc',
+      '<(DEPTH)/starboard/shared/win32/window_destroy.cc',
+      '<(DEPTH)/starboard/shared/win32/window_get_platform_handle.cc',
+      '<(DEPTH)/starboard/shared/win32/window_get_size.cc',
+      '<(DEPTH)/starboard/shared/win32/window_internal.cc',
+      '<(DEPTH)/starboard/shared/win32/window_intsdfdsfernal.h',
+      '<(DEPTH)/starboard/shared/win32/window_set_default_options.cc',
+    ],
+    'win32_media_player_files': [
+      '<(DEPTH)/starboard/shared/win32/atomic_queue.h',
+      '<(DEPTH)/starboard/shared/win32/audio_decoder.cc',
+      '<(DEPTH)/starboard/shared/win32/audio_decoder.h',
+      '<(DEPTH)/starboard/shared/win32/audio_decoder_thread.cc',
+      '<(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/dx_context_video_decoder.cc',
+      '<(DEPTH)/starboard/shared/win32/dx_context_video_decoder.h',
+      '<(DEPTH)/starboard/shared/win32/media_common.cc',
+      '<(DEPTH)/starboard/shared/win32/media_common.h',
+      '<(DEPTH)/starboard/shared/win32/media_foundation_utils.cc',
+      '<(DEPTH)/starboard/shared/win32/media_foundation_utils.h',
+      '<(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/player_components_impl.cc',
+      '<(DEPTH)/starboard/shared/win32/simple_thread.cc',
+      '<(DEPTH)/starboard/shared/win32/simple_thread.h',
+      '<(DEPTH)/starboard/shared/win32/video_decoder.cc',
+      '<(DEPTH)/starboard/shared/win32/video_decoder.h',
+      '<(DEPTH)/starboard/shared/win32/video_decoder_thread.cc',
+      '<(DEPTH)/starboard/shared/win32/video_decoder_thread.h',
+      '<(DEPTH)/starboard/shared/win32/video_renderer.cc',
+      '<(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',
+    ],
+    'win32_shared_media_player_files': [
+      '<(DEPTH)/starboard/shared/starboard/media/codec_util.cc',
+      '<(DEPTH)/starboard/shared/starboard/media/codec_util.h',
+      '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
+      '<(DEPTH)/starboard/shared/starboard/media/media_get_audio_configuration_stereo_only.cc',
+      '<(DEPTH)/starboard/shared/starboard/media/media_get_audio_output_count_stereo_only.cc',
+      '<(DEPTH)/starboard/shared/starboard/media/media_util.cc',
+      '<(DEPTH)/starboard/shared/starboard/media/media_util.h',
+      '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_resampler_impl.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_time_stretcher.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/decoded_audio_queue.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/wsola_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/job_queue.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_create.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_get_current_frame.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.h',
+      '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_playback_rate.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc',
+      '<(DEPTH)/starboard/shared/stub/media_is_transfer_characteristics_supported.cc',
+
+      # Shared renderers
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_impl_internal.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h',
+    ],
+    'starboard_platform_dependent_files': [
+      '<@(win32_media_player_files)',
+      '<@(win32_shared_media_player_files)',
+    ]
   },
   'targets': [
     {
       'target_name': 'starboard_platform',
       'type': 'static_library',
+      'hard_dependency': 1,
       'msvs_settings': {
         'VCCLCompilerTool': {
           'AdditionalIncludeDirectories': [
@@ -49,15 +130,31 @@
             '<(DEPTH)/third_party/angle/include/KHR',
           ],
           'AdditionalOptions': [
-            '/ZW',           # Windows Runtime
-            '/ZW:nostdlib',  # Windows Runtime, no default #using
-            '/EHsx',         # C++ exceptions (required with /ZW)
+            '/EHsc',         # C++ exceptions (required with /ZW)
             '/FU"<(visual_studio_install_path)/lib/x86/store/references/platform.winmd"',
             '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.FoundationContract/3.0.0.0/Windows.Foundation.FoundationContract.winmd"',
             '/FU"<(windows_sdk_path)/References/<(windows_sdk_version)/Windows.Foundation.UniversalApiContract/4.0.0.0/Windows.Foundation.UniversalApiContract.winmd"',
           ]
         }
       },
+      'conditions': [
+        ['winrt==1', {
+          'msvs_settings': {
+            'VCCLCompilerTool': {
+              'AdditionalOptions': [
+                '/ZW',           # Windows Runtime
+                '/ZW:nostdlib',  # Windows Runtime, no default #using
+              ],
+            },
+          },
+          'defines': [
+            # VS2017 always defines this for UWP apps
+            'WINAPI_FAMILY=WINAPI_FAMILY_APP',
+            # VS2017 always defines this for UWP apps
+            '__WRL_NO_DEFAULT_LIB__',
+          ]
+        }]
+      ],
       'sources': [
         '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
         '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
@@ -192,7 +289,6 @@
         '<(DEPTH)/starboard/shared/win32/file_seek.cc',
         '<(DEPTH)/starboard/shared/win32/file_truncate.cc',
         '<(DEPTH)/starboard/shared/win32/file_write.cc',
-        '<(DEPTH)/starboard/shared/win32/get_home_directory.cc',
         '<(DEPTH)/starboard/shared/win32/log.cc',
         '<(DEPTH)/starboard/shared/win32/log_flush.cc',
         '<(DEPTH)/starboard/shared/win32/log_format.cc',
@@ -236,7 +332,6 @@
         '<(DEPTH)/starboard/shared/win32/socket_resolve.cc',
         '<(DEPTH)/starboard/shared/win32/socket_send_to.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_connection_type.cc',
-        '<(DEPTH)/starboard/shared/win32/system_get_device_type.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_error_string.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_number_of_processors.cc',
         '<(DEPTH)/starboard/shared/win32/system_get_total_cpu_memory.cc',
@@ -291,19 +386,15 @@
         '<(DEPTH)/starboard/shared/win32/time_zone_get_name.cc',
         '<(DEPTH)/starboard/shared/win32/time_utils.h',
         '<(DEPTH)/starboard/shared/win32/wchar_utils.h',
-        'configuration_public.h',
         # Include private stubs, if present.
         '<!@(python "<(DEPTH)/starboard/tools/find_private_files.py" "<(DEPTH)" "shared/stub/*.cc")',
         '<@(starboard_platform_dependent_files)',
+
       ],
       'defines': [
         # This must be defined when building Starboard, and must not when
         # building Starboard client code.
         'STARBOARD_IMPLEMENTATION',
-        # VS2017 always defines this for UWP apps
-        'WINAPI_FAMILY=WINAPI_FAMILY_APP',
-        # VS2017 always defines this for UWP apps
-        '__WRL_NO_DEFAULT_LIB__',
       ],
       'dependencies': [
         'convert_i18n_data',
diff --git a/src/starboard/win/shared/system_get_path.cc b/src/starboard/win/shared/system_get_path.cc
index 9165e0a..9babc9d 100644
--- a/src/starboard/win/shared/system_get_path.cc
+++ b/src/starboard/win/shared/system_get_path.cc
@@ -127,6 +127,7 @@
   }
   return true;
 }
+
 }  // namespace
 
 // Note: This function is only minimally implemented to allow tests to run.
@@ -144,6 +145,12 @@
       return GetExecutablePath(out_path, path_size);
     case kSbSystemPathTempDirectory:
       return CreateAndGetTempPath(out_path, path_size);
+    case kSbSystemPathSourceDirectory:
+      return SbSystemGetPath(kSbSystemPathTempDirectory, out_path, path_size);
+    case kSbSystemPathFontConfigurationDirectory:
+      return false;
+    case kSbSystemPathFontDirectory:
+      return false;
     // TODO: implement all the other cases.
     default:
       SB_NOTIMPLEMENTED();
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/win/win32/atomic_public.h
similarity index 81%
rename from src/starboard/win/console/atomic_public.h
rename to src/starboard/win/win32/atomic_public.h
index 51f81a1..f7eaedf 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/win/win32/atomic_public.h
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#ifndef STARBOARD_WIN_WIN32_ATOMIC_PUBLIC_H_
+#define STARBOARD_WIN_WIN32_ATOMIC_PUBLIC_H_
 
 #include "starboard/shared/win32/atomic_public.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#endif  // STARBOARD_WIN_WIN32_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/win/console/configuration_public.h b/src/starboard/win/win32/configuration_public.h
similarity index 82%
rename from src/starboard/win/console/configuration_public.h
rename to src/starboard/win/win32/configuration_public.h
index 324627f..0eed9c1 100644
--- a/src/starboard/win/console/configuration_public.h
+++ b/src/starboard/win/win32/configuration_public.h
@@ -15,9 +15,9 @@
 // Other source files should never include this header directly, but should
 // include the generic "starboard/configuration.h" instead.
 
-#ifndef STARBOARD_WIN_CONSOLE_CONFIGURATION_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_CONFIGURATION_PUBLIC_H_
+#ifndef STARBOARD_WIN_WIN32_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_WIN_WIN32_CONFIGURATION_PUBLIC_H_
 
 #include "starboard/win/shared/configuration_public.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_CONFIGURATION_PUBLIC_H_
+#endif  // STARBOARD_WIN_WIN32_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/win/console/gyp_configuration.gypi b/src/starboard/win/win32/gyp_configuration.gypi
similarity index 66%
rename from src/starboard/win/console/gyp_configuration.gypi
rename to src/starboard/win/win32/gyp_configuration.gypi
index 0821e08..a0356ea 100644
--- a/src/starboard/win/console/gyp_configuration.gypi
+++ b/src/starboard/win/win32/gyp_configuration.gypi
@@ -16,24 +16,27 @@
   'variables': {
     'javascript_engine': 'mozjs',
     'cobalt_enable_jit': 0,
+    'angle_build_winrt': 0,
+    'winrt': 0,
+    'enable_d3d11_feature_level_11': 1,
   },
   'includes': [
     '../shared/gyp_configuration.gypi',
   ],
   'target_defaults': {
-    'default_configuration': 'win-console_debug',
+    'default_configuration': 'win32_debug',
     'configurations': {
-      'win-console_debug': {
-        'inherit_from': ['msvs_debug'],
+      'win-win32_debug': {
+        'inherit_from': ['win32_base', 'msvs_debug'],
       },
-      'win-console_devel': {
-       'inherit_from': ['msvs_devel'],
+      'win-win32_devel': {
+       'inherit_from': ['win32_base', 'msvs_devel'],
       },
-      'win-console_qa': {
-        'inherit_from': ['msvs_qa'],
+      'win-win32_qa': {
+        'inherit_from': ['win32_base', 'msvs_qa'],
       },
-      'win-console_gold': {
-        'inherit_from': ['msvs_gold'],
+      'win-win32_gold': {
+        'inherit_from': ['win32_base', 'msvs_gold'],
       },
     },  # end of configurations
   },
diff --git a/src/starboard/win/console/gyp_configuration.py b/src/starboard/win/win32/gyp_configuration.py
similarity index 73%
rename from src/starboard/win/console/gyp_configuration.py
rename to src/starboard/win/win32/gyp_configuration.py
index 68f835a..20db88a 100644
--- a/src/starboard/win/console/gyp_configuration.py
+++ b/src/starboard/win/win32/gyp_configuration.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Starboard win-win32 platform configuration for gyp_cobalt."""
 
 import logging
 import os
@@ -27,7 +28,16 @@
 
 def CreatePlatformConfig():
   try:
-    return gyp_configuration.PlatformConfig('win-console')
+    win_lib_config = WinWin32PlatformConfig('win-win32')
+    return win_lib_config
   except RuntimeError as e:
     logging.critical(e)
     return None
+
+
+
+class WinWin32PlatformConfig(gyp_configuration.PlatformConfig):
+  """Starboard win-32 platform configuration."""
+
+  def __init__(self, platform):
+    super(WinWin32PlatformConfig, self).__init__(platform)
diff --git a/src/starboard/win/console/atomic_public.h b/src/starboard/win/win32/main.cc
similarity index 75%
copy from src/starboard/win/console/atomic_public.h
copy to src/starboard/win/win32/main.cc
index 51f81a1..a43ef4d 100644
--- a/src/starboard/win/console/atomic_public.h
+++ b/src/starboard/win/win32/main.cc
@@ -12,9 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+#include "starboard/shared/win32/starboard_main.h"
 
-#include "starboard/shared/win32/atomic_public.h"
-
-#endif  // STARBOARD_WIN_CONSOLE_ATOMIC_PUBLIC_H_
+int main(int argc, char** argv) {
+  StarboardMain(argc, argv);
+}
diff --git a/src/starboard/win/win32/starboard_platform.gyp b/src/starboard/win/win32/starboard_platform.gyp
new file mode 100644
index 0000000..9373b0a
--- /dev/null
+++ b/src/starboard/win/win32/starboard_platform.gyp
@@ -0,0 +1,40 @@
+# 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': [
+        '../shared/starboard_platform.gypi',
+    ],
+    'variables': {
+      'starboard_platform_dependent_files': [
+        'atomic_public.h',
+        'configuration_public.h',
+        'thread_types_public.h',
+        '../shared/system_get_path.cc',
+        'main.cc',
+        '<(DEPTH)/starboard/shared/starboard/localized_strings.cc',
+        '<(DEPTH)/starboard/shared/starboard/localized_strings.cc',
+        '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_pause.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_pause.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_suspend.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_suspend.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
+        '<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc',
+        '<@(uwp_incompatible_win32)',
+        '<@(stub_media_player)'
+      ]
+    },
+  }
diff --git a/src/starboard/win/console/starboard_platform_tests.gyp b/src/starboard/win/win32/starboard_platform_tests.gyp
similarity index 100%
rename from src/starboard/win/console/starboard_platform_tests.gyp
rename to src/starboard/win/win32/starboard_platform_tests.gyp
diff --git a/src/starboard/win/console/thread_types_public.h b/src/starboard/win/win32/thread_types_public.h
similarity index 81%
rename from src/starboard/win/console/thread_types_public.h
rename to src/starboard/win/win32/thread_types_public.h
index b8cc69e..6ee053b 100644
--- a/src/starboard/win/console/thread_types_public.h
+++ b/src/starboard/win/win32/thread_types_public.h
@@ -14,9 +14,9 @@
 
 // Includes threading primitive types and initializers.
 
-#ifndef STARBOARD_WIN_CONSOLE_THREAD_TYPES_PUBLIC_H_
-#define STARBOARD_WIN_CONSOLE_THREAD_TYPES_PUBLIC_H_
+#ifndef STARBOARD_WIN_WIN32_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_WIN_WIN32_THREAD_TYPES_PUBLIC_H_
 
 #include "starboard/shared/win32/thread_types_public.h"
 
-#endif  // STARBOARD_WIN_CONSOLE_THREAD_TYPES_PUBLIC_H_
+#endif  // STARBOARD_WIN_WIN32_THREAD_TYPES_PUBLIC_H_
diff --git a/src/third_party/angle/angle.gyp b/src/third_party/angle/angle.gyp
index 356d9cd..485c738 100644
--- a/src/third_party/angle/angle.gyp
+++ b/src/third_party/angle/angle.gyp
@@ -5,12 +5,13 @@
 {
     'variables':
     {
-        'angle_build_winrt': 1,
+        'angle_build_winrt%': 1,
         'angle_code': 1,
         'angle_gen_path': '<(SHARED_INTERMEDIATE_DIR)/angle',
         'angle_use_commit_id%': 0,
         'angle_enable_d3d9%': 0,
         'angle_enable_d3d11%': 1,
+        'enable_d3d11_feature_level_11%': 0,
         'angle_enable_gl%': 0,
         'angle_enable_vulkan%': 0,
         'angle_enable_essl%': 1, # Enable this for all configs by default
diff --git a/src/third_party/angle/gyp/common_defines.gypi b/src/third_party/angle/gyp/common_defines.gypi
index e489f05..e90fde1 100644
--- a/src/third_party/angle/gyp/common_defines.gypi
+++ b/src/third_party/angle/gyp/common_defines.gypi
@@ -44,13 +44,21 @@
         {
             'defines': [ 'COMPONENT_BUILD' ],
         }],
-        ['target_os=="win"',
+        ['target_os=="win" and angle_build_winrt==1',
         {
             'defines': [
-                'WINAPI_FAMILY=WINAPI_FAMILY_APP',
+                'WINAPI_FAMILY=WINAPI_FAMILY_APP', # UWP.
                 ' __WRL_NO_DEFAULT_LIB__',
-             ],
+            ],
         }],
+        ['target_os=="win" and angle_build_winrt==0',
+        {
+            'defines': [
+                '_WIN32',
+                'WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP', # win32
+                ' __WRL_NO_DEFAULT_LIB__',
+            ],
+        }]
     ],
     'msvs_settings':
     {
diff --git a/src/third_party/angle/src/angle.gyp b/src/third_party/angle/src/angle.gyp
index 6d6aa1d..e28b9b9 100644
--- a/src/third_party/angle/src/angle.gyp
+++ b/src/third_party/angle/src/angle.gyp
@@ -14,6 +14,7 @@
         'angle_use_commit_id%': '<!(python <(angle_id_script_base) check ..)',
         'angle_enable_d3d9%': 0,
         'angle_enable_d3d11%': 0,
+        'enable_d3d11_feature_level_11%': 0,
         'angle_enable_gl%': 0,
         'angle_enable_vulkan%': 0,
         'angle_enable_essl%': 1, # Enable this for all configs by default
diff --git a/src/third_party/angle/src/libANGLE/Display.cpp b/src/third_party/angle/src/libANGLE/Display.cpp
index d67bdd2..9d44056 100644
--- a/src/third_party/angle/src/libANGLE/Display.cpp
+++ b/src/third_party/angle/src/libANGLE/Display.cpp
@@ -1018,7 +1018,7 @@
     {
         return true;
     }
-    return (WindowFromDC(display) != nullptr);
+    return (WindowFromDC(static_cast<HDC>(display)) != nullptr);
 #else
     return true;
 #endif
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/RendererD3D.h b/src/third_party/angle/src/libANGLE/renderer/d3d/RendererD3D.h
index 8a68444..7ae203d 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/RendererD3D.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/RendererD3D.h
@@ -218,7 +218,7 @@
     virtual gl::Error generateMipmapUsingD3D(TextureStorage *storage,
                                              const gl::TextureState &textureState) = 0;
     virtual TextureStorage *createTextureStorage2D(SwapChainD3D *swapChain) = 0;
-    virtual TextureStorage *createTextureStorage2D(IUnknown *texture, bool bindChroma) = 0;
+    virtual TextureStorage *createTextureStorage2D(IUnknown *texture, bool bindChroma, UINT arrayIndex) = 0;
     virtual TextureStorage *createTextureStorageEGLImage(EGLImageD3D *eglImage,
                                                          RenderTargetD3D *renderTargetD3D) = 0;
     virtual TextureStorage *createTextureStorageExternal(
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
index d5ece5d..2d0e74e 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
@@ -5,6 +5,7 @@
 //
 
 // SurfaceD3D.cpp: D3D implementation of an EGL surface
+#include <Mfobjects.h>
 
 #include "libANGLE/renderer/d3d/SurfaceD3D.h"
 
@@ -18,12 +19,37 @@
 #include <EGL/eglext.h>
 #include <algorithm>
 
+// A key for ID3D11DeviceChild. The value should be a bool.
+// When set and true, indicates that the NV12 texture passed
+// in eglCreatePbufferFromClientBuffer EGL_D3D_TEXTURE_ANGLE should
+// draw it's chroma component when a ShaderResourceView is created for it.
+// If absent or false, the luma component is drawn.
+//
+// The value is fetched from ID3D11DeviceChild and stored before
+// eglCreatePbufferFromClientBuffer returns.
+//
 // {3C3A43AB-C69B-46C9-AA8D-B0CFFCD4596D}
 static const GUID kCobaltNv12BindChroma = {
   0x3c3a43ab, 0xc69b, 0x46c9,
   { 0xaa, 0x8d, 0xb0, 0xcf, 0xfc, 0xd4, 0x59, 0x6d }
 };
 
+// A key for ID3D11DeviceChild. The value should be an IMFDXGIBuffer*.
+// When used with an NV12 texture passed in eglCreatePbufferFromClientBuffer
+// EGL_D3D_TEXTURE_ANGLE, this interface is asked for the appropriate
+// texture in a texture array to use via GetSubresourceIndex.
+// This is appropriate for use with IMFTransform video decoders that
+// return IMFDXGIBuffer's that have texture array resources.
+//
+// The value is fetched from ID3D11DeviceChild and stored before
+// eglCreatePbufferFromClientBuffer returns.
+//
+// C62BF18D-B5EE-46B1-9C31-F61BD8AE3B0D
+static const GUID kCobaltDxgiBuffer = {
+  0Xc62bf18d, 0Xb5ee, 0X46b1,
+  {0X9c, 0X31, 0Xf6, 0X1b, 0Xd8, 0Xae, 0X3b, 0X0d }
+};
+
 namespace rx
 {
 
@@ -50,6 +76,7 @@
       mShareHandle(0),
       mD3DTexture(nullptr),
       mBuftype(buftype),
+      mArrayIndex(0),
       mBindChroma(false)
 {
     if (window != nullptr && !mFixedSize)
@@ -76,6 +103,16 @@
             HRESULT hr = static_cast<ID3D11DeviceChild*>(mD3DTexture)->
                 GetPrivateData(kCobaltNv12BindChroma, &out, nullptr);
             mBindChroma = (SUCCEEDED(hr)) && (out != 0);
+
+            // kCobaltDxgiBuffer
+            IMFDXGIBuffer* dxgi_buffer = nullptr;
+            out = sizeof(dxgi_buffer);
+            hr = static_cast<ID3D11DeviceChild*>(mD3DTexture)->
+                GetPrivateData(kCobaltDxgiBuffer, &out, &dxgi_buffer);
+            ASSERT(SUCCEEDED(hr));
+            if (dxgi_buffer != nullptr) {
+              dxgi_buffer->GetSubresourceIndex(&mArrayIndex);
+            }
             break;
         }
 
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
index acc451a..b698228 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.h
@@ -67,6 +67,11 @@
       return mBindChroma;
     }
 
+    UINT getArrayIndex() const
+    {
+      return mArrayIndex;
+    }
+
   protected:
     SurfaceD3D(const egl::SurfaceState &state,
                RendererD3D *renderer,
@@ -101,6 +106,7 @@
     HANDLE mShareHandle;
     IUnknown *mD3DTexture;
     EGLenum mBuftype;
+    UINT mArrayIndex;
     bool mBindChroma;
 };
 
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp
index 8a25f72..e197dc9 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp
@@ -1176,7 +1176,8 @@
 
     if (surfaceD3D->getSwapChain() == nullptr)
         mTexStorage = mRenderer->createTextureStorage2D(
-            surfaceD3D->getD3DTexture(), surfaceD3D->getBindChroma());
+            surfaceD3D->getD3DTexture(), surfaceD3D->getBindChroma(),
+            surfaceD3D->getArrayIndex());
     else
         mTexStorage = mRenderer->createTextureStorage2D(surfaceD3D->getSwapChain());
     mEGLImageTarget = false;
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index f1c3712..6c2e9ad 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -447,9 +447,14 @@
         EGLint requestedMinorVersion = static_cast<EGLint>(
             attributes.get(EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, EGL_DONT_CARE));
 
-// Only allow feature level 10 on starboard.
+
 #if defined(STARBOARD)
+        // Only allow feature level 10 on starboard by default.
+#if defined(ENABLE_D3D11_FEATURE_LEVEL_11)
+        mAvailableFeatureLevels.push_back(D3D_FEATURE_LEVEL_11_0);
+#else
         mAvailableFeatureLevels.push_back(D3D_FEATURE_LEVEL_10_0);
+#endif // defined(ENABLE_D3D11_FEATURE_LEVEL_11)
 #else
         if (requestedMajorVersion == EGL_DONT_CARE || requestedMajorVersion >= 11)
         {
@@ -554,7 +559,7 @@
         // required.
         // The easiest way to check is to query for a IDXGIDevice2.
         bool requireDXGI1_2 = false;
-        HWND hwnd           = WindowFromDC(mDisplay->getNativeDisplayId());
+        HWND hwnd           = WindowFromDC(static_cast<HDC>(mDisplay->getNativeDisplayId()));
         if (hwnd)
         {
             DWORD currentProcessId = GetCurrentProcessId();
@@ -4038,9 +4043,11 @@
     return new TextureStorage11_2D(this, swapChain11);
 }
 
-TextureStorage *Renderer11::createTextureStorage2D(IUnknown *texture, bool bindChroma)
+TextureStorage *Renderer11::createTextureStorage2D(IUnknown *texture,
+                                                   bool bindChroma,
+                                                   UINT arrayIndex)
 {
-    return new TextureStorage11_2D(this, texture, bindChroma);
+    return new TextureStorage11_2D(this, texture, bindChroma, arrayIndex);
 }
 
 TextureStorage *Renderer11::createTextureStorageEGLImage(EGLImageD3D *eglImage,
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index e95ab1f..58c2406 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -263,7 +263,9 @@
     gl::Error generateMipmapUsingD3D(TextureStorage *storage,
                                      const gl::TextureState &textureState) override;
     TextureStorage *createTextureStorage2D(SwapChainD3D *swapChain) override;
-    TextureStorage *createTextureStorage2D(IUnknown *texture, bool bindChroma) override;
+    TextureStorage *createTextureStorage2D(IUnknown *texture,
+                                           bool bindChroma,
+                                           UINT arrayIndex) override;
     TextureStorage *createTextureStorageEGLImage(EGLImageD3D *eglImage,
                                                  RenderTargetD3D *renderTargetD3D) override;
     TextureStorage *createTextureStorageExternal(
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
index 066dd92..eb67992 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
@@ -28,6 +28,8 @@
 #include "libANGLE/renderer/d3d/EGLImageD3D.h"
 #include "libANGLE/renderer/d3d/TextureD3D.h"
 
+#include "starboard/log.h"
+
 namespace rx
 {
 
@@ -714,7 +716,8 @@
       mLevelZeroRenderTarget(nullptr),
       mUseLevelZeroTexture(false),
       mSwizzleTexture(nullptr),
-      mBindChroma(false)
+      mBindChroma(false),
+      mArrayIndex(0)
 {
     mTexture->AddRef();
 
@@ -733,7 +736,10 @@
     mHasKeyedMutex = (texDesc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) != 0;
 }
 
-TextureStorage11_2D::TextureStorage11_2D(Renderer11 *renderer, IUnknown *texture, bool bindChroma)
+TextureStorage11_2D::TextureStorage11_2D(Renderer11 *renderer,
+                                         IUnknown *texture,
+                                         bool bindChroma,
+                                         UINT arrayIndex)
     : TextureStorage11(renderer,
                        0,
                        0,
@@ -743,7 +749,8 @@
       mLevelZeroRenderTarget(nullptr),
       mUseLevelZeroTexture(false),
       mSwizzleTexture(nullptr),
-      mBindChroma(bindChroma)
+      mBindChroma(bindChroma),
+      mArrayIndex(arrayIndex)
 {
     mTexture->AddRef();
 
@@ -1200,6 +1207,12 @@
         d3Texture->GetDesc(&texture_desc);
         if (texture_desc.Format == DXGI_FORMAT_NV12)
         {
+            srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
+            srvDesc.Texture2DArray.MostDetailedMip = mTopLevel + baseLevel;
+            srvDesc.Texture2DArray.MipLevels       = mipLevels;
+            srvDesc.Texture2DArray.FirstArraySlice = mArrayIndex;
+            srvDesc.Texture2DArray.ArraySize       = 1;
+
             if (mBindChroma)
                 srvDesc.Format = DXGI_FORMAT_R8G8_UNORM;
             else
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
index 2df906b..006b427 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
@@ -154,7 +154,10 @@
 {
   public:
     TextureStorage11_2D(Renderer11 *renderer, SwapChain11 *swapchain);
-    TextureStorage11_2D(Renderer11 *renderer, IUnknown *texture, bool bindChroma);
+    TextureStorage11_2D(Renderer11 *renderer,
+                        IUnknown *texture,
+                        bool bindChroma,
+                        unsigned int arrayIndex);
     TextureStorage11_2D(Renderer11 *renderer, GLenum internalformat, bool renderTarget, GLsizei width, GLsizei height, int levels, bool hintLevelZeroOnly = false);
     ~TextureStorage11_2D() override;
 
@@ -209,6 +212,7 @@
 
     Image11 *mAssociatedImages[gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS];
     bool mBindChroma;
+    UINT mArrayIndex;
 };
 
 class TextureStorage11_External : public TextureStorage11
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
index 5394e3d..7aa0f21 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp
@@ -43,12 +43,12 @@
 
 bool NativeWindow11Win32::getClientRect(LPRECT rect) const
 {
-    return GetClientRect(getNativeWindow(), rect) == TRUE;
+    return GetClientRect(static_cast<HWND>(getNativeWindow()), rect) == TRUE;
 }
 
 bool NativeWindow11Win32::isIconic() const
 {
-    return IsIconic(getNativeWindow()) == TRUE;
+    return IsIconic(static_cast<HWND>(getNativeWindow())) == TRUE;
 }
 
 HRESULT NativeWindow11Win32::createSwapChain(ID3D11Device *device,
@@ -99,7 +99,7 @@
         if (!mCompositionTarget)
         {
             HRESULT result =
-                mDevice->CreateTargetForHwnd(getNativeWindow(), TRUE, &mCompositionTarget);
+                mDevice->CreateTargetForHwnd(static_cast<HWND>(getNativeWindow()), TRUE, &mCompositionTarget);
             if (FAILED(result))
             {
                 return result;
@@ -164,11 +164,11 @@
         swapChainDesc.AlphaMode     = DXGI_ALPHA_MODE_UNSPECIFIED;
         swapChainDesc.Flags         = 0;
         IDXGISwapChain1 *swapChain1 = nullptr;
-        HRESULT result = factory2->CreateSwapChainForHwnd(device, getNativeWindow(), &swapChainDesc,
+        HRESULT result = factory2->CreateSwapChainForHwnd(device, static_cast<HWND>(getNativeWindow()), &swapChainDesc,
                                                           nullptr, nullptr, &swapChain1);
         if (SUCCEEDED(result))
         {
-            factory2->MakeWindowAssociation(getNativeWindow(), DXGI_MWA_NO_ALT_ENTER);
+            factory2->MakeWindowAssociation(static_cast<HWND>(getNativeWindow()), DXGI_MWA_NO_ALT_ENTER);
             *swapChain = static_cast<IDXGISwapChain *>(swapChain1);
         }
         SafeRelease(factory2);
@@ -187,7 +187,7 @@
     swapChainDesc.BufferUsage =
         DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER;
     swapChainDesc.Flags              = 0;
-    swapChainDesc.OutputWindow       = getNativeWindow();
+    swapChainDesc.OutputWindow       = static_cast<HWND>(getNativeWindow());
     swapChainDesc.SampleDesc.Count   = samples;
     swapChainDesc.SampleDesc.Quality = 0;
     swapChainDesc.Windowed           = TRUE;
@@ -196,7 +196,7 @@
     HRESULT result = factory->CreateSwapChain(device, &swapChainDesc, swapChain);
     if (SUCCEEDED(result))
     {
-        factory->MakeWindowAssociation(getNativeWindow(), DXGI_MWA_NO_ALT_ENTER);
+        factory->MakeWindowAssociation(static_cast<HWND>(getNativeWindow()), DXGI_MWA_NO_ALT_ENTER);
     }
     return result;
 }
@@ -212,6 +212,6 @@
 // static
 bool NativeWindow11Win32::IsValidNativeWindow(EGLNativeWindowType window)
 {
-    return IsWindow(window) == TRUE;
+    return IsWindow(static_cast<HWND>(window)) == TRUE;
 }
 }  // namespace rx
diff --git a/src/third_party/angle/src/libGLESv2.gypi b/src/third_party/angle/src/libGLESv2.gypi
index 818c152..706102c 100644
--- a/src/third_party/angle/src/libGLESv2.gypi
+++ b/src/third_party/angle/src/libGLESv2.gypi
@@ -999,6 +999,12 @@
                                 '<@(libangle_d3d11_win32_sources)',
                             ],
                         }],
+                        ['enable_d3d11_feature_level_11==1',
+                        {
+                            'defines': [
+                                'ENABLE_D3D11_FEATURE_LEVEL_11'
+                            ]
+                        }],
                     ],
                 }],
                 ['angle_enable_gl==1',
diff --git a/src/third_party/libevent/libevent.gyp b/src/third_party/libevent/libevent.gyp
index 8f514c1..3b9f4ed 100644
--- a/src/third_party/libevent/libevent.gyp
+++ b/src/third_party/libevent/libevent.gyp
@@ -78,10 +78,6 @@
                   'include_dirs': [ 'starboard/ps4' ],
                   }
                 ],
-                [ 'target_os == "cell"', {
-                  'include_dirs': [ 'starboard/ps3' ],
-                  },
-                ],
               ],
             }],
             # libevent has platform-specific implementation files.  Since its
diff --git a/src/third_party/libwebp/utils/bit_reader.h b/src/third_party/libwebp/utils/bit_reader.h
index e9e0ab8..5168b5b 100644
--- a/src/third_party/libwebp/utils/bit_reader.h
+++ b/src/third_party/libwebp/utils/bit_reader.h
@@ -16,6 +16,7 @@
 #define WEBP_UTILS_BIT_READER_H_
 
 #if defined(STARBOARD)
+#include "starboard/byte_swap.h"
 #include "starboard/log.h"
 #include "starboard/memory.h"
 #else
@@ -165,7 +166,9 @@
 #if !defined(__BIG_ENDIAN__)
 #if (BITS > 32)
 // gcc 4.3 has builtin functions for swap32/swap64
-#if defined(__GNUC__) && \
+#if defined(STARBOARD)
+    bits = SbByteSwapU64(in_bits);
+#elif defined(__GNUC__) && \
            (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
     bits = (bit_t)__builtin_bswap64(in_bits);
 #elif defined(_MSC_VER)
@@ -183,7 +186,9 @@
 #endif
     bits >>= 64 - BITS;
 #elif (BITS >= 24)
-#if defined(__i386__) || defined(__x86_64__)
+#if defined(STARBOARD)
+    bits = SbByteSwapU32(in_bits);
+#elif defined(__i386__) || defined(__x86_64__)
     __asm__ volatile("bswap %k0" : "=r"(in_bits) : "0"(in_bits));
     bits = (bit_t)in_bits;   // 24b/32b -> 32b/64b zero-extension
 #elif defined(_MSC_VER)
diff --git a/src/third_party/libxml/src/xmlIO.c b/src/third_party/libxml/src/xmlIO.c
index f0c0929..797fb36 100644
--- a/src/third_party/libxml/src/xmlIO.c
+++ b/src/third_party/libxml/src/xmlIO.c
@@ -41,8 +41,8 @@
 #include <lzma.h>
 #endif
 
-#if (defined(WIN32) || defined(HAVE_WIN32)) && !defined(__LB_XB1__) &&  \
-    !defined(__LB_XB360__)
+#if (!defined(STARBOARD) && (defined(WIN32) || defined(HAVE_WIN32)) && !defined(__LB_XB1__) &&  \
+    !defined(__LB_XB360__))
 #define HAVE_WIN32
 #include <windows.h>
 #endif
diff --git a/src/third_party/mozjs-45/js/src/proxy/Proxy.cpp b/src/third_party/mozjs-45/js/src/proxy/Proxy.cpp
index 7e1cc65..e707cf7 100644
--- a/src/third_party/mozjs-45/js/src/proxy/Proxy.cpp
+++ b/src/third_party/mozjs-45/js/src/proxy/Proxy.cpp
@@ -443,6 +443,7 @@
 bool
 Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
 {
+    JS_CHECK_RECURSION(cx, return false);
     return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
 }
 
diff --git a/src/third_party/mozjs-45/mozjs-45.gyp b/src/third_party/mozjs-45/mozjs-45.gyp
index f331e80..4bc4c17 100644
--- a/src/third_party/mozjs-45/mozjs-45.gyp
+++ b/src/third_party/mozjs-45/mozjs-45.gyp
@@ -19,7 +19,8 @@
     'generated_include_directory': '<(SHARED_INTERMEDIATE_DIR)/mozjs-45/include',
 
     'common_cflags': [
-      '-include <(DEPTH)/third_party/mozjs-45/cobalt_config/include/js-confdefs.h',
+      '-include',
+      '<(DEPTH)/third_party/mozjs-45/cobalt_config/include/js-confdefs.h',
       '-Wno-inline-new-delete',
       '-Wno-invalid-offsetof',
       '-Wno-unused-function',
diff --git a/src/tools/gyp/pylib/gyp/generator/ninja.py b/src/tools/gyp/pylib/gyp/generator/ninja.py
index c8b973a..7295c7e 100755
--- a/src/tools/gyp/pylib/gyp/generator/ninja.py
+++ b/src/tools/gyp/pylib/gyp/generator/ninja.py
@@ -32,6 +32,8 @@
 if sys.platform == 'cygwin':
   import cygpath
 
+from starboard.tools.toolchain import abstract
+
 generator_default_variables = {
     'EXECUTABLE_PREFIX': '',
     'EXECUTABLE_SUFFIX': '',
@@ -70,20 +72,12 @@
 generator_additional_path_sections = []
 generator_extra_sources_for_rules = []
 
-# TODO: figure out how to not build extra host objects in the non-cross-compile
-# case when this is enabled, and enable unconditionally.
-generator_supports_multiple_toolsets = (os.environ.get('GYP_CROSSCOMPILE') or
-                                        os.environ.get('AR_host') or
-                                        os.environ.get('CC_host') or
-                                        os.environ.get('CXX_host') or
-                                        os.environ.get('AR_target') or
-                                        os.environ.get('CC_target') or
-                                        os.environ.get('CXX_target'))
+generator_supports_multiple_toolsets = True
 
 is_linux = platform.system() == 'Linux'
 is_windows = platform.system() == 'Windows'
 
-microsoft_flavors = ['win', 'win-console', 'win-lib', 'xb1', 'xb1-future']
+microsoft_flavors = ['win', 'win-win32', 'win-console', 'win-lib', 'xb1', 'xb1-future']
 sony_flavors = ['ps3', 'ps4']
 windows_host_flavors = microsoft_flavors + sony_flavors
 
@@ -92,6 +86,39 @@
   return config.GetPlatformConfig(flavor).GetToolchain()
 
 
+def GetTargetToolchain(flavor):
+  return config.GetPlatformConfig(flavor).GetTargetToolchain()
+
+
+def GetHostToolchain(flavor):
+  return config.GetPlatformConfig(flavor).GetHostToolchain()
+
+
+def FindFirstInstanceOf(type, instances):
+  try:
+    return (instance for instance in instances
+            if isinstance(instance, type)).next()
+  except StopIteration:
+    return None
+
+
+def GetNinjaRuleName(tool, toolset):
+  if tool.IsPlatformAgnostic() or toolset == 'target':
+    return tool.GetRuleName()
+  return '{0}_{1}'.format(tool.GetRuleName(), toolset)
+
+
+def GetConfigFlags(config, toolset, keyword):
+  flags = config.get(keyword, [])
+  if toolset == 'host':
+    flags = config.get('{0}_host'.format(keyword), flags)
+  return flags
+
+
+def JoinShellArguments(shell, arguments):
+  return ' '.join(shell.MaybeQuoteArgument(argument) for argument in arguments)
+
+
 def StripPrefix(arg, prefix):
   if arg.startswith(prefix):
     return arg[len(prefix):]
@@ -411,14 +438,17 @@
     if len(targets) == 1:
       return targets[0]
 
-    stamp = self.GypPathToUniqueOutput(name + '.stamp')
     try:
-      raise NotImplementedError()  # TODO: Implement the abstract toolchain.
+      assert FindFirstInstanceOf(abstract.Stamp, GetHostToolchain(
+          self.flavor)), 'Host toolchain must provide stamp tool.'
     except NotImplementedError:
       # Fall back to the legacy toolchain.
-      self.ninja.build(stamp, 'stamp', targets)
+      pass
+
+    stamp_output = self.GypPathToUniqueOutput(name + '.stamp')
+    self.ninja.build(stamp_output, 'stamp', targets)
     self.ninja.newline()
-    return stamp
+    return stamp_output
 
   def WriteSpec(self, spec, config_name, generator_flags):
     """The main entry point for NinjaWriter: write the build rules for a spec.
@@ -758,24 +788,27 @@
     return all_outputs
 
   def WriteCopy(self, src, dst, prebuild, env, mac_bundle_depends):
+    try:
+      assert FindFirstInstanceOf(abstract.Copy, GetHostToolchain(
+          self.flavor)), 'Host toolchain must provide copy tool.'
+    except NotImplementedError:
+      # Fall back to the legacy toolchain.
+      pass
+
     dst = self.GypPathToNinja(dst, env)
     # Renormalize with the separator character of the os on which ninja will run
     dst = self.path_module.normpath(dst)
 
-    try:
-      raise NotImplementedError()  # TODO: Implement the abstract toolchain.
-    except NotImplementedError:
-      # Fall back to the legacy toolchain.
-      self.ninja.build(dst, 'copy', src, order_only=prebuild)
-      if self.is_mac_bundle:
-        # gyp has mac_bundle_resources to copy things into a bundle's
-        # Resources folder, but there's no built-in way to copy files to other
-        # places in the bundle. Hence, some targets use copies for this. Check
-        # if this file is copied into the current bundle, and if so add it to
-        # the bundle depends so that dependent targets get rebuilt if the copy
-        # input changes.
-        if dst.startswith(self.xcode_settings.GetBundleContentsFolderPath()):
-          mac_bundle_depends.append(dst)
+    self.ninja.build(dst, 'copy', src, order_only=prebuild)
+    if self.is_mac_bundle:
+      # gyp has mac_bundle_resources to copy things into a bundle's
+      # Resources folder, but there's no built-in way to copy files to other
+      # places in the bundle. Hence, some targets use copies for this. Check
+      # if this file is copied into the current bundle, and if so add it to
+      # the bundle depends so that dependent targets get rebuilt if the copy
+      # input changes.
+      if dst.startswith(self.xcode_settings.GetBundleContentsFolderPath()):
+        mac_bundle_depends.append(dst)
     return [dst]
 
   def WriteCopies(self, copies, prebuild, mac_bundle_depends):
@@ -860,7 +893,86 @@
     """Write build rules to compile all of |sources|."""
 
     try:
-      raise NotImplementedError()  # TODO: Implement the abstract toolchain.
+      shell = FindFirstInstanceOf(abstract.Shell, GetHostToolchain(self.flavor))
+      assert shell, 'Host toolchain must provide shell.'
+
+      if self.toolset == 'target':
+        toolchain = GetTargetToolchain(self.flavor)
+      else:
+        toolchain = GetHostToolchain(self.flavor)
+
+      defines = config.get('defines', [])
+      include_dirs = [
+          self.GypPathToNinja(include_dir)
+          for include_dir in config.get('include_dirs', [])
+      ]
+
+      # TODO: This code emulates legacy toolchain behavior. We need to migrate
+      #       to single-responsibility, toolchain-independent GYP keywords as
+      #       per abstract toolchain design doc.
+      cflags = GetConfigFlags(config, self.toolset, 'cflags')
+      cflags_c = GetConfigFlags(config, self.toolset, 'cflags_c')
+      cflags_cc = GetConfigFlags(config, self.toolset, 'cflags_cc')
+
+      c_compiler = FindFirstInstanceOf(abstract.CCompiler, toolchain)
+      if c_compiler:
+        c_compiler_flags = c_compiler.GetFlags(defines, include_dirs,
+                                               cflags + cflags_c)
+        self.ninja.variable(
+            '{0}_flags'.format(GetNinjaRuleName(c_compiler, self.toolset)),
+            JoinShellArguments(shell, c_compiler_flags))
+
+      cxx_compiler = FindFirstInstanceOf(abstract.CxxCompiler, toolchain)
+      if cxx_compiler:
+        cxx_compiler_flags = cxx_compiler.GetFlags(defines, include_dirs,
+                                                   cflags + cflags_cc)
+        self.ninja.variable(
+            '{0}_flags'.format(GetNinjaRuleName(cxx_compiler, self.toolset)),
+            JoinShellArguments(shell, cxx_compiler_flags))
+
+      assembler = FindFirstInstanceOf(abstract.AssemblerWithCPreprocessor,
+                                      toolchain)
+      if assembler:
+        assembler_flags = assembler.GetFlags(defines, include_dirs,
+                                             cflags + cflags_c)
+        self.ninja.variable(
+            '{0}_flags'.format(GetNinjaRuleName(assembler, self.toolset)),
+            JoinShellArguments(shell, assembler_flags))
+
+      self.ninja.newline()
+
+      outputs = []
+      for source in sources:
+        _, extension = os.path.splitext(source)
+        if extension in ['.c']:
+          assert c_compiler, ('Toolchain must provide C compiler in order to '
+                              'build {0} for {1} platform.').format(
+                                  source, self.toolset)
+          rule_name = GetNinjaRuleName(c_compiler, self.toolset)
+        elif extension in ['.cc', '.cpp', '.cxx']:
+          assert cxx_compiler, ('Toolchain must provide C++ compiler in order '
+                                'to build {0} for {1} platform.').format(
+                                    source, self.toolset)
+          rule_name = GetNinjaRuleName(cxx_compiler, self.toolset)
+        elif extension in ['.S', '.s']:
+          assert assembler, ('Toolchain must provide assembler in order to '
+                             'build {0} for {1} platform.').format(
+                                 source, self.toolset)
+          rule_name = GetNinjaRuleName(assembler, self.toolset)
+        else:
+          rule_name = None
+
+        if rule_name:
+          input = self.GypPathToNinja(source)
+          output = '{0}.o'.format(self.GypPathToUniqueOutput(source))
+          self.ninja.build(
+              output,
+              rule_name,
+              input,
+              implicit=None,  # TODO: Implemenet precompiled headers.
+              order_only=predepends)
+          outputs.append(output)
+
     except NotImplementedError:
       # Fall back to the legacy toolchain.
 
@@ -1048,7 +1160,76 @@
     """Write out a link step. Fills out target.binary. """
 
     try:
-      raise NotImplementedError()  # TODO: Implement the abstract toolchain.
+      if self.toolset == 'target':
+        toolchain = GetTargetToolchain(self.flavor)
+      else:
+        toolchain = GetHostToolchain(self.flavor)
+
+      shell = FindFirstInstanceOf(abstract.Shell, GetHostToolchain(self.flavor))
+      assert shell, 'Host toolchain must provide shell.'
+
+      target_type = spec['type']
+      if target_type == 'executable':
+        executable_linker = FindFirstInstanceOf(abstract.ExecutableLinker,
+                                                toolchain)
+        assert executable_linker, ('Toolchain must provide executable linker '
+                                   'for {0} platform.').format(self.toolset)
+
+        rule_name = GetNinjaRuleName(executable_linker, self.toolset)
+
+        # TODO: This code emulates legacy toolchain behavior. We need to migrate
+        #       to single-responsibility, toolchain-independent GYP keywords as
+        #       per abstract toolchain design doc.
+        libraries_keyword = 'libraries{0}'.format('_host' if self.toolset ==
+                                                  'host' else '')
+        libraries = spec.get(libraries_keyword, []) + config.get(
+            libraries_keyword, [])
+        ldflags = gyp.common.uniquer(
+            map(self.ExpandSpecial,
+                GetConfigFlags(config, self.toolset, 'ldflags') + libraries))
+
+        executable_linker_flags = executable_linker.GetFlags(ldflags)
+        self.ninja.variable('{0}_flags'.format(rule_name),
+                            JoinShellArguments(shell, executable_linker_flags))
+      else:
+        raise Exception('Target type {0} is not supported.'.format(target_type))
+
+      order_only_deps = set()
+
+      if 'dependencies' in spec:
+        # Two kinds of dependencies:
+        # - Linkable dependencies (like a .a or a .so): add them to the link
+        #   line.
+        # - Non-linkable dependencies (like a rule that generates a file
+        #   and writes a stamp file): add them to implicit_deps or
+        #   order_only_deps
+        extra_link_deps = []
+        for dep in spec['dependencies']:
+          target = self.target_outputs.get(dep)
+          if not target:
+            continue
+          linkable = target.Linkable()
+          if linkable:
+            extra_link_deps.append(target.binary)
+
+          final_output = target.FinalOutput()
+          if not linkable or final_output != target.binary:
+            order_only_deps.add(final_output)
+
+        # dedup the extra link deps while preserving order
+        seen = set()
+        extra_link_deps = [
+            x for x in extra_link_deps if x not in seen and not seen.add(x)
+        ]
+
+        link_deps.extend(extra_link_deps)
+
+      output = self.ComputeOutput(spec)
+      self.target.binary = output
+
+      self.ninja.build(
+          output, rule_name, link_deps, order_only=list(order_only_deps))
+
     except NotImplementedError:
       # Fall back to the legacy toolchain.
 
@@ -1241,13 +1422,28 @@
       self.target.binary = compile_deps
     elif spec['type'] == 'static_library':
       self.target.binary = self.ComputeOutput(spec)
+      variables = []
 
       try:
-        raise NotImplementedError()  # TODO: Implement the abstract toolchain.
+        if self.toolset == 'target':
+          toolchain = GetTargetToolchain(self.flavor)
+        else:
+          toolchain = GetHostToolchain(self.flavor)
+
+        static_linker = FindFirstInstanceOf(abstract.StaticLinker, toolchain)
+        if not self.is_standalone_static_library:
+          static_thin_linker = FindFirstInstanceOf(abstract.StaticThinLinker,
+                                                   toolchain)
+          if static_thin_linker:
+            static_linker = static_thin_linker
+        assert static_linker, ('Toolchain must provide static linker in order '
+                               'to build {0} for {1} platform.').format(
+                                   self.target.binary, self.toolset)
+
+        rule_name = GetNinjaRuleName(static_linker, self.toolset)
       except NotImplementedError:
         # Fall back to the legacy toolchain.
 
-        variables = []
         if GetToolchainOrNone(self.flavor):
           libflags = GetToolchainOrNone(
               self.flavor).GetCompilerSettings().GetLibFlags(
@@ -1264,17 +1460,18 @@
         # TODO: Starboardize.
         if (self.flavor not in (['mac'] + microsoft_flavors) and
             not self.is_standalone_static_library):
-          command = 'alink_thin'
+          rule_name = 'alink_thin'
         else:
-          command = 'alink'
+          rule_name = 'alink'
         if self.toolset != 'target':
-          command += '_' + self.toolset
-        self.ninja.build(
-            self.target.binary,
-            command,
-            link_deps,
-            order_only=compile_deps,
-            variables=variables)
+          rule_name += '_' + self.toolset
+
+      self.ninja.build(
+          self.target.binary,
+          rule_name,
+          link_deps,
+          order_only=compile_deps,
+          variables=variables)
     else:
       self.WriteLink(spec, config_name, config, link_deps)
     return self.target.binary
@@ -1419,10 +1616,12 @@
     if extension:
       extension = '.' + extension
     elif self.toolset == 'host':
-      if is_linux:
-        extension = ''
-      elif is_windows:
+      # TODO: Based on a type of target, ask a corresponding
+      #       tool from a toolchain to compute the file name.
+      if is_windows:
         extension = '.exe'
+      else:
+        extension = ''
     else:
       extension = DEFAULT_EXTENSION.get(type, '')
 
@@ -1700,6 +1899,46 @@
     return 1
 
 
+def MaybeWritePathVariable(ninja, tool, toolset):
+  if tool.GetPath():
+    ninja.variable('{0}_path'.format(GetNinjaRuleName(tool, toolset)),
+                   tool.GetPath())
+
+
+def MaybeWriteExtraFlagsVariable(ninja, tool, toolset, shell):
+  if tool.GetExtraFlags():
+    ninja.variable(
+        '{0}_extra_flags'.format(GetNinjaRuleName(tool, toolset)),
+        JoinShellArguments(shell, tool.GetExtraFlags()))
+
+
+def MaybeWritePool(ninja, tool, toolset):
+  if tool.GetMaxConcurrentProcesses():
+    ninja.pool(
+        '{0}_pool'.format(GetNinjaRuleName(tool, toolset)),
+        depth=tool.GetMaxConcurrentProcesses())
+
+
+def MaybeWriteRule(ninja, tool, toolset):
+  if tool.GetRuleName():
+    name = GetNinjaRuleName(tool, toolset)
+
+    path = '${0}_path'.format(name)
+    extra_flags = '${0}_extra_flags'.format(name)
+    flags = '${0}_flags'.format(name)
+    pool = '{0}_pool'.format(name) if tool.GetMaxConcurrentProcesses() else None
+
+    ninja.rule(
+        name,
+        tool.GetCommand(path, extra_flags, flags),
+        description=tool.GetDescription(),
+        depfile=tool.GetHeaderDependenciesFilePath(),
+        deps=tool.GetHeaderDependenciesFormat(),
+        pool=pool,
+        rspfile=tool.GetRspFilePath(),
+        rspfile_content=tool.GetRspFileContent())
+
+
 def GenerateOutputForConfig(target_list, target_dicts, data, params,
                             config_name):
   options = params['options']
@@ -1729,7 +1968,40 @@
   make_global_settings = data[build_file].get('make_global_settings', [])
 
   try:
-    raise NotImplementedError()  # TODO: Implement the abstract toolchain.
+    # To avoid duplication, platform-agnostic tools (such as stamp and copy)
+    # will be processed only in the host toolchain.
+    target_toolchain = [
+        target_tool for target_tool in GetTargetToolchain(flavor)
+        if not target_tool.IsPlatformAgnostic()
+    ]
+    host_toolchain = GetHostToolchain(flavor)
+
+    shell = FindFirstInstanceOf(abstract.Shell, host_toolchain)
+    assert shell, 'Host toolchain must provide shell.'
+
+    for target_tool in target_toolchain:
+      MaybeWritePathVariable(master_ninja, target_tool, 'target')
+    for host_tool in host_toolchain:
+      MaybeWritePathVariable(master_ninja, host_tool, 'host')
+    master_ninja.newline()
+
+    for target_tool in target_toolchain:
+      MaybeWriteExtraFlagsVariable(master_ninja, target_tool, 'target', shell)
+    for host_tool in host_toolchain:
+      MaybeWriteExtraFlagsVariable(master_ninja, host_tool, 'host', shell)
+    master_ninja.newline()
+
+    for target_tool in target_toolchain:
+      MaybeWritePool(master_ninja, target_tool, 'target')
+    for host_tool in host_toolchain:
+      MaybeWritePool(master_ninja, host_tool, 'host')
+    master_ninja.newline()
+
+    for target_tool in target_toolchain:
+      MaybeWriteRule(master_ninja, target_tool, 'target')
+    for host_tool in host_toolchain:
+      MaybeWriteRule(master_ninja, host_tool, 'host')
+    master_ninja.newline()
   except NotImplementedError:
     # Fall back to the legacy toolchain.