Import Cobalt 13.103922

Change-Id: I06559c46741bb6050da0b0677b55c71bf6880784
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 4322073..a1f944d 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -1269,7 +1269,9 @@
   // render tree resources either.
   main_web_module_layer_->Reset();
   splash_screen_layer_->Reset();
+#if defined(ENABLE_DEBUG_CONSOLE)
   debug_console_layer_->Reset();
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
 
 #if defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR)
   // Note that the following function call will leak the GPU memory allocated.
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 6f95f5c..2ecdbcf 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -396,8 +396,10 @@
   // Manages the three render trees, combines and renders them.
   RenderTreeCombiner render_tree_combiner_;
   scoped_ptr<RenderTreeCombiner::Layer> main_web_module_layer_;
-  scoped_ptr<RenderTreeCombiner::Layer> debug_console_layer_;
   scoped_ptr<RenderTreeCombiner::Layer> splash_screen_layer_;
+#if defined(ENABLE_DEBUG_CONSOLE)
+  scoped_ptr<RenderTreeCombiner::Layer> debug_console_layer_;
+#endif  // defined(ENABLE_DEBUG_CONSOLE)
 
 #if defined(ENABLE_SCREENSHOT)
   // Helper object to create screen shots of the last layout tree.
diff --git a/src/cobalt/browser/testdata/mtm-demo/README.txt b/src/cobalt/browser/testdata/mtm-demo/README.txt
deleted file mode 100644
index 5368357..0000000
--- a/src/cobalt/browser/testdata/mtm-demo/README.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-These test whether the presence of the CSS MTM filter affect the path taken in
-rendering the video replaced box. <normal.html> has a video tag without the
-filter, while <mtm.html> has a video with the filter applied to it.
-
-To test locally, all 3 of mtm.html, normal.html and progressive.mp4 have to be
-hosted on a server that supports HTTP RANGE header, which is needed for video to
-be loaded correctly. One such server is the extension to the Python
-SimpleHTTPServer at https://github.com/danvk/RangeHTTPServer (run from this
-directory, "cobalt/src/cobalt/browser/testdata/mtm-demo/"):
-
-  $ sudo apt-get install python-pip
-  $ pip install --user rangehttpserver
-  $ python -m RangeHTTPServer
-
-Test video without the MTM filter (should render normally, run from cobalt/src
-directory):
-
-  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/normal.html
-
-Test the video with the MTM filter (should NOT render in its bounding box in the
-document, and in the presence of the correct rasterizer, should be rendered onto
-its own texture off the main UI layout):
-
-  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/mtm.html
-
-There is also the option of using the google cloud utils and the cloud storage
-to publish these files, as is done with the other demos. Care should be taken
-that nothing confidential gets uploaded with these files. "public-read" is
-needed in the ACL because linking and video srcing does not work without it.
-
-A bucket specifically for this demo already exists at "gs://yt-cobalt-mtm-test",
-but uploading to it often might be problematic since buckets are not verison-
-controlled:
-
-  $ gsutil cp -a public-read cobalt/browser/testdata/mtm-demo/normal.html cobalt/browser/testdata/mtm-demo/mtm.html cobalt/browser/testdata/mtm-demo/progressive.mp4 gs://yt-cobalt-mtm-test/
-  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/mtm.html
-  $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/normal.html
diff --git a/src/cobalt/browser/testdata/mtm-demo/mtm.html b/src/cobalt/browser/testdata/mtm-demo/mtm.html
index 6877674..e91a3cc 100644
--- a/src/cobalt/browser/testdata/mtm-demo/mtm.html
+++ b/src/cobalt/browser/testdata/mtm-demo/mtm.html
@@ -1,28 +1,77 @@
 <!DOCTYPE html>
 <html>
+
 <head>
-  <title>mtm Demo</title>
+  <title>Map-To-Mesh Demo</title>
+
   <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 100px;
+    #v {
+      width: 100%;
+      height: 100%;
+      filter: map-to-mesh(equirectangular, 100deg 60deg,
+                          matrix3d(1, 0, 0, 0,
+                                   0, 1, 0, 0,
+                                   0, 0, 1, 0,
+                                   0, 0, 0, 1));
     }
-    .vid {
-      margin: 100px;
-      border: 10px solid blue;
-      width: 960px;
-      height: 540px;
-      filter: -cobalt-mtm(url(projection.msh), 100deg 60deg,
-                              matrix3d(1, 0, 0, 0,
-                                       0, 1, 0, 0,
-                                       0, 0, 1, 0,
-                                       0, 0, 0, 1));
+
+    .instructions {
+      position: absolute;
+      left: 0;
+      background-color: white;
+      color: black;
     }
   </style>
+
+  <script>
+    var degreesPerSecond = 90;
+    // The following mappings are done in this order:
+    // Up, Down, Left, Right
+
+    // Direction keys
+    camera3D.createKeyMapping(38, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
+    camera3D.createKeyMapping(40, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
+    camera3D.createKeyMapping(37, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
+    camera3D.createKeyMapping(39, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
+
+    // DPAD
+    camera3D.createKeyMapping(
+        0x800C, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x800D, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x800E, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x800F, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
+
+    // Left joystick
+    camera3D.createKeyMapping(
+        0x8011, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8012, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8013, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8014, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
+
+    // Right joystick
+    camera3D.createKeyMapping(
+        0x8015, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8016, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8017, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
+    camera3D.createKeyMapping(
+        0x8018, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
+  </script>
 </head>
+
 <body>
-  <div>Mtm Demo!!!</div>
-  <video autoplay loop id="v" class="vid" src="progressive.mp4"></video>
+  <video autoplay loop id="v" src="progressive.mp4"></video>
+  <div class="instructions">
+    Use either the keyboard keys, direction keys, or analog joystick/thumbstick
+    to look around.
+  </div>
 </body>
+
 </html>
diff --git a/src/cobalt/browser/testdata/mtm-demo/progressive.mp4 b/src/cobalt/browser/testdata/mtm-demo/progressive.mp4
deleted file mode 100644
index 2686a9b..0000000
--- a/src/cobalt/browser/testdata/mtm-demo/progressive.mp4
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 36f609f..d4d1d7c 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-103164
\ No newline at end of file
+103922
\ No newline at end of file
diff --git a/src/cobalt/cssom/computed_style.cc b/src/cobalt/cssom/computed_style.cc
index a59143a..a26c451 100644
--- a/src/cobalt/cssom/computed_style.cc
+++ b/src/cobalt/cssom/computed_style.cc
@@ -613,6 +613,8 @@
 class ComputedHeightProvider : public NotReachedPropertyValueVisitor {
  public:
   ComputedHeightProvider(const PropertyValue* parent_computed_height,
+                         const PropertyValue* parent_computed_top,
+                         const PropertyValue* parent_computed_bottom,
                          const LengthValue* computed_font_size,
                          const LengthValue* root_computed_font_size,
                          const math::Size& viewport_size, bool out_of_flow);
@@ -627,6 +629,8 @@
 
  private:
   const PropertyValue* parent_computed_height_;
+  const PropertyValue* parent_computed_top_;
+  const PropertyValue* parent_computed_bottom_;
   const LengthValue* computed_font_size_;
   const LengthValue* root_computed_font_size_;
   const math::Size& viewport_size_;
@@ -639,10 +643,14 @@
 
 ComputedHeightProvider::ComputedHeightProvider(
     const PropertyValue* parent_computed_height,
+    const PropertyValue* parent_computed_top,
+    const PropertyValue* parent_computed_bottom,
     const LengthValue* computed_font_size,
     const LengthValue* root_computed_font_size, const math::Size& viewport_size,
     bool out_of_flow)
     : parent_computed_height_(parent_computed_height),
+      parent_computed_top_(parent_computed_top),
+      parent_computed_bottom_(parent_computed_bottom),
       computed_font_size_(computed_font_size),
       root_computed_font_size_(root_computed_font_size),
       viewport_size_(viewport_size),
@@ -725,7 +733,10 @@
   // (i.e., it depends on content height), and this element is not absolutely
   // positioned, the value computes to "auto".
   //   https://www.w3.org/TR/CSS21/visudet.html#the-height-property
-  computed_height_ = (parent_computed_height_ == auto_value && !out_of_flow_)
+  computed_height_ = (parent_computed_height_ == auto_value &&
+                      (parent_computed_top_ == auto_value ||
+                       parent_computed_bottom_ == auto_value) &&
+                      !out_of_flow_)
                          ? auto_value
                          : percentage;
 }
@@ -2858,7 +2869,9 @@
     } break;
     case kHeightProperty: {
       ComputedHeightProvider height_provider(
-          parent_computed_style_.height().get(), GetFontSize(),
+          parent_computed_style_.height().get(),
+          parent_computed_style_.top().get(),
+          parent_computed_style_.bottom().get(), GetFontSize(),
           GetRootFontSize(), GetViewportSizeOnePercent(),
           IsAbsolutelyPositioned());
       (*value)->Accept(&height_provider);
diff --git a/src/cobalt/dom/url_utils.cc b/src/cobalt/dom/url_utils.cc
index df204d1..57604bd 100644
--- a/src/cobalt/dom/url_utils.cc
+++ b/src/cobalt/dom/url_utils.cc
@@ -14,9 +14,76 @@
 
 #include "cobalt/dom/url_utils.h"
 
+namespace {
+// Returns true if the url can have non-opaque origin.
+// Blob URLs needs to be pre-processed.
+bool NonBlobURLCanHaveTupleOrigin(const GURL& url) {
+  if (url.SchemeIs("https")) {
+    return true;
+  } else if (url.SchemeIs("http")) {
+    return true;
+  } else if (url.SchemeIs("ftp")) {
+    return true;
+  } else if (url.SchemeIs("ws")) {
+    return true;
+  } else if (url.SchemeIs("wss")) {
+    return true;
+  }
+  return false;
+}
+// Returns false if url should be opaque.
+// Otherwise, extract origin tuple from the url and assemble them as a string
+// for easier access and comparison.
+bool NonBlobURLGetOriginStr(const GURL& url, std::string* output) {
+  if (!NonBlobURLCanHaveTupleOrigin(url)) {
+    return false;
+  }
+  *output = url.scheme() + "://" + url.host() +
+            (url.has_port() ? ":" + url.port() : "");
+  return true;
+}
+}  // namespace
+
 namespace cobalt {
 namespace dom {
 
+URLUtils::Origin::Origin() : is_opaque_(true) {}
+
+URLUtils::Origin::Origin(const GURL& url) : is_opaque_(false) {
+  if (url.is_valid() && url.has_scheme() && url.has_host()) {
+    if (url.SchemeIs("blob")) {
+      // Let path_url be the result of parsing URL's path.
+      // Return a new opaque origin, if url is failure, and url's origin
+      // otherwise.
+      GURL path_url(url.path());
+      if (path_url.is_valid() && path_url.has_host() && path_url.has_scheme() &&
+          NonBlobURLGetOriginStr(path_url, &origin_str_)) {
+        return;
+      }
+    } else if (NonBlobURLGetOriginStr(url, &origin_str_)) {
+      // Assign a tuple origin if given url is allowed to have one.
+      return;
+    }
+  }
+  // Othwise, return a new opaque origin.
+  is_opaque_ = true;
+}
+
+std::string URLUtils::Origin::SerializedOrigin() const {
+  if (is_opaque_) {
+    return "null";
+  }
+  return origin_str_;
+}
+
+bool URLUtils::Origin::operator==(const Origin& rhs) const {
+  if (is_opaque_ || rhs.is_opaque_) {
+    return false;
+  } else {
+    return origin_str_ == rhs.origin_str_;
+  }
+}
+
 URLUtils::URLUtils(const GURL& url, bool is_opaque)
     : url_(url), origin_(is_opaque ? Origin() : Origin(url)) {}
 URLUtils::URLUtils(const GURL& url, const UpdateStepsCallback& update_steps,
diff --git a/src/cobalt/dom/url_utils.h b/src/cobalt/dom/url_utils.h
index af3f3ad..aa66825 100644
--- a/src/cobalt/dom/url_utils.h
+++ b/src/cobalt/dom/url_utils.h
@@ -20,36 +20,6 @@
 #include "base/callback.h"
 #include "googleurl/src/gurl.h"
 
-namespace {
-// Returns true if the url can have non-opaque origin.
-// Blob URLs needs to be pre-processed.
-bool NonBlobURLCanHaveTupleOrigin(const GURL& url) {
-  if (url.SchemeIs("https")) {
-    return true;
-  } else if (url.SchemeIs("http")) {
-    return true;
-  } else if (url.SchemeIs("ftp")) {
-    return true;
-  } else if (url.SchemeIs("ws")) {
-    return true;
-  } else if (url.SchemeIs("wss")) {
-    return true;
-  }
-  return false;
-}
-// Returns false if url should be opaque.
-// Otherwise, extract origin tuple from the url and assemble them as a string
-// for easier access and comparison.
-bool NonBlobURLGetOriginStr(const GURL& url, std::string* output) {
-  if (!NonBlobURLCanHaveTupleOrigin(url)) {
-    return false;
-  }
-  *output = url.scheme() + "://" + url.host() +
-            (url.has_port() ? ":" + url.port() : "");
-  return true;
-}
-}  // namespace
-
 namespace cobalt {
 namespace dom {
 
@@ -69,48 +39,19 @@
   class Origin {
    public:
     // To create an opaque origin, use Origin().
-    Origin() : is_opaque_(true) {}
+    Origin();
     // Initialize an origin to the url's origin.
     // https://url.spec.whatwg.org/#concept-url-origin
-    Origin(const GURL& url) : is_opaque_(false) {
-      if (url.is_valid() && url.has_scheme() && url.has_host()) {
-        if (url.SchemeIs("blob")) {
-          // Let path_url be the result of parsing URL's path.
-          // Return a new opaque origin, if url is failure, and url's origin
-          // otherwise.
-          GURL path_url(url.path());
-          if (path_url.is_valid() && path_url.has_host() &&
-              path_url.has_scheme() &&
-              NonBlobURLGetOriginStr(path_url, &origin_str_)) {
-            return;
-          }
-        } else if (NonBlobURLGetOriginStr(url, &origin_str_)) {
-          // Assign a tuple origin if given url is allowed to have one.
-          return;
-        }
-      }
-      // Othwise, return a new opaque origin.
-      is_opaque_ = true;
-    }
+    explicit Origin(const GURL& url);
     // https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin
-    std::string SerializedOrigin() const {
-      if (is_opaque_) {
-        return "null";
-      }
-      return origin_str_;
-    }
+    std::string SerializedOrigin() const;
+    bool is_opaque() const { return is_opaque_; }
     // Only document has an origin and no elements inherit document's origin, so
     // opaque origin comparison can always return false.
     // https://html.spec.whatwg.org/multipage/origin.html#same-origin
-    bool operator==(const Origin& rhs) {
-      if (is_opaque_ || rhs.is_opaque_) {
-        return false;
-      } else {
-        return origin_str_ == rhs.origin_str_;
-      }
-    }
+    bool operator==(const Origin& rhs) const;
     // Returns true if two origins are different(cross-origin).
-    bool operator!=(const Origin& rhs) { return !(*this == rhs); }
+    bool operator!=(const Origin& rhs) const { return !(*this == rhs); }
 
    private:
     bool is_opaque_;
diff --git a/src/cobalt/layout/block_container_box.cc b/src/cobalt/layout/block_container_box.cc
index 80d1edd..557b666 100644
--- a/src/cobalt/layout/block_container_box.cc
+++ b/src/cobalt/layout/block_container_box.cc
@@ -110,8 +110,14 @@
     // it depends on content height), and this element is not absolutely
     // positioned, the value [of "height"] computes to "auto".
     //   https://www.w3.org/TR/CSS21/visudet.html#the-height-property
-    child_layout_params.containing_block_size.set_height(
-        maybe_height.value_or(LayoutUnit()));
+    if (maybe_height) {
+      child_layout_params.containing_block_size.set_height(*maybe_height);
+    } else if (maybe_top && maybe_bottom) {
+      child_layout_params.containing_block_size.set_height(
+          containing_block_size.height() - *maybe_top - *maybe_bottom);
+    } else {
+      child_layout_params.containing_block_size.set_height(LayoutUnit());
+    }
   }
   scoped_ptr<FormattingContext> formatting_context =
       UpdateRectOfInFlowChildBoxes(child_layout_params);
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 38c644f..c8ed055 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -547,21 +547,11 @@
   render_tree::CompositionNode::Builder border_node_builder(border_box_offset);
   AnimateNode::Builder animate_node_builder;
 
-  base::optional<RoundedCorners> rounded_corners = ComputeRoundedCorners();
+  const base::optional<RoundedCorners> rounded_corners =
+      ComputeRoundedCorners();
 
-  // If we have rounded corners and a non-zero border, then we need to compute
-  // the "inner" rounded corners, as the ones specified by CSS apply to the
-  // outer border edge.
-  base::optional<RoundedCorners> padding_rounded_corners_if_different;
-  if (rounded_corners && !border_insets_.zero()) {
-    padding_rounded_corners_if_different = rounded_corners->Inset(math::InsetsF(
-        border_insets_.left().toFloat(), border_insets_.top().toFloat(),
-        border_insets_.right().toFloat(), border_insets_.bottom().toFloat()));
-  }
-  const base::optional<RoundedCorners>& padding_rounded_corners =
-      padding_rounded_corners_if_different
-          ? padding_rounded_corners_if_different
-          : rounded_corners;
+  const base::optional<RoundedCorners> padding_rounded_corners =
+      ComputePaddingRoundedCorners(rounded_corners);
 
   // The painting order is:
   // - background color.
@@ -594,8 +584,8 @@
     }
     RenderAndAnimateBorder(rounded_corners, &border_node_builder,
                            &animate_node_builder);
-    RenderAndAnimateBoxShadow(rounded_corners, &border_node_builder,
-                              &animate_node_builder);
+    RenderAndAnimateBoxShadow(rounded_corners, padding_rounded_corners,
+                              &border_node_builder, &animate_node_builder);
   }
 
   const bool overflow_hidden =
@@ -744,8 +734,7 @@
 
   if (rounded_corners) {
     rect_node_builder->rounded_corners =
-        scoped_ptr<RoundedCorners>(new RoundedCorners(
-            rounded_corners->Normalize(rect_node_builder->rect)));
+        scoped_ptr<RoundedCorners>(new RoundedCorners(*rounded_corners));
   }
 }
 
@@ -834,8 +823,7 @@
 
   if (rounded_corners) {
     rect_node_builder->rounded_corners =
-        scoped_ptr<RoundedCorners>(new RoundedCorners(
-            rounded_corners->Normalize(rect_node_builder->rect)));
+        scoped_ptr<RoundedCorners>(new RoundedCorners(*rounded_corners));
   }
 }
 
@@ -1225,7 +1213,7 @@
 
 }  // namespace
 
-base::optional<render_tree::RoundedCorners> Box::ComputeRoundedCorners() {
+base::optional<render_tree::RoundedCorners> Box::ComputeRoundedCorners() const {
   UsedBorderRadiusProvider border_radius_provider(GetBorderBoxSize());
   render_tree::RoundedCorner border_top_left_radius;
   render_tree::RoundedCorner border_top_right_radius;
@@ -1269,13 +1257,36 @@
     rounded_corners.emplace(border_top_left_radius, border_top_right_radius,
                             border_bottom_right_radius,
                             border_bottom_left_radius);
+    rounded_corners =
+        rounded_corners->Normalize(math::RectF(GetBorderBoxSize()));
   }
 
   return rounded_corners;
 }
 
+base::optional<render_tree::RoundedCorners> Box::ComputePaddingRoundedCorners(
+    const base::optional<RoundedCorners>& rounded_corners) const {
+  base::optional<RoundedCorners> padding_rounded_corners_if_different;
+
+  if (rounded_corners && !border_insets_.zero()) {
+    // If we have rounded corners and a non-zero border, then we need to
+    // compute the "inner" rounded corners, as the ones specified by CSS apply
+    // to the outer border edge.
+    padding_rounded_corners_if_different = rounded_corners->Inset(math::InsetsF(
+        border_insets_.left().toFloat(), border_insets_.top().toFloat(),
+        border_insets_.right().toFloat(), border_insets_.bottom().toFloat()));
+  }
+
+  const base::optional<RoundedCorners>& padding_rounded_corners =
+      padding_rounded_corners_if_different
+          ? padding_rounded_corners_if_different
+          : rounded_corners;
+  return padding_rounded_corners;
+}
+
 void Box::RenderAndAnimateBoxShadow(
-    const base::optional<RoundedCorners>& rounded_corners,
+    const base::optional<RoundedCorners>& outer_rounded_corners,
+    const base::optional<RoundedCorners>& inner_rounded_corners,
     CompositionNode::Builder* border_node_builder,
     AnimateNode::Builder* animate_node_builder) {
   UNREFERENCED_PARAMETER(animate_node_builder);
@@ -1327,9 +1338,13 @@
       render_tree::RectShadowNode::Builder shadow_builder(
           math::RectF(rect_offset, shadow_rect_size), shadow,
           shadow_value->has_inset(), spread_radius);
-      if (rounded_corners) {
-        shadow_builder.rounded_corners = rounded_corners->Normalize(
-            shadow_builder.rect);
+
+      if (outer_rounded_corners) {
+        if (shadow_value->has_inset()) {
+          shadow_builder.rounded_corners = inner_rounded_corners;
+        } else {
+          shadow_builder.rounded_corners = outer_rounded_corners;
+        }
       }
 
       // Finally, create our shadow node.
@@ -1484,8 +1499,7 @@
         // Apply rounded viewport filter to the background image.
         FilterNode::Builder filter_node_builder(background_node);
         filter_node_builder.viewport_filter =
-            ViewportFilter(image_frame,
-                           rounded_corners->Normalize(image_frame));
+            ViewportFilter(image_frame, *rounded_corners);
         background_node = new FilterNode(filter_node_builder);
       }
 
@@ -1548,18 +1562,11 @@
 scoped_refptr<render_tree::Node> Box::RenderAndAnimateOverflow(
     const scoped_refptr<render_tree::Node>& content_node,
     const math::Vector2dF& border_offset) {
-  base::optional<RoundedCorners> rounded_corners = ComputeRoundedCorners();
+  const base::optional<RoundedCorners> rounded_corners =
+      ComputeRoundedCorners();
 
-  base::optional<RoundedCorners> padding_rounded_corners_if_different;
-  if (rounded_corners && !border_insets_.zero()) {
-    padding_rounded_corners_if_different = rounded_corners->Inset(math::InsetsF(
-        border_insets_.left().toFloat(), border_insets_.top().toFloat(),
-        border_insets_.right().toFloat(), border_insets_.bottom().toFloat()));
-  }
-  const base::optional<RoundedCorners>& padding_rounded_corners =
-      padding_rounded_corners_if_different
-          ? padding_rounded_corners_if_different
-          : rounded_corners;
+  const base::optional<RoundedCorners> padding_rounded_corners =
+      ComputePaddingRoundedCorners(rounded_corners);
 
   return RenderAndAnimateOverflow(padding_rounded_corners, content_node, NULL,
                                   border_offset);
@@ -1584,9 +1591,7 @@
                   border_node_offset.y() + border_top_width().toFloat(),
                   padding_size.width(), padding_size.height()));
   if (rounded_corners) {
-    filter_node_builder.viewport_filter->set_rounded_corners(
-        rounded_corners->Normalize(
-            filter_node_builder.viewport_filter->viewport()));
+    filter_node_builder.viewport_filter->set_rounded_corners(*rounded_corners);
   }
 
   return scoped_refptr<render_tree::Node>(new FilterNode(filter_node_builder));
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 67056ab..dda5300 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -699,8 +699,13 @@
   // Updates used values of "padding" properties.
   void UpdatePaddings(const LayoutParams& layout_params);
 
-  // Computes the rounded corners (if there are any) from the border radii.
-  base::optional<render_tree::RoundedCorners> ComputeRoundedCorners();
+  // Computes the normalized "outer" rounded corners (if there are any) from the
+  // border radii.
+  base::optional<render_tree::RoundedCorners> ComputeRoundedCorners() const;
+
+  // Computes the corresponding "inner" rounded corners.
+  base::optional<render_tree::RoundedCorners> ComputePaddingRoundedCorners(
+      const base::optional<render_tree::RoundedCorners>& rounded_corners) const;
 
   // Called after TryPlaceEllipsisOrProcessPlacedEllipsis() determines that the
   // box is impacted by the ellipsis. This handles both determining the location
@@ -736,7 +741,8 @@
   RenderAndAnimateBackgroundImageResult RenderAndAnimateBackgroundImage(
       const base::optional<render_tree::RoundedCorners>& rounded_corners);
   void RenderAndAnimateBoxShadow(
-      const base::optional<render_tree::RoundedCorners>& rounded_corners,
+      const base::optional<render_tree::RoundedCorners>& outer_rounded_corners,
+      const base::optional<render_tree::RoundedCorners>& inner_rounded_corners,
       render_tree::CompositionNode::Builder* border_node_builder,
       render_tree::animations::AnimateNode::Builder* animate_node_builder);
 
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom-expected.png
new file mode 100644
index 0000000..6b01be2
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom.html b/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom.html
new file mode 100644
index 0000000..3e23d27
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+ | If the height of the child is a percent of the height of the containing
+ | block and the containing block's height is produced from top and bottom,
+ | the height of the child should be properly calculated.
+ |   https://www.w3.org/TR/CSS21/visudet.html#the-height-property
+ |   https://www.w3.org/TR/CSS21/visuren.html#propdef-top
+ |   https://www.w3.org/TR/CSS21/visuren.html#propdef-bottom
+ -->
+<html>
+<head>
+  <style">
+    body {
+      background-color: #FFFFFF;
+    }
+    .container {
+      position: absolute;
+      height: 300px;
+      width: 100px;
+    }
+    .top-bottom {
+      background-color: #EA4335;
+      position: absolute;
+      top: 10px;
+      bottom: 0px;
+      left: 10px;
+      width: 100px;
+    }
+    .percent-height {
+      background-color: #4285F4;
+      height: 100%;
+      position: relative;
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <div class="top-bottom">
+      <div class="percent-height"></div>
+    </div>
+  </div>
+</body>
+</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 995b02a..37e4c1e 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
@@ -43,6 +43,7 @@
 10-4-min-width-and-max-width-percentage-should-refer-containing-block-width
 10-5-percentage-of-auto-height-on-absolute-element-should-compute-to-percentage
 10-5-percentage-of-auto-height-should-compute-to-auto
+10-5-percentage-of-height-should-work-properly-with-top-and-bottom
 10-6-1-content-height-of-inline-boxes-matches-font-size
 10-6-2-replaced-box-height
 10-8-1-inline-box-with-non-normal-line-height-should-use-first-available-font-for-font-metrics
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-inset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-inset-shadow.html
new file mode 100644
index 0000000..a46b00a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-inset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 2 values for the border radius for a box with a background color,
+ | a border and an inset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px;
+      box-shadow: 10px 10px #FF7F50 inset;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-outset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-outset-shadow.html
new file mode 100644
index 0000000..1cc1359
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-outset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 2 values for the border radius for a box with a background color,
+ | a border and an outset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px;
+      box-shadow: 10px 10px #FF7F50;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border.html
new file mode 100644
index 0000000..63e2826
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-2-values-with-background-color-and-border.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+ | Setting 2 values for the border radius for a box with a background color
+ | and a border.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-inset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-inset-shadow.html
new file mode 100644
index 0000000..93ab665
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-inset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 3 values for the border radius for a box with a background color,
+ | a border and in inset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 10px 120px 50px;
+      box-shadow: 10px 10px #FF7F50 inset;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-outset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-outset-shadow.html
new file mode 100644
index 0000000..3e5517a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-outset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 3 values for the border radius for a box with a background color,
+ | a border and in outset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 10px 120px 50px;
+      box-shadow: 10px 10px #FF7F50;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border.html
new file mode 100644
index 0000000..7d26761
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-3-values-with-background-color-and-border.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+ | Setting 3 values for the border radius for a box with a background color
+ | and a border.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 10px 120px 50px;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-inset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-inset-shadow.html
new file mode 100644
index 0000000..4ec00df
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-inset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color,
+ | a border and an inset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 120px 40px 70px;
+      box-shadow: 10px 10px #FF7F50 inset;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-outset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-outset-shadow.html
new file mode 100644
index 0000000..256fe37
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-outset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color,
+ | a border and an outset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 120px 40px 70px;
+      box-shadow: 10px 10px #FF7F50;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border.html
new file mode 100644
index 0000000..c419580
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-4-values-with-background-color-and-border.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color
+ | and a border.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 120px 40px 70px;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-inset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-inset-shadow.html
new file mode 100644
index 0000000..b969b0a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-inset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color,
+ | a border and an inset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px 0px 75px;
+      box-shadow: 10px 10px #FF7F50 inset;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-outset-shadow.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-outset-shadow.html
new file mode 100644
index 0000000..3ec1d92
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-outset-shadow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color,
+ | a border and an outset shadow.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px 0px 75px;
+      box-shadow: 10px 10px #FF7F50;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border.html b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border.html
new file mode 100644
index 0000000..390ecf9
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+ | Setting 4 values for the border radius for a box with a background color
+ | and a border.
+ | Note that this will not look the exact same in Chrome since Cobalt
+ | normalizes opposing corners and Chrome does not.
+ |   https://www.w3.org/TR/css3-background/#border-radius
+ -->
+<html>
+<head>
+  <style>
+    div {
+      border: 25px solid rgb(255,192,203);
+      background-color: rgb(176,176,176);
+      border-radius: 50px 100px 0px 75px;
+      height: 200px;
+      width: 100px;
+    }
+  </style>
+</head>
+<body>
+  <div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index 50f1699..52a5f6a 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -19,11 +19,7 @@
 #include "base/debug/trace_event.h"
 #include "cobalt/loader/image/dummy_gif_image_decoder.h"
 #include "cobalt/loader/image/image_decoder_starboard.h"
-#if defined(USE_PS3_JPEG_IMAGE_DECODER)
-#include "cobalt/loader/image/jpeg_image_decoder_ps3.h"
-#else  // defined(USE_PS3_JPEG_IMAGE_DECODER)
 #include "cobalt/loader/image/jpeg_image_decoder.h"
-#endif  // defined(USE_PS3_JPEG_IMAGE_DECODER)
 #include "cobalt/loader/image/png_image_decoder.h"
 #include "cobalt/loader/image/stub_image_decoder.h"
 #include "cobalt/loader/image/webp_image_decoder.h"
@@ -336,13 +332,8 @@
     return make_scoped_ptr<ImageDataDecoder>(
         new StubImageDecoder(resource_provider));
   } else if (image_type == ImageDecoder::kImageTypeJPEG) {
-#if defined(USE_PS3_JPEG_IMAGE_DECODER)
-    return make_scoped_ptr<ImageDataDecoder>(
-        new JPEGImageDecoderPS3(resource_provider));
-#else   // defined(USE_PS3_JPEG_IMAGE_DECODER)
     return make_scoped_ptr<ImageDataDecoder>(
         new JPEGImageDecoder(resource_provider));
-#endif  // defined(USE_PS3_JPEG_IMAGE_DECODER)
   } else if (image_type == ImageDecoder::kImageTypePNG) {
     return make_scoped_ptr<ImageDataDecoder>(
         new PNGImageDecoder(resource_provider));
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 9a50b4e..1ab0435 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -97,12 +97,6 @@
         'embed_resources_as_header_files',
       ],
       'conditions': [
-        ['target_arch == "ps3"', {
-          'sources': [
-            'image/jpeg_image_decoder_ps3.cc',
-            'image/jpeg_image_decoder_ps3.h',
-          ],
-        }],
         ['enable_about_scheme == 1', {
           'defines': [ 'ENABLE_ABOUT_SCHEME' ],
           'sources': [
diff --git a/src/cobalt/render_tree/rounded_corners.cc b/src/cobalt/render_tree/rounded_corners.cc
index 7eacb19..8149cd4 100644
--- a/src/cobalt/render_tree/rounded_corners.cc
+++ b/src/cobalt/render_tree/rounded_corners.cc
@@ -29,6 +29,7 @@
 }
 
 RoundedCorners RoundedCorners::Normalize(const math::RectF& rect) const {
+  const float kEpsilon = 0.0001f;
   float scale = 1.0f;
   float size;
 
@@ -38,27 +39,28 @@
   size = top_left.horizontal +
          std::max(top_right.horizontal, bottom_right.horizontal);
   if (size > rect.width()) {
-    scale = rect.width() / size;
+    scale = (rect.width() - kEpsilon) / size;
   }
 
   size = bottom_left.horizontal +
          std::max(bottom_right.horizontal, top_right.horizontal);
   if (size > rect.width()) {
-    scale = std::min(rect.width() / size, scale);
+    scale = std::min((rect.width() - kEpsilon) / size, scale);
   }
 
-  size = top_left.vertical +
-         std::max(bottom_left.vertical, bottom_right.vertical);
+  size =
+      top_left.vertical + std::max(bottom_left.vertical, bottom_right.vertical);
   if (size > rect.height()) {
-    scale = std::min(rect.height() / size, scale);
+    scale = std::min((rect.height() - kEpsilon) / size, scale);
   }
 
   size = top_right.vertical +
          std::max(bottom_right.vertical, bottom_left.vertical);
   if (size > rect.height()) {
-    scale = std::min(rect.height() / size, scale);
+    scale = std::min((rect.height() - kEpsilon) / size, scale);
   }
 
+  scale = std::max(scale, 0.0f);
   return Scale(scale, scale);
 }
 
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 603eb8e..6b7ba0c 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -640,6 +640,11 @@
 
   backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
       graphics_context_, render_target_egl);
+  // Make sure the render target's framebuffer is bound before continuing.
+  // Skia will usually do this, but it is possible for some render trees to
+  // have non-skia draw calls only, in which case this needs to be done.
+  GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER,
+                            render_target_egl->GetPlatformHandle()));
 
   // First reset the graphics context state for the pending render tree
   // draw calls, in case we have modified state in between.
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index a8548ea..057bb38 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -53,7 +53,7 @@
 // changes. It is reasonable to base a port on the Release Candidate API
 // version, but be aware that small incompatible changes may still be made to
 // it.
-#define SB_RELEASE_CANDIDATE_API_VERSION 6
+#define SB_RELEASE_CANDIDATE_API_VERSION 7
 
 // --- Experimental Feature Defines ------------------------------------------
 
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index ea8d300..212a904 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Google Inc. All Rights Reserved.
+# 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.
@@ -80,5 +80,7 @@
         test_filter.TestFilter(
             'web_platform_tests', 'xhr/WebPlatformTest.Run/125', 'debug'),
         test_filter.TestFilter(
-            'web_platform_tests', 'streams/WebPlatformTest.Run/11', 'debug')
+            'web_platform_tests', 'streams/WebPlatformTest.Run/11', 'debug'),
+        test_filter.TestFilter(
+            'starboard_platform_tests', test_filter.FILTER_ALL)
     ]
diff --git a/src/starboard/linux/x64directfb/__init__.py b/src/starboard/linux/x64directfb/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/linux/x64directfb/__init__.py
diff --git a/src/starboard/linux/x64directfb/future/gyp_configuration.py b/src/starboard/linux/x64directfb/future/gyp_configuration.py
index 1074bc3..446f822 100644
--- a/src/starboard/linux/x64directfb/future/gyp_configuration.py
+++ b/src/starboard/linux/x64directfb/future/gyp_configuration.py
@@ -16,7 +16,7 @@
 import logging
 
 # Import the shared Linux platform configuration.
-from starboard.linux.shared import gyp_configuration
+import starboard.linux.x64directfb.gyp_configuration as gyp_configuration
 
 
 def CreatePlatformConfig():
diff --git a/src/starboard/linux/x64directfb/gyp_configuration.py b/src/starboard/linux/x64directfb/gyp_configuration.py
index 7e60294..483e5dd 100644
--- a/src/starboard/linux/x64directfb/gyp_configuration.py
+++ b/src/starboard/linux/x64directfb/gyp_configuration.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Google Inc. All Rights Reserved.
+# 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.
@@ -16,17 +16,45 @@
 import logging
 
 # Import the shared Linux platform configuration.
-from starboard.linux.shared import gyp_configuration
+import starboard.linux.shared.gyp_configuration as gyp_configuration
+import starboard.tools.testing.test_filter as test_filter
 
 
 def CreatePlatformConfig():
   try:
-    return gyp_configuration.PlatformConfig(
-        'linux-x64directfb',
-        # Unfortunately, some memory leaks outside of our control, and difficult
-        # to pattern match with ASAN's suppression list, appear in DirectFB
-        # builds, and so this must be disabled.
-        asan_enabled_by_default=False)
+    return PlatformConfig(
+        'linux-x64directfb')
   except RuntimeError as e:
     logging.critical(e)
     return None
+
+
+class PlatformConfig(gyp_configuration.PlatformConfig):
+
+  # Unfortunately, some memory leaks outside of our control, and difficult
+  # to pattern match with ASAN's suppression list, appear in DirectFB
+  # builds, and so this must be disabled.
+  def __init__(self, platform, asan_enabled_by_default=False,
+               goma_supported_by_compiler=True):
+    super(PlatformConfig, self).__init__(
+        platform, asan_enabled_by_default, goma_supported_by_compiler)
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return [
+        test_filter.TestFilter(
+            'bindings_test', ('GlobalInterfaceBindingsTest.'
+                              'PropertiesAndOperationsAreOwnProperties')),
+        test_filter.TestFilter(
+            'net_unittests', 'HostResolverImplDnsTest.DnsTaskUnspec'),
+        test_filter.TestFilter(
+            'web_platform_tests', 'xhr/WebPlatformTest.Run/125', 'debug'),
+        test_filter.TestFilter(
+            'web_platform_tests', 'streams/WebPlatformTest.Run/11', 'debug'),
+        test_filter.TestFilter(
+            'starboard_platform_tests', test_filter.FILTER_ALL, 'debug')
+    ]
diff --git a/src/starboard/linux/x64x11/gyp_configuration.py b/src/starboard/linux/x64x11/gyp_configuration.py
index 6685818..9b4962f 100644
--- a/src/starboard/linux/x64x11/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/gyp_configuration.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Google Inc. All Rights Reserved.
+# 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.
@@ -16,6 +16,7 @@
 import logging
 
 from starboard.linux.shared import gyp_configuration
+import starboard.tools.testing.test_filter as test_filter
 from starboard.tools.toolchain import ar
 from starboard.tools.toolchain import bash
 from starboard.tools.toolchain import clang
@@ -49,6 +50,26 @@
         bash.Shell(),
     ]
 
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return [
+        test_filter.TestFilter(
+            'bindings_test', ('GlobalInterfaceBindingsTest.'
+                              'PropertiesAndOperationsAreOwnProperties')),
+        test_filter.TestFilter(
+            'net_unittests', 'HostResolverImplDnsTest.DnsTaskUnspec'),
+        test_filter.TestFilter(
+            'nplb_blitter_pixel_tests', test_filter.FILTER_ALL),
+        test_filter.TestFilter(
+            'web_platform_tests', 'xhr/WebPlatformTest.Run/125', 'debug'),
+        test_filter.TestFilter(
+            'web_platform_tests', 'streams/WebPlatformTest.Run/11', 'debug'),
+    ]
+
 
 def CreatePlatformConfig():
   try:
diff --git a/src/starboard/linux/x64x11/mock/gyp_configuration.py b/src/starboard/linux/x64x11/mock/gyp_configuration.py
index ff41cea..fa14e09 100644
--- a/src/starboard/linux/x64x11/mock/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/mock/gyp_configuration.py
@@ -17,7 +17,7 @@
 
 import config.starboard
 import gyp_utils
-
+import starboard.tools.testing.test_filter as test_filter
 
 def CreatePlatformConfig():
   try:
@@ -56,3 +56,25 @@
         'CXX': self.host_compiler_environment['CXX_host'],
     })
     return env_variables
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return [
+        test_filter.TestFilter(
+            'bindings_test', ('GlobalInterfaceBindingsTest.'
+                              'PropertiesAndOperationsAreOwnProperties')),
+        test_filter.TestFilter(
+            'net_unittests', 'HostResolverImplDnsTest.DnsTaskUnspec'),
+        test_filter.TestFilter(
+            'nplb_blitter_pixel_tests', test_filter.FILTER_ALL),
+        test_filter.TestFilter(
+            'web_platform_tests', 'xhr/WebPlatformTest.Run/125', 'debug'),
+        test_filter.TestFilter(
+            'web_platform_tests', 'streams/WebPlatformTest.Run/11', 'debug'),
+        test_filter.TestFilter(
+            'starboard_platform_tests', test_filter.FILTER_ALL)
+    ]
diff --git a/src/starboard/raspi/shared/gyp_configuration.py b/src/starboard/raspi/shared/gyp_configuration.py
index f288584..a750f06 100644
--- a/src/starboard/raspi/shared/gyp_configuration.py
+++ b/src/starboard/raspi/shared/gyp_configuration.py
@@ -19,6 +19,7 @@
 
 import config.starboard
 import gyp_utils
+import starboard.tools.testing.test_filter as test_filter
 
 
 class RaspiPlatformConfig(config.starboard.PlatformConfigStarboard):
@@ -64,3 +65,44 @@
         'CXX': os.path.join(toolchain_bin_dir, 'arm-linux-gnueabihf-g++'),
     })
     return env_variables
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return [
+        # Fails with SpiderMonkey.
+        test_filter.TestFilter(
+            'bindings_test', ('GlobalInterfaceBindingsTest.'
+                              'PropertiesAndOperationsAreOwnProperties')),
+        test_filter.TestFilter(
+            'net_unittests', 'HostResolverImplDnsTest.DnsTaskUnspec'),
+
+        # The RasPi test devices don't have access to an IPV6 network, so
+        # disable the related tests.
+        test_filter.TestFilter(
+            'nplb', 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.'
+                    'SunnyDayDestination/1'),
+        test_filter.TestFilter(
+            'nplb', 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.'
+                    'SunnyDaySourceForDestination/1'),
+        test_filter.TestFilter(
+            'nplb', 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.'
+                    'SunnyDaySourceNotLoopback/1'),
+
+        # These tests are currently producing slightly different images on the
+        # RasPi.
+        test_filter.TestFilter(
+            'renderer_test', 'PixelTest.CircularSubPixelBorder'),
+        test_filter.TestFilter(
+            'renderer_test', 'PixelTest.FilterBlurred100PxText'),
+
+        test_filter.TestFilter('starboard_platform_tests',
+                               test_filter.FILTER_ALL),
+        test_filter.TestFilter('nplb_blitter_pixel_tests',
+                               test_filter.FILTER_ALL),
+        test_filter.TestFilter('web_platform_tests', test_filter.FILTER_ALL)
+
+    ]
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 7138894..6fd98c6 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
@@ -94,20 +94,8 @@
   if (frames_.empty()) {
     return last_displayed_frame_;
   }
-  // Remove any frames with timestamps earlier than |media_time|, but always
-  // keep at least one of the frames.
-  while (frames_.size() > 1 && frames_.front()->pts() < media_time) {
-    if (frames_.front() != last_displayed_frame_) {
-      ++dropped_frames_;
-    }
-    frames_.pop_front();
-  }
 
-  if (audio_eos_reached) {
-    while (frames_.size() > 1) {
-      frames_.pop_back();
-    }
-  }
+  AdvanceTime(media_time, audio_eos_reached);
 
   last_displayed_frame_ = frames_.front();
   return last_displayed_frame_;
@@ -157,11 +145,30 @@
   need_more_input_ = (status == VideoDecoder::kNeedMoreInput);
 }
 
+void VideoRendererImpl::AdvanceTime(
+    SbMediaTime media_time, bool audio_eos_reached) {
+  while (frames_.size() > 1 && frames_.front()->pts() < media_time) {
+    if (frames_.front() != last_displayed_frame_) {
+      ++dropped_frames_;
+    }
+    frames_.pop_front();
+  }
+
+  if (audio_eos_reached) {
+    while (frames_.size() > 1) {
+      frames_.pop_back();
+    }
+  }
+}
+
 SbDecodeTarget VideoRendererImpl::GetCurrentDecodeTarget(
     SbMediaTime media_time,
     bool audio_eos_reached) {
-  SB_UNREFERENCED_PARAMETER(media_time);
-  SB_UNREFERENCED_PARAMETER(audio_eos_reached);
+  {
+    ScopedLock lock(mutex_);
+    AdvanceTime(media_time, audio_eos_reached);
+  }
+
   if (decoder_) {
     return decoder_->GetCurrentDecodeTarget();
   } else {
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 1dfdad6..675dea8 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
@@ -74,6 +74,9 @@
   //    no longer accept more data.
   static const size_t kMaxCachedFrames = 12;
 
+  // Advances the clock, potentially dropping any expired frames.
+  void AdvanceTime(SbMediaTime media_time, bool audio_eos_reached);
+
   // VideoDecoder::Host method.
   void OnDecoderStatusUpdate(VideoDecoder::Status status,
                              const scoped_refptr<VideoFrame>& frame)
diff --git a/src/starboard/shared/uwp/analog_thumbstick_input.cc b/src/starboard/shared/uwp/analog_thumbstick_input.cc
new file mode 100644
index 0000000..e4ab130
--- /dev/null
+++ b/src/starboard/shared/uwp/analog_thumbstick_input.cc
@@ -0,0 +1,100 @@
+/*

+ * Copyright 2017 Google Inc. All Rights Reserved.

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

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

+

+#include <Windows.Gaming.Input.h>

+#include <algorithm>

+

+#include "starboard/log.h"

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

+#include "starboard/types.h"

+

+#pragma comment(lib, "xinput9_1_0.lib")

+

+using Windows::Foundation::Collections::IVectorView;

+using Windows::Gaming::Input::Gamepad;

+using Windows::Gaming::Input::GamepadReading;

+using Windows::Gaming::Input::RawGameController;

+

+namespace starboard {

+namespace shared {

+namespace uwp {

+namespace {

+

+const int kMaxPlayerCounter = 4;

+const float kDeadZoneThreshold = .24f;

+

+float ApplyLinearDeadZone(float value, float maxValue, float deadZoneSize) {

+  if (value < -deadZoneSize) {

+    // Increase negative values to remove the deadzone discontinuity.

+    value += deadZoneSize;

+  } else if (value > deadZoneSize) {

+    // Decrease positive values to remove the deadzone discontinuity.

+    value -= deadZoneSize;

+  } else {

+    // Values inside the deadzone come out zero.

+    return 0;

+  }

+

+  // Scale into 0-1 range.

+  float scaledValue = value / (maxValue - deadZoneSize);

+  return std::max(-1.f, std::min(scaledValue, 1.f));

+}

+

+void ApplyStickDeadZone(float x,

+                        float y,

+                        float max_value,

+                        float dead_zone_size,

+                        float* result_x,

+                        float* result_y) {

+  *result_x = ApplyLinearDeadZone(x, max_value, dead_zone_size);

+  *result_y = ApplyLinearDeadZone(y, max_value, dead_zone_size);

+}

+

+ThumbSticks ReadThumbStick(Gamepad ^ controller) {

+  ThumbSticks output;

+  GamepadReading reading = controller->GetCurrentReading();

+

+  ApplyStickDeadZone(static_cast<float>(reading.LeftThumbstickX),

+                     static_cast<float>(reading.LeftThumbstickY), 1.f,

+                     kDeadZoneThreshold, &output.left_x, &output.left_y);

+

+  ApplyStickDeadZone(static_cast<float>(reading.RightThumbstickX),

+                     static_cast<float>(reading.RightThumbstickY), 1.f,

+                     kDeadZoneThreshold, &output.right_x, &output.right_y);

+  return output;

+}

+}  // namespace

+

+void GetGamepadThumbSticks(std::vector<ThumbSticks>* destination) {

+  destination->erase(destination->begin(), destination->end());

+

+  // This profiled to an average time of 33us to execute.

+  IVectorView<Gamepad ^> ^ gamepads = Gamepad::Gamepads;

+

+  // This profiled to take on average 9us of time to read controller state.

+  const uint32_t n = gamepads->Size;

+  for (uint32_t i = 0; i < n; ++i) {

+    Gamepad ^ gamepad = gamepads->GetAt(i);

+    ThumbSticks thumb_stick = ReadThumbStick(gamepad);

+    destination->push_back(thumb_stick);

+  }

+}

+

+}  // namespace uwp

+}  // namespace shared

+}  // namespace starboard

diff --git a/src/starboard/shared/uwp/analog_thumbstick_input.h b/src/starboard/shared/uwp/analog_thumbstick_input.h
new file mode 100644
index 0000000..490bc23
--- /dev/null
+++ b/src/starboard/shared/uwp/analog_thumbstick_input.h
@@ -0,0 +1,44 @@
+/*

+ * Copyright 2017 Google Inc. All Rights Reserved.

+ *

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

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

+ * You may obtain a copy of the License at

+ *

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

+ *

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

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

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+#ifndef STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_H_

+#define STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_H_

+

+#include <vector>

+

+#include "starboard/types.h"

+

+namespace starboard {

+namespace shared {

+namespace uwp {

+

+struct ThumbSticks {

+  float left_x = 0.0f;

+  float left_y = 0.0f;

+  float right_x = 0.0f;

+  float right_y = 0.0f;

+};

+

+// Reads all connected game pads and stores the joystick states in the

+// destination vector. Note that the destination vector is unconditionally

+// cleared before being populated.

+void GetGamepadThumbSticks(std::vector<ThumbSticks>* destination);

+

+}  // namespace uwp

+}  // namespace shared

+}  // namespace starboard

+

+#endif  // STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_H_

diff --git a/src/starboard/shared/uwp/analog_thumbstick_input_thread.cc b/src/starboard/shared/uwp/analog_thumbstick_input_thread.cc
new file mode 100644
index 0000000..6ce605a
--- /dev/null
+++ b/src/starboard/shared/uwp/analog_thumbstick_input_thread.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "starboard/shared/uwp/analog_thumbstick_input_thread.h"
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "starboard/double.h"
+#include "starboard/shared/uwp/analog_thumbstick_input.h"
+#include "starboard/shared/win32/simple_thread.h"
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace shared {
+namespace uwp {
+
+using starboard::shared::win32::SimpleThread;
+
+class AnalogThumbstickThread::Impl : public SimpleThread {
+ public:
+  explicit Impl(Callback* cb) : SimpleThread("AnalogGamepad"), callback_(cb) {
+    stick_is_centered_[kSbKeyGamepadLeftStickLeft] = true;
+    stick_is_centered_[kSbKeyGamepadRightStickLeft] = true;
+    stick_is_centered_[kSbKeyGamepadLeftStickUp] = true;
+    stick_is_centered_[kSbKeyGamepadRightStickUp] = true;
+
+    SimpleThread::Start();
+  }
+  ~Impl() { SimpleThread::Join(); }
+
+  void Run() override {
+    while (!join_called()) {
+      Update();
+      // 120hz to provide smooth 60fps playback.
+      SbThreadSleep(kSbTimeSecond / kPollingFrequency);
+    }
+  }
+
+  void Update() {
+    ThumbSticks thumb_state = GetCombinedThumbStickState();
+
+    FireEventIfNecessary(kSbKeyGamepadLeftStickLeft, thumb_state.left_x);
+    FireEventIfNecessary(kSbKeyGamepadLeftStickUp, thumb_state.left_y);
+    FireEventIfNecessary(kSbKeyGamepadRightStickLeft, thumb_state.right_x);
+    FireEventIfNecessary(kSbKeyGamepadRightStickUp, thumb_state.right_y);
+  }
+
+  void FireEventIfNecessary(SbKey sb_key, float value) {
+    if (value == 0.0f) {
+      if (stick_is_centered_[sb_key]) {
+        // The previous stick input is in center position, so it is not
+        // necessary to inject another center input event.
+        return;
+      }
+      stick_is_centered_[sb_key] = true;
+    } else {
+      stick_is_centered_[sb_key] = false;
+    }
+
+    SbInputVector input_vector = {0, 0};
+
+    switch (sb_key) {
+      case kSbKeyGamepadRightStickLeft:
+      case kSbKeyGamepadLeftStickLeft: {
+        input_vector.x = value;
+        break;
+      }
+      case kSbKeyGamepadRightStickUp:
+      case kSbKeyGamepadLeftStickUp: {
+        input_vector.y = -value;
+        break;
+      }
+      default: {
+        SB_NOTREACHED();
+        break;
+      }
+    }
+    callback_->OnJoystickUpdate(sb_key, input_vector);
+  }
+
+  ThumbSticks GetCombinedThumbStickState() {
+    ThumbSticks all_thumb_state;
+    GetGamepadThumbSticks(&thumb_sticks_);
+
+    for (int i = 0; i < thumb_sticks_.size(); ++i) {
+      ThumbSticks thumb_state = thumb_sticks_[i];
+
+      all_thumb_state.left_x += thumb_state.left_x;
+      all_thumb_state.left_y += thumb_state.left_y;
+      all_thumb_state.right_x += thumb_state.right_x;
+      all_thumb_state.right_y += thumb_state.right_y;
+    }
+
+    all_thumb_state.left_x = ClampToZeroOne(all_thumb_state.left_x);
+    all_thumb_state.left_y = ClampToZeroOne(all_thumb_state.left_y);
+    all_thumb_state.right_x = ClampToZeroOne(all_thumb_state.right_x);
+    all_thumb_state.right_y = ClampToZeroOne(all_thumb_state.right_y);
+
+    return all_thumb_state;
+  }
+
+  static float ClampToZeroOne(float in) {
+    return std::max(-1.0f, std::min(1.0f, in));
+  }
+  Callback* callback_;
+  std::map<SbKey, bool> stick_is_centered_;
+  std::vector<ThumbSticks> thumb_sticks_;
+};
+
+AnalogThumbstickThread::AnalogThumbstickThread(Callback* cb) {
+  impl_.reset(new Impl(cb));
+}
+
+AnalogThumbstickThread::~AnalogThumbstickThread() {
+  impl_.reset(nullptr);
+}
+
+}  // namespace uwp
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/uwp/analog_thumbstick_input_thread.h b/src/starboard/shared/uwp/analog_thumbstick_input_thread.h
new file mode 100644
index 0000000..e5bbefa
--- /dev/null
+++ b/src/starboard/shared/uwp/analog_thumbstick_input_thread.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_THREAD_H_
+#define STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_THREAD_H_
+
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/configuration.h"
+#include "starboard/input.h"
+
+namespace starboard {
+namespace shared {
+namespace uwp {
+
+// This class represents a thread that will poll all gamepads for
+// the analog sticks. The sticks are summed together and then
+// the callback will be invoked with the thumbstick values.
+class AnalogThumbstickThread {
+ public:
+  enum { kPollingFrequency = 120 };  // Smooth playback for 60fps.
+
+  class Callback {
+   public:
+    virtual ~Callback() {}
+    virtual void OnJoystickUpdate(SbKey key, SbInputVector position) = 0;
+  };
+
+  explicit AnalogThumbstickThread(Callback* cb);
+  ~AnalogThumbstickThread();
+
+ private:
+  SB_DISALLOW_COPY_AND_ASSIGN(AnalogThumbstickThread);
+  class Impl;
+  scoped_ptr<Impl> impl_;
+};
+
+}  // namespace uwp
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_UWP_ANALOG_THUMBSTICK_INPUT_THREAD_H_
diff --git a/src/starboard/shared/uwp/application_uwp.cc b/src/starboard/shared/uwp/application_uwp.cc
index 2343b2a..3609033 100644
--- a/src/starboard/shared/uwp/application_uwp.cc
+++ b/src/starboard/shared/uwp/application_uwp.cc
@@ -25,13 +25,16 @@
 #include <vector>
 
 #include "starboard/event.h"
+#include "starboard/input.h"
 #include "starboard/log.h"
 #include "starboard/mutex.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
 #include "starboard/shared/starboard/player/video_frame_internal.h"
+#include "starboard/shared/uwp/analog_thumbstick_input_thread.h"
 #include "starboard/shared/uwp/async_utils.h"
 #include "starboard/shared/uwp/window_internal.h"
+#include "starboard/shared/win32/log_file_impl.h"
 #include "starboard/shared/win32/thread_private.h"
 #include "starboard/shared/win32/wchar_utils.h"
 #include "starboard/string.h"
@@ -91,9 +94,20 @@
 const int kWinSockVersionMinor = 2;
 
 const char kDialParamPrefix[] = "cobalt-dial:?";
+const char kLogPathSwitch[] = "xb1_log_file";
 
 int main_return_value = 0;
 
+int MakeDeviceId() {
+  // TODO: Devices MIGHT have colliding hashcodes. Some other unique int
+  // ID generation tool would be better.
+  using Windows::Security::ExchangeActiveSyncProvisioning::
+      EasClientDeviceInformation;
+  auto device_information = ref new EasClientDeviceInformation();
+  Platform::String ^ device_id_string = device_information->Id.ToString();
+  return device_id_string->GetHashCode();
+}
+
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
 
 // Parses a starboard: URI scheme by splitting args at ';' boundaries.
@@ -149,6 +163,16 @@
   return std::equal(substring.rbegin(), substring.rend(), full_string.rbegin());
 }
 
+std::string GetBinaryName() {
+  std::string full_binary_path = GetArgvZero();
+  std::string::size_type index = full_binary_path.rfind(SB_FILE_SEP_CHAR);
+  if (index == std::string::npos) {
+    return full_binary_path;
+  }
+
+  return full_binary_path.substr(index + 1);
+}
+
 }  // namespace
 
 // Note that this is a "struct" and not a "class" because
@@ -274,7 +298,7 @@
   virtual void Uninitialize() { SbAudioSinkPrivate::TearDown(); }
 
   void OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) {
-    SB_DLOG(INFO) << "Suspending";
+    SB_DLOG(INFO) << "Suspending application.";
     // Note if we dispatch "suspend" here before pause, application.cc
     // will inject the "pause" which will cause us to go async which
     // will cause us to not have completed the suspend operation before
@@ -380,6 +404,17 @@
         ApplicationUwp::Get()->SetCommandLine(
             static_cast<int>(argv_.size()), argv_.data());
       }
+
+      ApplicationUwp* application_uwp = ApplicationUwp::Get();
+      CommandLine* command_line =
+          ::starboard::shared::uwp::GetCommandLinePointer(application_uwp);
+      if (command_line->HasSwitch(kLogPathSwitch)) {
+        std::string switch_val = command_line->GetSwitchValue(kLogPathSwitch);
+        sbwin32::OpenLogInCacheDirectory(switch_val.c_str(),
+                                         kSbFileCreateAlways);
+      }
+      SB_LOG(INFO) << "Starting " << GetBinaryName();
+
       CoreWindow::GetForCurrentThread()->Activate();
       // Call DispatchStart async so the UWP system thinks we're activated.
       // Some tools seem to want the application to be activated before
@@ -456,7 +491,7 @@
 // If an argv[0] is required, fill it in with the result of
 // GetModuleFileName()
 std::string GetArgvZero() {
-  const size_t kMaxModuleNameSize = 256;
+  const size_t kMaxModuleNameSize = SB_FILE_MAX_NAME;
   wchar_t buffer[kMaxModuleNameSize];
   DWORD result = GetModuleFileName(NULL, buffer, kMaxModuleNameSize);
   std::string arg;
@@ -469,9 +504,15 @@
 }
 
 ApplicationUwp::ApplicationUwp()
-    : window_(kSbWindowInvalid), localized_strings_(SbSystemGetLocaleId()) {}
+    : window_(kSbWindowInvalid),
+      localized_strings_(SbSystemGetLocaleId()),
+      device_id_(MakeDeviceId()) {
+  analog_thumbstick_thread_.reset(new AnalogThumbstickThread(this));
+}
 
-ApplicationUwp::~ApplicationUwp() {}
+ApplicationUwp::~ApplicationUwp() {
+  analog_thumbstick_thread_.reset(nullptr);
+}
 
 void ApplicationUwp::Initialize() {}
 
@@ -595,6 +636,42 @@
   SB_NOTIMPLEMENTED();
   return 0;
 }
+
+void ApplicationUwp::OnJoystickUpdate(SbKey key, SbInputVector input_vector) {
+  scoped_ptr<SbInputData> data(new SbInputData());
+  SbMemorySet(data.get(), 0, sizeof(*data));
+  data->window = window_;
+  data->type = kSbInputEventTypeMove;
+  data->device_type = kSbInputDeviceTypeGamepad;
+  data->device_id = device_id();
+  data->key = key;
+  data->character = 0;
+
+  data->key_modifiers = kSbKeyModifiersNone;
+  data->position = input_vector;
+
+  SbKeyLocation key_location = kSbKeyLocationUnspecified;
+  switch (key) {
+    case kSbKeyGamepadLeftStickLeft:
+    case kSbKeyGamepadLeftStickUp: {
+      key_location = kSbKeyLocationLeft;
+      break;
+    }
+    case kSbKeyGamepadRightStickLeft:
+    case kSbKeyGamepadRightStickUp: {
+      key_location = kSbKeyLocationRight;
+    }
+    default: {
+      SB_NOTREACHED();
+      break;
+    }
+  }
+
+  data->key_location = key_location;
+  Inject(new Event(kSbEventTypeInput, data.release(),
+                   &Application::DeleteDestructor<SbInputData>));
+}
+
 SbSystemPlatformError ApplicationUwp::OnSbSystemRaisePlatformError(
     SbSystemPlatformErrorType type,
     SbSystemPlatformErrorCallback callback,
diff --git a/src/starboard/shared/uwp/application_uwp.h b/src/starboard/shared/uwp/application_uwp.h
index 3ded30a..f961b79 100644
--- a/src/starboard/shared/uwp/application_uwp.h
+++ b/src/starboard/shared/uwp/application_uwp.h
@@ -1,145 +1,164 @@
-// 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_APPLICATION_UWP_H_
-#define STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
-
-#include <agile.h>
-#include <string>
-#include <unordered_map>
-
-#include "starboard/configuration.h"
-#include "starboard/mutex.h"
-#include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/application.h"
-#include "starboard/shared/starboard/command_line.h"
-#include "starboard/shared/starboard/localized_strings.h"
-#include "starboard/types.h"
-#include "starboard/window.h"
-
-namespace starboard {
-namespace shared {
-namespace uwp {
-
-using Windows::Media::Protection::HdcpSession;
-
-// Returns win32's GetModuleFileName(). For cases where we'd like an argv[0].
-std::string GetArgvZero();
-
-class ApplicationUwp : public shared::starboard::Application {
- public:
-  ApplicationUwp();
-  ~ApplicationUwp() SB_OVERRIDE;
-
-  static ApplicationUwp* Get() {
-    return static_cast<ApplicationUwp*>(shared::starboard::Application::Get());
-  }
-
-  SbWindow CreateWindowForUWP(const SbWindowOptions* options);
-
-  bool DestroyWindow(SbWindow window);
-
-  void DispatchStart() {
-    shared::starboard::Application::DispatchStart();
-  }
-
-  // public for IFrameworkView subclass
-  void SetCommandLine(int argc, const char** argv) {
-    shared::starboard::Application::SetCommandLine(argc, argv);
-  }
-
-  // public for IFrameworkView subclass
-  bool DispatchAndDelete(Application::Event* event) {
-    return shared::starboard::Application::DispatchAndDelete(event);
-  }
-
-  Platform::Agile<Windows::UI::Core::CoreWindow> GetCoreWindow() const {
-    return core_window_;
-  }
-
-  // public for IFrameworkView subclass
-  void SetCoreWindow(Windows::UI::Core::CoreWindow^ window) {
-    core_window_ = window;
-  }
-
-  void OnKeyEvent(Windows::UI::Core::CoreWindow^ sender,
-      Windows::UI::Core::KeyEventArgs^ args, bool up);
-
-  void Inject(Event* event) SB_OVERRIDE;
-
-  void SetStartLink(const char* link) SB_OVERRIDE {
-    shared::starboard::Application::SetStartLink(link);
-  }
-
-  SbSystemPlatformError OnSbSystemRaisePlatformError(
-     SbSystemPlatformErrorType type,
-     SbSystemPlatformErrorCallback callback,
-     void* user_data);
-
-  void OnSbSystemClearPlatformError(SbSystemPlatformError handle);
-
-  // Schedules a lambda to run on the main thread and returns immediately.
-  template<typename T>
-  void RunInMainThreadAsync(const T& lambda) {
-    core_window_->Dispatcher->RunAsync(
-      CoreDispatcherPriority::Normal,
-      ref new DispatchedHandler(lambda));
-  }
-
-  Platform::String^ GetString(const char* id, const char* fallback) const;
-
-  bool IsHdcpOn();
-  // Returns true on success.
-  bool TurnOnHdcp();
-  // Returns true on success.
-  bool TurnOffHdcp();
-
- private:
-  // --- Application overrides ---
-  bool IsStartImmediate() SB_OVERRIDE { return false; }
-  void Initialize() SB_OVERRIDE;
-  void Teardown() SB_OVERRIDE;
-  Event* GetNextEvent() SB_OVERRIDE;
-  bool DispatchNextEvent() SB_OVERRIDE;
-  void InjectTimedEvent(TimedEvent* timed_event) SB_OVERRIDE;
-  void CancelTimedEvent(SbEventId event_id) SB_OVERRIDE;
-  TimedEvent* GetNextDueTimedEvent() SB_OVERRIDE;
-  SbTimeMonotonic GetNextTimedEventTargetTime() SB_OVERRIDE;
-
-  // These two functions should only be called while holding
-  // |hdcp_session_mutex_|.
-  Windows::Media::Protection::HdcpSession^ GetHdcpSession();
-  void ResetHdcpSession();
-
-  // The single open window, if any.
-  SbWindow window_;
-  Platform::Agile<Windows::UI::Core::CoreWindow> core_window_;
-
-  shared::starboard::LocalizedStrings localized_strings_;
-
-  Mutex mutex_;
-  // |timer_event_map_| is locked by |mutex_|.
-  std::unordered_map<SbEventId, Windows::System::Threading::ThreadPoolTimer^>
-    timer_event_map_;
-
-  // |hdcp_session_| is locked by |hdcp_session_mutex_|.
-  Mutex hdcp_session_mutex_;
-  Windows::Media::Protection::HdcpSession^ hdcp_session_;
-};
-
-}  // namespace uwp
-}  // namespace shared
-}  // namespace starboard
-
-#endif  // STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
+// 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_APPLICATION_UWP_H_

+#define STARBOARD_SHARED_UWP_APPLICATION_UWP_H_

+

+#include <agile.h>

+#include <string>

+#include <unordered_map>

+

+#include "starboard/configuration.h"

+#include "starboard/input.h"

+#include "starboard/key.h"

+#include "starboard/mutex.h"

+#include "starboard/shared/internal_only.h"

+#include "starboard/shared/starboard/application.h"

+#include "starboard/shared/starboard/command_line.h"

+#include "starboard/shared/starboard/localized_strings.h"

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

+#include "starboard/types.h"

+#include "starboard/window.h"

+

+namespace starboard {

+namespace shared {

+namespace uwp {

+

+using Windows::Media::Protection::HdcpSession;

+

+// Returns win32's GetModuleFileName(). For cases where we'd like an argv[0].

+std::string GetArgvZero();

+

+// Including <agile.h>, will eventually include <windows.h>, which includes

+// C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\um\processenv.h,

+// line 164 in processenv.h redefines GetCommandLine to GetCommandLineW if

+// UNICODE is defined.

+// This function was added so that it could be used as a work around when

+// GetCommandLine() needed to be called.

+starboard::CommandLine* GetCommandLinePointer(starboard::Application* app);

+

+class ApplicationUwp : public shared::starboard::Application,

+                       private AnalogThumbstickThread::Callback {

+ public:

+  ApplicationUwp();

+  ~ApplicationUwp() SB_OVERRIDE;

+

+  static ApplicationUwp* Get() {

+    return static_cast<ApplicationUwp*>(shared::starboard::Application::Get());

+  }

+

+  SbWindow CreateWindowForUWP(const SbWindowOptions* options);

+

+  bool DestroyWindow(SbWindow window);

+

+  void DispatchStart() {

+    shared::starboard::Application::DispatchStart();

+  }

+

+  // public for IFrameworkView subclass

+  void SetCommandLine(int argc, const char** argv) {

+    shared::starboard::Application::SetCommandLine(argc, argv);

+  }

+

+  // public for IFrameworkView subclass

+  bool DispatchAndDelete(Application::Event* event) {

+    return shared::starboard::Application::DispatchAndDelete(event);

+  }

+

+  Platform::Agile<Windows::UI::Core::CoreWindow> GetCoreWindow() const {

+    return core_window_;

+  }

+

+  // public for IFrameworkView subclass

+  void SetCoreWindow(Windows::UI::Core::CoreWindow^ window) {

+    core_window_ = window;

+  }

+

+  void OnKeyEvent(Windows::UI::Core::CoreWindow^ sender,

+      Windows::UI::Core::KeyEventArgs^ args, bool up);

+

+  void Inject(Event* event) SB_OVERRIDE;

+

+  void SetStartLink(const char* link) SB_OVERRIDE {

+    shared::starboard::Application::SetStartLink(link);

+  }

+

+  SbSystemPlatformError OnSbSystemRaisePlatformError(

+     SbSystemPlatformErrorType type,

+     SbSystemPlatformErrorCallback callback,

+     void* user_data);

+

+  void OnSbSystemClearPlatformError(SbSystemPlatformError handle);

+

+  // Schedules a lambda to run on the main thread and returns immediately.

+  template<typename T>

+  void RunInMainThreadAsync(const T& lambda) {

+    core_window_->Dispatcher->RunAsync(

+      CoreDispatcherPriority::Normal,

+      ref new DispatchedHandler(lambda));

+  }

+

+  Platform::String^ GetString(const char* id, const char* fallback) const;

+

+  bool IsHdcpOn();

+  // Returns true on success.

+  bool TurnOnHdcp();

+  // Returns true on success.

+  bool TurnOffHdcp();

+

+ private:

+  // --- Application overrides ---

+  bool IsStartImmediate() SB_OVERRIDE { return false; }

+  void Initialize() SB_OVERRIDE;

+  void Teardown() SB_OVERRIDE;

+  Event* GetNextEvent() SB_OVERRIDE;

+  bool DispatchNextEvent() SB_OVERRIDE;

+  void InjectTimedEvent(TimedEvent* timed_event) SB_OVERRIDE;

+  void CancelTimedEvent(SbEventId event_id) SB_OVERRIDE;

+  TimedEvent* GetNextDueTimedEvent() SB_OVERRIDE;

+  SbTimeMonotonic GetNextTimedEventTargetTime() SB_OVERRIDE;

+

+  int device_id() const { return device_id_; }

+  void OnJoystickUpdate(SbKey key, SbInputVector value) SB_OVERRIDE;

+

+  // These two functions should only be called while holding

+  // |hdcp_session_mutex_|.

+  Windows::Media::Protection::HdcpSession^ GetHdcpSession();

+  void ResetHdcpSession();

+

+  // The single open window, if any.

+  SbWindow window_;

+  Platform::Agile<Windows::UI::Core::CoreWindow> core_window_;

+

+  shared::starboard::LocalizedStrings localized_strings_;

+

+  Mutex mutex_;

+  // |timer_event_map_| is locked by |mutex_|.

+  std::unordered_map<SbEventId, Windows::System::Threading::ThreadPoolTimer^>

+    timer_event_map_;

+

+  int device_id_;

+

+  // |hdcp_session_| is locked by |hdcp_session_mutex_|.

+  Mutex hdcp_session_mutex_;

+  Windows::Media::Protection::HdcpSession^ hdcp_session_;

+

+  scoped_ptr<AnalogThumbstickThread> analog_thumbstick_thread_;

+};

+

+}  // namespace uwp

+}  // namespace shared

+}  // namespace starboard

+

+#endif  // STARBOARD_SHARED_UWP_APPLICATION_UWP_H_
\ No newline at end of file
diff --git a/src/starboard/shared/uwp/application_uwp_get_commandline_pointer.cc b/src/starboard/shared/uwp/application_uwp_get_commandline_pointer.cc
new file mode 100644
index 0000000..669e6a3
--- /dev/null
+++ b/src/starboard/shared/uwp/application_uwp_get_commandline_pointer.cc
@@ -0,0 +1,28 @@
+// Copyright 2017 Google Inc. All Rights Reserved.

+//

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

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

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

+//

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

+//

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

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

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+// See the License for the specific language governing permissions and

+// limitations under the License.

+

+#include "starboard/shared/starboard/application.h"

+#include "starboard/shared/starboard/command_line.h"

+

+namespace starboard {

+namespace shared {

+namespace uwp {

+

+starboard::CommandLine* GetCommandLinePointer(starboard::Application* app) {

+  return app->GetCommandLine();

+}

+

+}  // namespace uwp

+}  // namespace shared

+}  // namespace starboard

diff --git a/src/starboard/shared/uwp/application_uwp_key_event.cc b/src/starboard/shared/uwp/application_uwp_key_event.cc
index 73b7d27..cfe7c16 100644
--- a/src/starboard/shared/uwp/application_uwp_key_event.cc
+++ b/src/starboard/shared/uwp/application_uwp_key_event.cc
@@ -21,8 +21,6 @@
 using Windows::UI::Core::CoreWindow;
 using Windows::UI::Core::KeyEventArgs;
 using Windows::UI::Core::CoreVirtualKeyStates;
-using Windows::Security::ExchangeActiveSyncProvisioning::
-    EasClientDeviceInformation;
 using Windows::System::VirtualKey;
 
 namespace {
@@ -255,11 +253,7 @@
 
   data->window = window_;
   data->device_type = kSbInputDeviceTypeKeyboard;
-  // TODO: Devices might have colliding hashcodes. Some other unique int
-  // ID generation tool would be better.
-  auto device_information = ref new EasClientDeviceInformation();
-  Platform::String^ device_id_string = device_information->Id.ToString();
-  data->device_id = device_id_string->GetHashCode();
+  data->device_id = device_id();
   data->key = VirtualKeyToSbKey(args->VirtualKey);
 
   if (up) {
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi
index 8d082dc..bb3a5d5 100644
--- a/src/starboard/shared/uwp/starboard_platform.gypi
+++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -14,7 +14,12 @@
 {
   'variables': {
     'starboard_platform_dependent_files': [
+      'analog_thumbstick_input.cc',
+      'analog_thumbstick_input.h',
+      'analog_thumbstick_input_thread.cc',
+      'analog_thumbstick_input_thread.h',
       'application_uwp_key_event.cc',
+      'application_uwp_get_commandline_pointer.cc',
       'application_uwp.cc',
       'application_uwp.h',
       'async_utils.h',
diff --git a/src/starboard/shared/win32/gyp_configuration.py b/src/starboard/shared/win32/gyp_configuration.py
index 93f92f1..616a9c0 100644
--- a/src/starboard/shared/win32/gyp_configuration.py
+++ b/src/starboard/shared/win32/gyp_configuration.py
@@ -18,6 +18,7 @@
 import config.starboard
 
 from starboard.tools.paths import STARBOARD_ROOT
+import starboard.tools.testing.test_filter as test_filter
 
 import sdk_configuration
 
@@ -87,3 +88,39 @@
         os.path.join(STARBOARD_ROOT, 'shared', 'msvc', 'uwp'))
     from msvc_toolchain import MSVCUWPToolchain  # pylint: disable=g-import-not-at-top,g-bad-import-order
     return MSVCUWPToolchain()
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return [
+        # Fails on JSC.
+        test_filter.TestFilter(
+            'bindings_test', ('EvaluateScriptTest.ThreeArguments')),
+        test_filter.TestFilter(
+            'bindings_test', ('GarbageCollectionTest.*')),
+
+        test_filter.TestFilter('nplb', test_filter.FILTER_ALL),
+        test_filter.TestFilter('poem_unittests', test_filter.FILTER_ALL),
+
+        # The Windows platform uses D3D9 which doesn't let you create a D3D
+        # device without a display, causing these unit tests to erroneously
+        # fail on the buildbots, so they are disabled for Windows only.
+        test_filter.TestFilter('layout_tests', test_filter.FILTER_ALL),
+        test_filter.TestFilter('renderer_test', test_filter.FILTER_ALL),
+
+        # No network on Windows, yet.
+        test_filter.TestFilter('web_platform_tests', test_filter.FILTER_ALL),
+        test_filter.TestFilter('net_unittests', test_filter.FILTER_ALL),
+
+        test_filter.TestFilter('starboard_platform_tests',
+                               test_filter.FILTER_ALL),
+        test_filter.TestFilter('nplb_blitter_pixel_tests',
+                               test_filter.FILTER_ALL),
+        test_filter.TestFilter('webdriver_test',
+                               test_filter.FILTER_ALL)
+
+    ]
+
diff --git a/src/starboard/shared/win32/log_file_impl.cc b/src/starboard/shared/win32/log_file_impl.cc
new file mode 100644
index 0000000..1d258ae
--- /dev/null
+++ b/src/starboard/shared/win32/log_file_impl.cc
@@ -0,0 +1,108 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/win32/log_file_impl.h"
+
+#include <string>
+
+#include "starboard/file.h"
+#include "starboard/memory.h"
+#include "starboard/mutex.h"
+#include "starboard/once.h"
+#include "starboard/shared/win32/file_internal.h"
+#include "starboard/string.h"
+
+namespace {
+
+SbMutex log_mutex = SB_MUTEX_INITIALIZER;
+SbFile log_file = kSbFileInvalid;
+
+}  // namespace
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+void CloseLogFile() {
+  SbMutexAcquire(&log_mutex);
+
+  if (SbFileIsValid(log_file)) {
+    SbFileFlush(log_file);
+    SbFileClose(log_file);
+    log_file = kSbFileInvalid;
+  }
+
+  SbMutexRelease(&log_mutex);
+}
+
+void OpenLogInCacheDirectory(const char* log_file_name, int creation_flags) {
+  SB_DCHECK((creation_flags & kSbFileOpenAlways) ||
+            (creation_flags & kSbFileCreateAlways));
+  SB_DCHECK(SbStringGetLength(log_file_name) != 0);
+  SB_DCHECK(SbStringFindCharacter(log_file_name, SB_FILE_SEP_CHAR) == nullptr);
+  char out_path[SB_FILE_MAX_PATH + 1];
+  out_path[0] = '\0';
+
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, out_path,
+                       SB_ARRAY_SIZE_INT(out_path))) {
+    return;
+  }
+
+  const int path_size = SB_ARRAY_SIZE_INT(out_path);
+  if (SbStringConcat(out_path, SB_FILE_SEP_STRING, path_size) >= path_size) {
+    return;
+  }
+  if (SbStringConcat(out_path, log_file_name, path_size) >= path_size) {
+    return;
+  }
+
+  OpenLogFile(out_path, creation_flags);
+}
+
+void OpenLogFile(const char* path, const int creation_flags) {
+  SB_DCHECK((creation_flags & kSbFileOpenAlways) ||
+            (creation_flags & kSbFileCreateAlways));
+  CloseLogFile();
+  SB_DLOG(INFO) << "Logging to [" << path << "]";
+
+  int flags = creation_flags | kSbFileWrite;
+
+  SbMutexAcquire(&log_mutex);
+  if ((path != nullptr) && (path != '\0')) {
+    log_file = SbFileOpen(path, flags, nullptr, nullptr);
+    SB_DCHECK(SbFileIsValid(log_file));
+  }
+  SbMutexRelease(&log_mutex);
+}
+
+void WriteToLogFile(const char* text, const int text_length) {
+  if (text_length <= 0) {
+    return;
+  }
+  SbMutexAcquire(&log_mutex);
+  if (!SbFileIsValid(log_file)) {
+    SbMutexRelease(&log_mutex);
+    return;
+  }
+
+  int bytes_written = SbFileWriteAll(log_file, text, text_length);
+  SB_DCHECK(text_length == bytes_written);
+
+  SbFileFlush(log_file);
+  SbMutexRelease(&log_mutex);
+}
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/win32/log_file_impl.h b/src/starboard/shared/win32/log_file_impl.h
new file mode 100644
index 0000000..2ed28b9
--- /dev/null
+++ b/src/starboard/shared/win32/log_file_impl.h
@@ -0,0 +1,50 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This header provides a mechanism for multiple Android logging
+// formats to share a single log file handle.
+
+#ifndef STARBOARD_SHARED_WIN32_LOG_FILE_IMPL_H_
+#define STARBOARD_SHARED_WIN32_LOG_FILE_IMPL_H_
+
+#include "starboard/file.h"
+#include "starboard/mutex.h"
+
+namespace starboard {
+namespace shared {
+namespace win32 {
+
+// Closes the log file.
+void CloseLogFile();
+
+// Opens a file in |kSbSystemPathCacheDirectory| directory.
+// |log_file_name|: C-style string of a filename
+// |creation_flags|: Must be kSbFileCreateAlways (which will truncate the file)
+//   |kSbFileOpenAlways|, which can be used to append to the file.
+void OpenLogInCacheDirectory(const char* log_file_name, int creation_flags);
+
+// Opens a file at |log_file_path| with |creation_flags|.
+// |log_file_name|: C-style string of a filename
+// |creation_flags|: Must be kSbFileCreateAlways (which will truncate the file)
+//   |kSbFileOpenAlways|, which can be used to append to the file.
+void OpenLogFile(const char* log_file_path, int creation_flags);
+
+// Writes |text_length| bytes starting from |text| to the current log file.
+void WriteToLogFile(const char* text, const int text_length);
+
+}  // namespace win32
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIN32_LOG_FILE_IMPL_H_
diff --git a/src/starboard/shared/win32/log_raw.cc b/src/starboard/shared/win32/log_raw.cc
index d33f086..2929156 100644
--- a/src/starboard/shared/win32/log_raw.cc
+++ b/src/starboard/shared/win32/log_raw.cc
@@ -17,7 +17,14 @@
 #include <stdio.h>
 #include <windows.h>
 
+#include "starboard/shared/win32/log_file_impl.h"
+#include "starboard/string.h"
+
+namespace sbwin32 = starboard::shared::win32;
+
 void SbLogRaw(const char* message) {
   fprintf(stderr, "%s", message);
   OutputDebugStringA(message);
+  sbwin32::WriteToLogFile(
+      message, static_cast<int>(SbStringGetLength(message)));
 }
diff --git a/src/starboard/shared/win32/log_raw_format.cc b/src/starboard/shared/win32/log_raw_format.cc
index 5b6c59e..39b5400 100644
--- a/src/starboard/shared/win32/log_raw_format.cc
+++ b/src/starboard/shared/win32/log_raw_format.cc
@@ -17,14 +17,19 @@
 #include <stdio.h>
 #include <windows.h>
 
+#include "starboard/shared/win32/log_file_impl.h"
+
 static const int kMaxLogLineChars = 16 * 1024;
 
+namespace sbwin32 = starboard::shared::win32;
+
 void SbLogRawFormat(const char* format, va_list arguments) {
   vfprintf(stderr, format, arguments);
   char log_buffer[kMaxLogLineChars];
   int result = vsprintf_s(log_buffer, kMaxLogLineChars, format, arguments);
-  if (result >= 0) {
+  if (result > 0) {
     OutputDebugStringA(log_buffer);
+    sbwin32::WriteToLogFile(log_buffer, result);
   } else {
     OutputDebugStringA("[log line too long]");
   }
diff --git a/src/starboard/stub/gyp_configuration.py b/src/starboard/stub/gyp_configuration.py
index 63c1c9a..70f8340 100644
--- a/src/starboard/stub/gyp_configuration.py
+++ b/src/starboard/stub/gyp_configuration.py
@@ -46,3 +46,11 @@
         'CXX': self.host_compiler_environment['CXX_host'],
     })
     return env_variables
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return []
diff --git a/src/starboard/tizen/armv7l/gyp_configuration.py b/src/starboard/tizen/armv7l/gyp_configuration.py
index 3cee759..50bae01 100644
--- a/src/starboard/tizen/armv7l/gyp_configuration.py
+++ b/src/starboard/tizen/armv7l/gyp_configuration.py
@@ -48,3 +48,11 @@
         'CXX_host': 'armv7l-tizen-linux-gnueabi-g++',
     }
     return env_variables
+
+  def GetTestFilters(self):
+    """Gets all tests to be excluded from a unit test run.
+
+    Returns:
+      A list of initialized TestFilter objects.
+    """
+    return []
diff --git a/src/starboard/win/shared/configuration_public.h b/src/starboard/win/shared/configuration_public.h
index 138ca41..44e4ac9 100644
--- a/src/starboard/win/shared/configuration_public.h
+++ b/src/starboard/win/shared/configuration_public.h
@@ -20,7 +20,7 @@
 
 // The API version implemented by this platform. This will generally be set to
 // the current value of SB_MAXIMUM_API_VERSION at the time of implementation.
-#define SB_API_VERSION 6
+#define SB_API_VERSION 7
 
 // --- Architecture Configuration --------------------------------------------
 
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi
index ff78819..9302e9c 100644
--- a/src/starboard/win/shared/starboard_platform.gypi
+++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -302,6 +302,8 @@
         '<(DEPTH)/starboard/shared/win32/file_truncate.cc',
         '<(DEPTH)/starboard/shared/win32/file_write.cc',
         '<(DEPTH)/starboard/shared/win32/log.cc',
+        '<(DEPTH)/starboard/shared/win32/log_file_impl.cc',
+        '<(DEPTH)/starboard/shared/win32/log_file_impl.h',
         '<(DEPTH)/starboard/shared/win32/log_flush.cc',
         '<(DEPTH)/starboard/shared/win32/log_format.cc',
         '<(DEPTH)/starboard/shared/win32/log_is_tty.cc',