Import Cobalt 20.lts.2.234067
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 5e3d559..8907972 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-224508
\ No newline at end of file
+234067
\ No newline at end of file
diff --git a/src/cobalt/cssom/property_definitions.cc b/src/cobalt/cssom/property_definitions.cc
index 72d36ac..6a4db89 100644
--- a/src/cobalt/cssom/property_definitions.cc
+++ b/src/cobalt/cssom/property_definitions.cc
@@ -884,7 +884,7 @@
   SetShorthandPropertyDefinition(kBorderRadiusProperty, "border-radius",
                                  border_radius_longhand_properties);
 
-  //   https://www.w3.org/TR/css3-background/#border
+  //   https://www.w3.org/TR/css-backgrounds-3/#propdef-border
   LonghandPropertySet border_longhand_properties;
   border_longhand_properties.insert(kBorderColorProperty);
   border_longhand_properties.insert(kBorderStyleProperty);
diff --git a/src/cobalt/doc/net_log.md b/src/cobalt/doc/net_log.md
new file mode 100644
index 0000000..dfc3c98
--- /dev/null
+++ b/src/cobalt/doc/net_log.md
@@ -0,0 +1,30 @@
+# Cobalt NetLog

+

+Chromium has a very useful network diagnostic tool called the NetLog and Cobalt

+is hooked up to use it. It's the main tool to track network traffic and debug

+network code.

+

+### Activate the NetLog

+

+The following command line switch will activate the NetLog and store net log

+record to the specified location.

+`./cobalt --net_log=/PATH/TO/YOUR_NETLOG_NAME.json`

+The output json file will be stored at the file location you choose.

+

+

+### Read the NetLog records

+

+The produced json file is not human-friendly, use the

+[NetLog Viewer](https://netlog-viewer.appspot.com/#import)

+

+Cobalt's net_log can not enable some features in the web viewer, but all the

+network traffic is recorded in the event tab.

+

+

+### Add NetLog entries

+

+To Add NetLog entry, get the NetLog instance owned by NetworkModule to where you

+want to add entries and start/end your entry according to the NetLog interface.

+

+A NetLog object is created at each NetworkModule initialization and is passed

+into Chromium net through URLRequestContext.

diff --git a/src/cobalt/layout/flex_container_box.cc b/src/cobalt/layout/flex_container_box.cc
index bacd234..8bb4127 100644
--- a/src/cobalt/layout/flex_container_box.cc
+++ b/src/cobalt/layout/flex_container_box.cc
@@ -311,8 +311,11 @@
   set_margin_right(maybe_margin_right.value_or(LayoutUnit()));
   set_margin_top(maybe_margin_top.value_or(LayoutUnit()));
   set_margin_bottom(maybe_margin_bottom.value_or(LayoutUnit()));
-  if (child_boxes().empty()) {
-    baseline_ = GetBorderBoxHeight();
+
+  UpdateRectOfPositionedChildBoxes(child_layout_params, layout_params);
+
+  if (items.empty()) {
+    baseline_ = GetPaddingBoxHeight() + border_bottom_width() + margin_bottom();
   } else {
     baseline_ = flex_formatting_context.GetBaseline();
   }
@@ -480,8 +483,7 @@
 }
 
 AnonymousBlockBox* FlexContainerBox::GetLastChildAsAnonymousBlockBox() {
-  return child_boxes().empty() ? NULL
-                               : child_boxes().back()->AsAnonymousBlockBox();
+  return NULL;
 }
 
 AnonymousBlockBox* FlexContainerBox::GetOrAddAnonymousBlockBox() {
diff --git a/src/cobalt/layout/flex_container_box.h b/src/cobalt/layout/flex_container_box.h
index a962d44..5e55ce2 100644
--- a/src/cobalt/layout/flex_container_box.h
+++ b/src/cobalt/layout/flex_container_box.h
@@ -15,6 +15,9 @@
 #ifndef COBALT_LAYOUT_FLEX_CONTAINER_BOX_H_
 #define COBALT_LAYOUT_FLEX_CONTAINER_BOX_H_
 
+#include <memory>
+
+#include "base/optional.h"
 #include "cobalt/cssom/css_computed_style_declaration.h"
 #include "cobalt/layout/base_direction.h"
 #include "cobalt/layout/block_container_box.h"
diff --git a/src/cobalt/layout/flex_line.cc b/src/cobalt/layout/flex_line.cc
index 124099a..a55819f 100644
--- a/src/cobalt/layout/flex_line.cc
+++ b/src/cobalt/layout/flex_line.cc
@@ -358,7 +358,7 @@
   // If the remaining free space is positive and at least one main-axis margin
   // on this line is auto, distribute the free space equally among these
   // margins.
-  std::vector<bool> auto_margins(items_.size());
+  std::vector<bool> auto_margins(items_.size() * 2);
   int auto_margin_count = 0;
   int margin_idx = 0;
   for (auto& item : items_) {
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index 3d2a447..ab107f5 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -366,8 +366,8 @@
   if (IsAbsolutelyPositioned()) {
     // TODO: Implement CSS section 10.3.8, see
     // https://www.w3.org/TR/CSS21/visudet.html#abs-replaced-width.
-    set_left(maybe_left.value_or(LayoutUnit()));
-    set_top(maybe_top.value_or(LayoutUnit()));
+    set_left(maybe_left.value_or(LayoutUnit(GetStaticPositionLeft())));
+    set_top(maybe_top.value_or(LayoutUnit(GetStaticPositionTop())));
   }
   // Note that computed height may be "auto", even if it is specified as a
   // percentage (depending on conditions of the containing block). See details
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children-expected.png b/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children-expected.png
new file mode 100644
index 0000000..cb33c85
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children.html b/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children.html
new file mode 100644
index 0000000..52e896a
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/absolutely-positioned-children.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<!--
+ | Tests for CSS Flexible Box Layout Module.
+ |   Testing absolutely positioned flex container children.
+ -->
+<html>
+<head>
+<style>
+  body {
+    margin: 0px;
+    font-family: Roboto;
+    background-color: gray;
+    font-size: 12px;
+  }
+  .flex {
+    display: inline-flex;
+    flex-flow: row wrap;
+    color: fuchsia;
+    background-color: yellow;
+    opacity: 0.75;
+    min-width: 400px;
+    min-height: 200px;
+    margin: 10px 20px 40px 30px;
+    border: solid white;
+    border-width: 10px 25px 25px 25px;
+    -no-position: relative;
+    -no-transform: translateX(0);
+  }
+  .absolute {
+    background-color: blue;
+    position: absolute;
+  }
+  .fixed {
+    background-color: purple;
+    position: fixed;
+  }
+  div > span {
+    padding: 1.25%;
+    border: 10px solid black;
+  }
+  div > video {
+    opacity: 1;
+    width: 5%;
+    height: 5%;
+    z-index: 1;
+  }
+</style>
+</head>
+<body>
+    -gh-
+    <div style="height: 5px; background-color: black;"></div>
+  <div style="display: inline-flex; width: 5px; height: 50px; background-color: black;"></div>
+  <span style="display: inline-block; width: 40px; background-color: lime;">-gh-</span>
+  <div class="flex">
+      <video class="absolute" style="top: 20%;"></video>
+      <video class="absolute" style="top: 30%; left: 0%;"></video>
+      <video class="absolute" style="left: 20%;"></video>
+      <video class="absolute" style="left: 30%; top: 0%;"></video>
+      <video class="fixed" style="top: 40%;"></video>
+      <video class="fixed" style="top: 50%; left: 0%;"></video>
+      <video class="fixed" style="left: 40%;"></video>
+      <video class="fixed" style="left: 50%; top: 0%;"></video>
+      <span class="absolute" style="top: 60%;"></span>
+      <span class="absolute" style="top: 70%; left: 0%;"></span>
+      <span class="absolute" style="left: 60%;"></span>
+      <span class="absolute" style="left: 70%; top: 0%;"></span>
+      <span class="fixed" style="top: 80%;"></span>
+      <span class="fixed" style="top: 90%; left: 0%;"></span>
+      <span class="fixed" style="left: 80%;"></span>
+      <span class="fixed" style="left: 90%; top: 0%"></span>
+  </div>
+  <div style="display: inline-flex; width: 5px; height: 50px; background-color: black;"></div>
+  <div style="height: 5px; background-color: black;"></div>
+  </body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/combined-baseline-expected.png b/src/cobalt/layout_tests/testdata/css3-flexbox/combined-baseline-expected.png
index 4afe5c9..b9b6c39 100644
--- a/src/cobalt/layout_tests/testdata/css3-flexbox/combined-baseline-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/combined-baseline-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline-expected.png b/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline-expected.png
new file mode 100644
index 0000000..8b26646
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline.html b/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline.html
new file mode 100644
index 0000000..e605155
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/empty_container_baseline.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<!--
+ | Tests for CSS Flexible Box Layout Module.
+ |   Testing absolutely positioned flex container children.
+ -->
+<html>
+<head>
+<style>
+  body {
+    margin: 0px;
+    font-family: Roboto;
+    background-color: gray;
+    font-size: 36px;
+  }
+  .flex {
+    display: inline-flex;
+    flex-flow: row wrap;
+    color: fuchsia;
+    background-color: yellow;
+    opacity: 0.75;
+    min-width: 10px;
+    min-height: 10px;
+    border: solid white;
+  }
+  .absolute {
+    background-color: blue;
+    position: absolute;
+  }
+  .fixed {
+    background-color: purple;
+    position: fixed;
+  }
+  div > span {
+    padding: 1.25%;
+    border: 10px solid black;
+  }
+  div > video {
+    opacity: 1;
+    width: 5%;
+    height: 5%;
+    z-index: 1;
+  }
+  .margin-a {
+    margin: 6px 12px 24px 16px;
+  }
+  .margin-b {
+    margin: 24px 16px 6px 12px;
+  }
+  .border-a {
+    border-width: 6px 12px 12px 12px;
+  }
+  .border-b {
+    border-width: 12px 12px 5px 12px;
+  }
+</style>
+</head>
+<body>
+  -gh-
+  <div style="height: 5px; background-color: black;"></div>
+  <div style="display: inline-flex; width: 5px; height: 50px; background-color: black;"></div>
+  <span style="display: inline-block; width: 60px; background-color: lime">-gh- -gh-</span>
+  <div class="flex"></div>
+  <div class="flex margin-a"></div>
+  <div class="flex margin-b"></div>
+  <div class="flex border-a"></div>
+  <div class="flex border-b"></div>
+  <div class="flex margin-a border-a"></div>
+  <div class="flex margin-a border-b""></div>
+  <div class="flex margin-b border-a"></div>
+  <div class="flex margin-b border-b""></div>
+  <div style="display: inline-flex; width: 5px; height: 50px; background-color: black;"></div>
+  <div style="height: 5px; background-color: black;"></div>
+  </body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-flexbox/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-flexbox/layout_tests.txt
index c8a34e8..26b3994 100644
--- a/src/cobalt/layout_tests/testdata/css3-flexbox/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css3-flexbox/layout_tests.txt
@@ -1,3 +1,4 @@
+absolutely-positioned-children
 combined-baseline
 combined-container-sizing-edge-cases
 combined-order-and-multiline
@@ -133,5 +134,6 @@
 csswg_flex-shrink-006
 csswg_flex-shrink-007
 csswg_flex-shrink-008
+empty_container_baseline
 flex-items-flexibility
 positioned-containers
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 628898c..c41e9ac 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -337,6 +337,13 @@
     base::TimeDelta* buffer_start_time, base::TimeDelta* buffer_length_time) {
   DCHECK(buffer_start_time || buffer_length_time);
   DCHECK(is_url_based_);
+
+  if (state_ == kSuspended) {
+    *buffer_start_time = base::TimeDelta();
+    *buffer_length_time = base::TimeDelta();
+    return;
+  }
+
   DCHECK(SbPlayerIsValid(player_));
 
   SbUrlPlayerExtraInfo url_player_info;
@@ -355,9 +362,16 @@
 void StarboardPlayer::GetVideoResolution(int* frame_width, int* frame_height) {
   DCHECK(frame_width);
   DCHECK(frame_height);
-  DCHECK(SbPlayerIsValid(player_));
   DCHECK(is_url_based_);
 
+  if (state_ == kSuspended) {
+    *frame_width = video_sample_info_.frame_width;
+    *frame_height = video_sample_info_.frame_height;
+    return;
+  }
+
+  DCHECK(SbPlayerIsValid(player_));
+
   SbPlayerInfo2 out_player_info;
   SbPlayerGetInfo2(player_, &out_player_info);
 
diff --git a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
index e4d7754..391318b 100644
--- a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
+++ b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
@@ -287,6 +287,8 @@
         sample_format = kSampleFormatU8;
       } else if (entry.samplesize == 16) {
         sample_format = kSampleFormatS16;
+      } else if (entry.samplesize == 24) {
+        sample_format = kSampleFormatS24;
       } else if (entry.samplesize == 32) {
         sample_format = kSampleFormatS32;
       } else {
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index 6185a52..2a268ae 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -646,7 +646,8 @@
     // Any error that occurs before reaching ReadyStateHaveMetadata should
     // be considered a format error.
     SetNetworkError(WebMediaPlayer::kNetworkStateFormatError,
-                    "Ready state have nothing.");
+                    message.empty() ? "Ready state have nothing."
+                                    : "Ready state have nothing: " + message);
     return;
   }
 
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
index 40c04a4..8d6e121 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -109,8 +109,13 @@
 }
 
 bool ImageNodeSupportedNatively(render_tree::ImageNode* image_node) {
+  // Ensure any required backend processing is done to create the necessary
+  // GPU resource. This must be done to verify whether the GPU resource can
+  // be rendered by the shader.
   skia::Image* skia_image =
       base::polymorphic_downcast<skia::Image*>(image_node->data().source.get());
+  skia_image->EnsureInitialized();
+
   if (skia_image->GetTypeId() == base::GetTypeId<skia::MultiPlaneImage>()) {
     skia::HardwareMultiPlaneImage* hardware_image =
         base::polymorphic_downcast<skia::HardwareMultiPlaneImage*>(skia_image);
@@ -501,10 +506,6 @@
       base::polymorphic_downcast<skia::Image*>(data.source.get());
   bool is_opaque = skia_image->IsOpaque() && IsOpaque(draw_state_.opacity);
 
-  // Ensure any required backend processing is done to create the necessary
-  // GPU resource.
-  skia_image->EnsureInitialized();
-
   // Calculate matrix to transform texture coordinates according to the local
   // transform.
   math::Matrix3F texcoord_transform(math::Matrix3F::Identity());
diff --git a/src/cobalt/script/mozjs-45/mozjs_global_environment.h b/src/cobalt/script/mozjs-45/mozjs_global_environment.h
index 0bc4428..0aa069d 100644
--- a/src/cobalt/script/mozjs-45/mozjs_global_environment.h
+++ b/src/cobalt/script/mozjs-45/mozjs_global_environment.h
@@ -185,14 +185,17 @@
   JSContext* context_;
   int garbage_collection_count_;
   WeakHeapObjectManager weak_object_manager_;
-  std::unordered_map<Wrappable*, CountedHeapObject> kept_alive_objects_;
   std::unique_ptr<ReferencedObjectMap> referenced_objects_;
-  std::vector<InterfaceData> cached_interface_data_;
 
+  // Beware the order of destruction. Anything which references the JSContext
+  // should be destroyed before ~ContextDestructor.
   ContextDestructor context_destructor_;
+
+  JS::Heap<JSObject*> global_object_proxy_;
   std::unique_ptr<WrapperFactory> wrapper_factory_;
   std::unique_ptr<MozjsScriptValueFactory> script_value_factory_;
-  JS::Heap<JSObject*> global_object_proxy_;
+  std::vector<InterfaceData> cached_interface_data_;
+  std::unordered_map<Wrappable*, CountedHeapObject> kept_alive_objects_;
   EnvironmentSettings* environment_settings_;
   // TODO: Should be |std::unordered_set| once C++11 is enabled.
   base::hash_set<Traceable*> visited_traceables_;
diff --git a/src/cobalt/script/mozjs-45/referenced_object_map.cc b/src/cobalt/script/mozjs-45/referenced_object_map.cc
index bceb6c4..b822336 100644
--- a/src/cobalt/script/mozjs-45/referenced_object_map.cc
+++ b/src/cobalt/script/mozjs-45/referenced_object_map.cc
@@ -29,6 +29,10 @@
 ReferencedObjectMap::ReferencedObjectMap(JSContext* context)
     : context_(context) {}
 
+ReferencedObjectMap::~ReferencedObjectMap() {
+  DCHECK(referenced_objects_.empty());
+}
+
 // Add/Remove a reference from a WrapperPrivate to a JSValue.
 void ReferencedObjectMap::AddReferencedObject(Wrappable* wrappable,
                                               JS::HandleValue referee) {
diff --git a/src/cobalt/script/mozjs-45/referenced_object_map.h b/src/cobalt/script/mozjs-45/referenced_object_map.h
index 487f2db..134c515 100644
--- a/src/cobalt/script/mozjs-45/referenced_object_map.h
+++ b/src/cobalt/script/mozjs-45/referenced_object_map.h
@@ -32,6 +32,7 @@
 class ReferencedObjectMap {
  public:
   explicit ReferencedObjectMap(JSContext* context);
+  ~ReferencedObjectMap();
 
   void AddReferencedObject(Wrappable* wrappable, JS::HandleValue referee);
   void RemoveReferencedObject(Wrappable* wrappable, JS::HandleValue referee);
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index cc3f61a..e8b74f9 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "20.lts.1"
+#define COBALT_VERSION "20.lts.2"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index ed7f026..dc0c961 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -760,17 +760,22 @@
       return;
     }
   }
-  // Ensure all fetched data is read and transfered to this XHR.
-  OnURLFetchDownloadProgress(source, 0, 0, 0);
-  fetch_callback_.reset();
-  fetch_mode_callback_.reset();
+
   const net::URLRequestStatus& status = source->GetStatus();
   if (status.is_success()) {
     stop_timeout_ = true;
     if (error_) {
+      // Ensure the fetch callbacks are reset when URL fetch is complete,
+      // regardless of error status.
+      fetch_callback_.reset();
+      fetch_mode_callback_.reset();
       return;
     }
 
+    // Ensure all fetched data is read and transfered to this XHR. This should
+    // only be done for successful and error-free fetches.
+    OnURLFetchDownloadProgress(source, 0, 0, 0);
+
     // The request may have completed too quickly, before URLFetcher's upload
     // progress timer had a chance to inform us upload is finished.
     if (!upload_complete_ && upload_listener_) {
@@ -786,6 +791,9 @@
   } else {
     HandleRequestError(kNetworkError);
   }
+
+  fetch_callback_.reset();
+  fetch_mode_callback_.reset();
 }
 
 // Reset some variables in case the XHR object is reused.
@@ -979,6 +987,7 @@
   FireProgressEvent(this, base::Tokens::loadend());
 
   fetch_callback_.reset();
+  fetch_mode_callback_.reset();
   DecrementActiveRequests();
 }
 
diff --git a/src/glimp/include/quad_drawer/helper.h b/src/glimp/include/quad_drawer/helper.h
deleted file mode 100644
index 7749fac..0000000
--- a/src/glimp/include/quad_drawer/helper.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2019 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// The QuadDrawer interface implementation.
-
-#ifndef GLIMP_INCLUDE_QUAD_DRAWER_HELPER_H_
-#define GLIMP_INCLUDE_QUAD_DRAWER_HELPER_H_
-
-#include "starboard/ps4/singleton.h"
-
-class QuadDrawerHelper : public starboard::ps4::Singleton<QuadDrawerHelper> {
- public:
-  typedef enum color_range {
-    kStudioRange = 0,  // Y [16..235], UV [16..240]
-    kFullRange = 1     // YUV/RGB [0..255]
-  } color_range_t;
-
-  typedef enum primary_id {
-    kPrimaryBt709 = 0,
-    kPrimaryBt2020 = 1
-  } primary_id_t;
-
-  struct QuadDrawerTarget {
-    unsigned int x;
-    unsigned int y;
-    unsigned int width;
-    unsigned int height;
-    unsigned int display_width;
-    unsigned int display_height;
-    unsigned char* planes[3];
-    int stride[3];
-    unsigned int bit_depth;
-    color_range_t color_range;
-    primary_id_t primary;
-  };
-
-  using QuadDrawerCallBack = void(void* context, void* target);
-
-  void SetCallBack(void* context, QuadDrawerCallBack* quad_drawer_callback);
-  QuadDrawerTarget* GetTarget();
-
- private:
-  QuadDrawerTarget quad_drawer_target_ = {};
-  QuadDrawerCallBack* quad_drawer_callback_ = nullptr;
-  void* context_ = nullptr;
-};
-
-#endif  // GLIMP_INCLUDE_QUAD_DRAWER_HELPER_H_
diff --git a/src/net/socket/tcp_socket_starboard.cc b/src/net/socket/tcp_socket_starboard.cc
index 829a279..5964db6 100644
--- a/src/net/socket/tcp_socket_starboard.cc
+++ b/src/net/socket/tcp_socket_starboard.cc
@@ -198,7 +198,7 @@
       // waiting for Accept() to succeed and 2. Peer address is unused in
       // most use cases. Chromium implementations get the address from accept()
       // directly, but Starboard API is incapable of that.
-      LOG(WARNING) << "Could not get peer address for the server socket.";
+      DVLOG(1) << "Could not get peer address for the server socket.";
     }
   }
 
diff --git a/src/net/third_party/quic/platform/impl/quic_logging_impl.h b/src/net/third_party/quic/platform/impl/quic_logging_impl.h
index 4d2bc0d..0e7c18d 100644
--- a/src/net/third_party/quic/platform/impl/quic_logging_impl.h
+++ b/src/net/third_party/quic/platform/impl/quic_logging_impl.h
@@ -18,41 +18,25 @@
 #define QUIC_LOG_IF_IMPL(severity, condition) \
   QUIC_CHROMIUM_LOG_IF_##severity(condition)
 
-#if defined(STARBOARD)
-#define QUIC_CHROMIUM_LOG_INFO DLOG(INFO)
-#else
 #define QUIC_CHROMIUM_LOG_INFO VLOG(1)
-#endif
 #define QUIC_CHROMIUM_LOG_WARNING DLOG(WARNING)
 #define QUIC_CHROMIUM_LOG_ERROR DLOG(ERROR)
 #define QUIC_CHROMIUM_LOG_FATAL LOG(FATAL)
 #define QUIC_CHROMIUM_LOG_DFATAL LOG(DFATAL)
 
-#if defined(STARBOARD)
-#define QUIC_CHROMIUM_DLOG_INFO DLOG(INFO)
-#else
 #define QUIC_CHROMIUM_DLOG_INFO DVLOG(1)
-#endif
 #define QUIC_CHROMIUM_DLOG_WARNING DLOG(WARNING)
 #define QUIC_CHROMIUM_DLOG_ERROR DLOG(ERROR)
 #define QUIC_CHROMIUM_DLOG_FATAL DLOG(FATAL)
 #define QUIC_CHROMIUM_DLOG_DFATAL DLOG(DFATAL)
 
-#if defined(STARBOARD)
-#define QUIC_CHROMIUM_LOG_IF_INFO(condition) DLOG_IF(INFO, condition)
-#else
 #define QUIC_CHROMIUM_LOG_IF_INFO(condition) VLOG_IF(1, condition)
-#endif
 #define QUIC_CHROMIUM_LOG_IF_WARNING(condition) DLOG_IF(WARNING, condition)
 #define QUIC_CHROMIUM_LOG_IF_ERROR(condition) DLOG_IF(ERROR, condition)
 #define QUIC_CHROMIUM_LOG_IF_FATAL(condition) LOG_IF(FATAL, condition)
 #define QUIC_CHROMIUM_LOG_IF_DFATAL(condition) LOG_IF(DFATAL, condition)
 
-#if defined(STARBOARD)
-#define QUIC_CHROMIUM_DLOG_IF_INFO(condition) DLOG_IF(INFO, condition)
-#else
 #define QUIC_CHROMIUM_DLOG_IF_INFO(condition) DVLOG_IF(1, condition)
-#endif
 #define QUIC_CHROMIUM_DLOG_IF_WARNING(condition) DLOG_IF(WARNING, condition)
 #define QUIC_CHROMIUM_DLOG_IF_ERROR(condition) DLOG_IF(ERROR, condition)
 #define QUIC_CHROMIUM_DLOG_IF_FATAL(condition) DLOG_IF(FATAL, condition)
@@ -70,11 +54,7 @@
 #define QUIC_LOG_WARNING_IS_ON_IMPL() 1
 #define QUIC_LOG_ERROR_IS_ON_IMPL() 1
 #endif
-#if defined(STARBOARD) && !defined(NDEBUG)
-#define QUIC_DLOG_INFO_IS_ON_IMPL() 1
-#else
 #define QUIC_DLOG_INFO_IS_ON_IMPL() 0
-#endif
 
 #if defined(OS_WIN)
 // wingdi.h defines ERROR to be 0. When we call QUIC_DLOG(ERROR), it gets
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
index 65e6dc8..e5a4254 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
@@ -26,6 +26,9 @@
     /** Get the name of the service. */
     public String getServiceName();
   }
+  /** Take in a reference to StarboardBridge & use it as needed. Default behavior is no-op. */
+  public void receiveStarboardBridge(StarboardBridge bridge) {}
+
   // Lifecycle
   /** Prepare service for start or resume. */
   public abstract void beforeStartOrResume();
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 49b37c0..85c7c56 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -525,6 +525,11 @@
     return false;
   }
 
+  /** Return the CobaltMediaSession. */
+  public CobaltMediaSession cobaltMediaSession() {
+    return cobaltMediaSession;
+  }
+
   public void registerCobaltService(CobaltService.Factory factory) {
     cobaltServiceFactories.put(factory.getServiceName(), factory);
   }
@@ -550,6 +555,7 @@
     }
     CobaltService service = factory.createCobaltService(nativeService);
     if (service != null) {
+      service.receiveStarboardBridge(this);
       cobaltServices.put(serviceName, service);
     }
     return service;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
index d9aba31..06cc02f 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/VoiceRecognizer.java
@@ -14,6 +14,8 @@
 
 package dev.cobalt.coat;
 
+import static dev.cobalt.util.Log.TAG;
+
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -24,6 +26,7 @@
 import android.speech.RecognizerIntent;
 import android.speech.SpeechRecognizer;
 import dev.cobalt.util.Holder;
+import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.util.ArrayList;
 
@@ -175,7 +178,12 @@
   }
 
   private void reset() {
-    speechRecognizer.destroy();
+    try {
+      speechRecognizer.destroy();
+    } catch (IllegalArgumentException ex) {
+      // Soft handling
+      Log.e(TAG, "Error in speechRecognizer.destroy()!", ex);
+    }
     speechRecognizer = null;
 
     nativeSpeechRecognizerImpl = 0;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
index fcc31b1..ae511c9 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -84,9 +84,17 @@
   private static final String[] PLAYBACK_STATE_NAME = {"playing", "paused", "none"};
 
   // Accessed on the main looper thread only.
-  private int playbackState = PLAYBACK_STATE_NONE;
+  private int currentPlaybackState = PLAYBACK_STATE_NONE;
   private boolean transientPause = false;
   private boolean suspended = true;
+  private boolean explicitUserActionRequired = false;
+
+  /** LifecycleCallback to notify listeners when |mediaSession| becomes active or inactive. */
+  public interface LifecycleCallback {
+    void onMediaSessionLifecycle(boolean isActive, MediaSessionCompat.Token token);
+  }
+
+  private LifecycleCallback lifecycleCallback = null;
 
   public CobaltMediaSession(
       Context context, Holder<Activity> activityHolder, UpdateVolumeListener volumeListener) {
@@ -98,7 +106,16 @@
     setMediaSession();
   }
 
+  public void setLifecycleCallback(LifecycleCallback lifecycleCallback) {
+    this.lifecycleCallback = lifecycleCallback;
+    if (lifecycleCallback != null) {
+      lifecycleCallback.onMediaSessionLifecycle(
+          this.mediaSession.isActive(), this.mediaSession.getSessionToken());
+    }
+  }
+
   private void setMediaSession() {
+    Log.i(TAG, "MediaSession new");
     mediaSession = new MediaSessionCompat(context, TAG);
     mediaSession.setFlags(MEDIA_SESSION_FLAG_HANDLES_TRANSPORT_CONTROLS);
     mediaSession.setCallback(
@@ -106,6 +123,7 @@
           @Override
           public void onFastForward() {
             Log.i(TAG, "MediaSession action: FAST FORWARD");
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_FAST_FORWARD);
           }
 
@@ -118,30 +136,35 @@
           @Override
           public void onPlay() {
             Log.i(TAG, "MediaSession action: PLAY");
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_PLAY);
           }
 
           @Override
           public void onRewind() {
             Log.i(TAG, "MediaSession action: REWIND");
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_REWIND);
           }
 
           @Override
           public void onSkipToNext() {
             Log.i(TAG, "MediaSession action: SKIP NEXT");
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
           }
 
           @Override
           public void onSkipToPrevious() {
             Log.i(TAG, "MediaSession action: SKIP PREVIOUS");
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
           }
 
           @Override
           public void onSeekTo(long pos) {
             Log.i(TAG, "MediaSession action: SEEK " + pos);
+            explicitUserActionRequired = false;
             nativeInvokeAction(PlaybackStateCompat.ACTION_SEEK_TO, pos);
           }
 
@@ -164,8 +187,10 @@
   }
 
   /**
-   * Sets system media resources active or not according to whether media is playing. This is
-   * idempotent as it may be called multiple times during the course of a media session.
+   * Sets system media resources active or not according to whether media is playing. The concept of
+   * "media focus" encapsulates wake lock, audio focus and active media session so that all three
+   * are set together to stay coherent as playback state changes. This is idempotent as it may be
+   * called multiple times during the course of a media session.
    */
   private void configureMediaFocus(int playbackState) {
     checkMainLooperThread();
@@ -186,8 +211,13 @@
       setMediaSession();
     }
     mediaSession.setActive(playbackState != PLAYBACK_STATE_NONE);
+    if (lifecycleCallback != null) {
+      lifecycleCallback.onMediaSessionLifecycle(
+          this.mediaSession.isActive(), this.mediaSession.getSessionToken());
+    }
     if (deactivating) {
       // Suspending lands here.
+      Log.i(TAG, "MediaSession release");
       mediaSession.release();
     }
   }
@@ -268,7 +298,7 @@
         // fall through
       case AudioManager.AUDIOFOCUS_LOSS:
         Log.i(TAG, "Audiofocus loss" + logExtra);
-        if (playbackState == PLAYBACK_STATE_PLAYING) {
+        if (currentPlaybackState == PLAYBACK_STATE_PLAYING) {
           Log.i(TAG, "Audiofocus action: PAUSE");
           nativeInvokeAction(PlaybackStateCompat.ACTION_PAUSE);
         }
@@ -285,7 +315,7 @@
         // The app has been granted audio focus (again). Raise volume to normal,
         // restart playback if necessary.
         volumeListener.onUpdateVolume(1.0f);
-        if (transientPause && playbackState == PLAYBACK_STATE_PAUSED) {
+        if (transientPause && currentPlaybackState == PLAYBACK_STATE_PAUSED) {
           Log.i(TAG, "Audiofocus action: PLAY");
           nativeInvokeAction(PlaybackStateCompat.ACTION_PLAY);
         }
@@ -295,6 +325,9 @@
 
     // Keep track of whether we're currently paused because of a transient loss of audiofocus.
     transientPause = (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+    // To restart playback after permanent loss, the user must take an explicit action.
+    // See: https://developer.android.com/guide/topics/media-apps/audio-focus
+    explicitUserActionRequired = (focusChange == AudioManager.AUDIOFOCUS_LOSS);
   }
 
   private AudioManager getAudioManager() {
@@ -315,17 +348,21 @@
     checkMainLooperThread();
     suspended = false;
     // Undoing what may have been done in suspendInternal().
-    configureMediaFocus(playbackState);
+    configureMediaFocus(currentPlaybackState);
   }
 
   public void suspend() {
-    mainHandler.post(
-        new Runnable() {
-          @Override
-          public void run() {
-            suspendInternal();
-          }
-        });
+    if (Looper.getMainLooper() == Looper.myLooper()) {
+      suspendInternal();
+    } else {
+      mainHandler.post(
+          new Runnable() {
+            @Override
+            public void run() {
+              suspendInternal();
+            }
+          });
+    }
   }
 
   private void suspendInternal() {
@@ -339,9 +376,9 @@
     // it's in a playing state. We'll configure it again in resumeInternal() and the HTML5 app will
     // be none the wiser.
     playbackStateBuilder.setState(
-        playbackState,
+        currentPlaybackState,
         PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
-        playbackState == PLAYBACK_STATE_PLAYING ? 1.0f : 0.0f);
+        currentPlaybackState == PLAYBACK_STATE_PLAYING ? 1.0f : 0.0f);
     configureMediaFocus(PLAYBACK_STATE_NONE);
   }
 
@@ -384,9 +421,10 @@
       final long duration) {
     checkMainLooperThread();
 
+    boolean hasStateChange = this.currentPlaybackState != playbackState;
     // Always keep track of what the HTML5 app thinks the playback state is so we can configure the
     // media focus correctly, either immediately or when resuming from being suspended.
-    this.playbackState = playbackState;
+    this.currentPlaybackState = playbackState;
 
     // Don't update anything while suspended.
     if (suspended) {
@@ -394,7 +432,25 @@
       return;
     }
 
-    configureMediaFocus(playbackState);
+    if (hasStateChange) {
+      if (playbackState == PLAYBACK_STATE_PLAYING) {
+        // We don't want to request media focus if |explicitUserActionRequired| is true when we
+        // don't have window focus. Ideally, we should recognize user action to re-request audio
+        // focus if |explicitUserActionRequired| is true. Currently we're not able to recognize
+        // it. But if we don't have window focus, we know the user is not interacting with our app
+        // and we should not request media focus.
+        if (!explicitUserActionRequired || activityHolder.get().hasWindowFocus()) {
+          explicitUserActionRequired = false;
+          configureMediaFocus(playbackState);
+        } else {
+          Log.w(TAG, "Audiofocus action: PAUSE (explicit user action required)");
+          nativeInvokeAction(PlaybackStateCompat.ACTION_PAUSE);
+        }
+      } else {
+        // It's fine to abandon media focus anytime.
+        configureMediaFocus(playbackState);
+      }
+    }
 
     // Ignore updates to the MediaSession metadata if playback is stopped.
     if (playbackState == PLAYBACK_STATE_NONE) {
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index 8b411b0..cae801e 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -249,7 +249,8 @@
       }
       break;
     case AndroidCommand::kNativeWindowDestroyed:
-      env->CallStarboardVoidMethodOrAbort("beforeSuspend", "()V");
+      // No need to JNI call StarboardBridge.beforeSuspend() since we did it
+      // early in SendAndroidCommand().
       {
         ScopedLock lock(android_command_mutex_);
         // Cobalt can't keep running without a window, even if the Activity
@@ -331,6 +332,14 @@
 void ApplicationAndroid::SendAndroidCommand(AndroidCommand::CommandType type,
                                             void* data) {
   SB_LOG(INFO) << "Send Android command: " << AndroidCommandName(type);
+  if (type == AndroidCommand::kNativeWindowDestroyed) {
+    // When this command is processed it will suspend Cobalt, so make the JNI
+    // call to StarboardBridge.beforeSuspend() early while still here on the
+    // Android main thread. This lets the MediaSession get released now without
+    // having to wait to bounce between threads.
+    JniEnvExt* env = JniEnvExt::Get();
+    env->CallStarboardVoidMethodOrAbort("beforeSuspend", "()V");
+  }
   AndroidCommand cmd {type, data};
   ScopedLock lock(android_command_mutex_);
   write(android_command_writefd_, &cmd, sizeof(cmd));
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
index 8fc3c9b..8d973cb 100644
--- a/src/starboard/android/shared/gyp_configuration.py
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -290,4 +290,9 @@
           # the following tests depend upon.
           'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/*',
       ],
+      'nplb': [
+          # This test is failing because localhost is not defined for IPv6 in
+          # /etc/hosts.
+          'SbSocketAddressTypes/SbSocketResolveTest.Localhost/1',
+      ],
   }
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
index 01ed2e3..398f3b5 100644
--- a/src/starboard/android/shared/media_codec_bridge.cc
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -245,14 +245,6 @@
   env->DeleteGlobalRef(j_media_codec_bridge_);
   j_media_codec_bridge_ = NULL;
 
-  SB_DCHECK(j_reused_dequeue_input_result_);
-  env->DeleteGlobalRef(j_reused_dequeue_input_result_);
-  j_reused_dequeue_input_result_ = NULL;
-
-  SB_DCHECK(j_reused_dequeue_output_result_);
-  env->DeleteGlobalRef(j_reused_dequeue_output_result_);
-  j_reused_dequeue_output_result_ = NULL;
-
   SB_DCHECK(j_reused_get_output_format_result_);
   env->DeleteGlobalRef(j_reused_get_output_format_result_);
   j_reused_get_output_format_result_ = NULL;
@@ -402,17 +394,6 @@
   JniEnvExt* env = JniEnvExt::Get();
   SB_DCHECK(env->GetObjectRefType(j_media_codec_bridge_) == JNIGlobalRefType);
 
-  j_reused_dequeue_input_result_ = env->NewObjectOrAbort(
-      "dev/cobalt/media/MediaCodecBridge$DequeueInputResult", "()V");
-  SB_DCHECK(j_reused_dequeue_input_result_);
-  j_reused_dequeue_input_result_ =
-      env->ConvertLocalRefToGlobalRef(j_reused_dequeue_input_result_);
-
-  j_reused_dequeue_output_result_ = env->NewObjectOrAbort(
-      "dev/cobalt/media/MediaCodecBridge$DequeueOutputResult", "()V");
-  SB_DCHECK(j_reused_dequeue_output_result_);
-  j_reused_dequeue_output_result_ =
-      env->ConvertLocalRefToGlobalRef(j_reused_dequeue_output_result_);
 
   j_reused_get_output_format_result_ = env->NewObjectOrAbort(
       "dev/cobalt/media/MediaCodecBridge$GetOutputFormatResult", "()V");
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
index 23ddf8a..0329f06 100644
--- a/src/starboard/android/shared/media_codec_bridge.h
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -110,7 +110,6 @@
 
   ~MediaCodecBridge();
 
-  DequeueInputResult DequeueInputBuffer(jlong timeout_us);
   // It is the responsibility of the client to manage the lifetime of the
   // jobject that |GetInputBuffer| returns.
   jobject GetInputBuffer(jint index);
@@ -124,7 +123,6 @@
                               const SbDrmSampleInfo& drm_sample_info,
                               jlong presentation_time_microseconds);
 
-  DequeueOutputResult DequeueOutputBuffer(jlong timeout_us);
   // It is the responsibility of the client to manage the lifetime of the
   // jobject that |GetOutputBuffer| returns.
   jobject GetOutputBuffer(jint index);
@@ -159,8 +157,6 @@
   // playback.  We mitigate this by reusing these output objects between calls
   // to |DequeueInputBuffer|, |DequeueOutputBuffer|, and
   // |GetOutputDimensions|.
-  jobject j_reused_dequeue_input_result_ = NULL;
-  jobject j_reused_dequeue_output_result_ = NULL;
   jobject j_reused_get_output_format_result_ = NULL;
 
   SB_DISALLOW_COPY_AND_ASSIGN(MediaCodecBridge);
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index fe757cc..482b6a8 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -466,8 +466,15 @@
     is_output_restricted_ = true;
     drm_system_->OnInsufficientOutputProtection();
   } else {
-    error_cb_(kSbPlayerErrorDecode,
-              FormatString("%s failed with status %d.", action_name, status));
+    if (media_type_ == kSbMediaTypeAudio) {
+      error_cb_(kSbPlayerErrorDecode,
+                FormatString("%s failed with status %d (audio).", action_name,
+                             status));
+    } else {
+      error_cb_(kSbPlayerErrorDecode,
+                FormatString("%s failed with status %d (video).", action_name,
+                             status));
+    }
   }
 
   if (retry) {
@@ -489,7 +496,15 @@
                   << " error with message: " << diagnostic_info;
 
   if (!is_transient) {
-    error_cb_(kSbPlayerErrorDecode, "OnMediaCodecError");
+    if (media_type_ == kSbMediaTypeAudio) {
+      error_cb_(kSbPlayerErrorDecode,
+                "OnMediaCodecError (audio): " + diagnostic_info +
+                    (is_recoverable ? ", recoverable " : ", unrecoverable "));
+    } else {
+      error_cb_(kSbPlayerErrorDecode,
+                "OnMediaCodecError (video): " + diagnostic_info +
+                    (is_recoverable ? ", recoverable " : ", unrecoverable "));
+    }
   }
 }
 
diff --git a/src/starboard/linux/shared/media_is_video_supported.cc b/src/starboard/linux/shared/media_is_video_supported.cc
index dcb89b9..7c59b66 100644
--- a/src/starboard/linux/shared/media_is_video_supported.cc
+++ b/src/starboard/linux/shared/media_is_video_supported.cc
@@ -53,7 +53,7 @@
   SB_UNREFERENCED_PARAMETER(level);
 
   if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) {
-    if (bit_depth != 10) {
+    if (bit_depth != 10 && bit_depth != 12) {
       return false;
     }
     if (video_codec != kSbMediaVideoCodecAv1 &&
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h b/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
index 53a5d3f..d2ba6f7 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/10/configuration_public.h
@@ -18,6 +18,9 @@
 #ifndef STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_10_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_10_CONFIGURATION_PUBLIC_H_
 
+#undef SB_API_VERSION
+#define SB_API_VERSION 10
+
 #include "starboard/linux/x64x11/blittergles/configuration_public.h"
 
 #endif  // STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_10_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h b/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
index 55cf0ba..561cb2b 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/11/configuration_public.h
@@ -18,6 +18,9 @@
 #ifndef STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_11_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_11_CONFIGURATION_PUBLIC_H_
 
+#undef SB_API_VERSION
+#define SB_API_VERSION 11
+
 #include "starboard/linux/x64x11/blittergles/configuration_public.h"
 
 #endif  // STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/6/configuration_public.h b/src/starboard/linux/x64x11/blittergles/sbversion/6/configuration_public.h
index e73676b..678695d 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/6/configuration_public.h
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/6/configuration_public.h
@@ -18,6 +18,9 @@
 #ifndef STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_6_CONFIGURATION_PUBLIC_H_
 #define STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_6_CONFIGURATION_PUBLIC_H_
 
+#undef SB_API_VERSION
+#define SB_API_VERSION 6
+
 #include "starboard/linux/x64x11/blittergles/configuration_public.h"
 
 #endif  // STARBOARD_LINUX_X64X11_BLITTERGLES_SBVERSION_6_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/blittergles/sbversion/6/gyp_configuration.py b/src/starboard/linux/x64x11/blittergles/sbversion/6/gyp_configuration.py
index 6930874..228c1ba 100644
--- a/src/starboard/linux/x64x11/blittergles/sbversion/6/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/blittergles/sbversion/6/gyp_configuration.py
@@ -20,7 +20,7 @@
 
 
 class LinuxX64X11BlitterglesSbversion6Configuration(
-    parent_configuration.LinuxX64X11Configuration('linux-x64x11-blittergles')):
+    parent_configuration.LinuxX64X11Configuration):
 
   def GetVariables(self, config_name):
     variables = super(LinuxX64X11BlitterglesSbversion6Configuration,
@@ -35,5 +35,5 @@
 
 
 def CreatePlatformConfig():
-  return parent_configuration.LinuxX64X11Configuration(
+  return LinuxX64X11BlitterglesSbversion6Configuration(
       'linux-x64x11-blittergles-sbversion-6')
diff --git a/src/starboard/shared/blittergles/blitter_create_default_device.cc b/src/starboard/shared/blittergles/blitter_create_default_device.cc
index 8435118..41bb497 100644
--- a/src/starboard/shared/blittergles/blitter_create_default_device.cc
+++ b/src/starboard/shared/blittergles/blitter_create_default_device.cc
@@ -15,6 +15,7 @@
 #include "starboard/blitter.h"
 
 #include <EGL/egl.h>
+#include <sanitizer/lsan_interface.h>
 
 #include <memory>
 
@@ -96,13 +97,27 @@
   SbBlitterDestroySurface(dummy_surface);
 }
 
+#if defined ADDRESS_SANITIZER
+
+class ScopedLeakSanitizerDisabler {
+ public:
+  ScopedLeakSanitizerDisabler() { __lsan_disable(); }
+  ~ScopedLeakSanitizerDisabler() { __lsan_enable(); }
+};
+#define ANNOTATE_SCOPED_MEMORY_LEAK \
+    ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast<void>(0)
+
+#else
+
+#define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0)
+
+#endif
 }  // namespace
 
 SbBlitterDevice SbBlitterCreateDefaultDevice() {
   starboard::shared::blittergles::SbBlitterDeviceRegistry* device_registry =
       starboard::shared::blittergles::GetBlitterDeviceRegistry();
   starboard::ScopedLock lock(device_registry->mutex);
-
   if (device_registry->default_device) {
     SB_DLOG(ERROR) << ": Default device has already been created.";
     return kSbBlitterInvalidDevice;
@@ -115,9 +130,14 @@
     return kSbBlitterInvalidDevice;
   }
 
-  if (!eglInitialize(device->display, NULL, NULL)) {
-    SB_DLOG(ERROR) << ": Failed to initialize device.";
-    return kSbBlitterInvalidDevice;
+  {
+    // Despite eglTerminate() being used in SbBlitterDestroyDevice(), the
+    // current mesa egl drivers still leak memory.
+    ANNOTATE_SCOPED_MEMORY_LEAK;
+    if (!eglInitialize(device->display, NULL, NULL)) {
+      SB_DLOG(ERROR) << ": Failed to initialize device.";
+      return kSbBlitterInvalidDevice;
+    }
   }
 
   starboard::optional<EGLConfig> config = GetEGLConfig(device->display);
diff --git a/src/starboard/shared/libaom/aom_video_decoder.cc b/src/starboard/shared/libaom/aom_video_decoder.cc
index f75f816..f2caac4 100644
--- a/src/starboard/shared/libaom/aom_video_decoder.cc
+++ b/src/starboard/shared/libaom/aom_video_decoder.cc
@@ -240,7 +240,8 @@
     }
   }
 
-  if (aom_image->bit_depth != 8 && aom_image->bit_depth != 10) {
+  if (aom_image->bit_depth != 8 && aom_image->bit_depth != 10 &&
+      aom_image->bit_depth != 12) {
     SB_DLOG(ERROR) << "Unsupported bit depth " << aom_image->bit_depth;
     ReportError(
         FormatString("Unsupported bit depth %d.", aom_image->bit_depth));
diff --git a/src/starboard/shared/libde265/de265_video_decoder.cc b/src/starboard/shared/libde265/de265_video_decoder.cc
index 13db895..619f962 100644
--- a/src/starboard/shared/libde265/de265_video_decoder.cc
+++ b/src/starboard/shared/libde265/de265_video_decoder.cc
@@ -249,7 +249,7 @@
   int strides[kImagePlanes];
 
   auto bit_depth = de265_get_bits_per_pixel(image, 0);
-  if (bit_depth != 8 && bit_depth != 10) {
+  if (bit_depth != 8 && bit_depth != 10 && bit_depth != 12) {
     SB_DLOG(ERROR) << "Unsupported bit depth " << bit_depth;
     ReportError(FormatString("Unsupported bit depth %d.", bit_depth));
     return;
diff --git a/src/starboard/shared/libvpx/vpx_video_decoder.cc b/src/starboard/shared/libvpx/vpx_video_decoder.cc
index 1a1029b..9cb5253 100644
--- a/src/starboard/shared/libvpx/vpx_video_decoder.cc
+++ b/src/starboard/shared/libvpx/vpx_video_decoder.cc
@@ -238,7 +238,8 @@
     }
   }
 
-  if (vpx_image->bit_depth != 8 && vpx_image->bit_depth != 10) {
+  if (vpx_image->bit_depth != 8 && vpx_image->bit_depth != 10 &&
+      vpx_image->bit_depth != 12) {
     SB_DLOG(ERROR) << "Unsupported bit depth " << vpx_image->bit_depth;
     ReportError(
         FormatString("Unsupported bit depth %d.", vpx_image->bit_depth));
diff --git a/src/starboard/shared/starboard/decode_target/decode_target_context_runner.h b/src/starboard/shared/starboard/decode_target/decode_target_context_runner.h
index 012eb23..4d3bcee 100644
--- a/src/starboard/shared/starboard/decode_target/decode_target_context_runner.h
+++ b/src/starboard/shared/starboard/decode_target/decode_target_context_runner.h
@@ -30,6 +30,10 @@
       SbDecodeTargetGraphicsContextProvider* provider);
   void RunOnGlesContext(std::function<void()> function);
 
+  SbDecodeTargetGraphicsContextProvider* context_provider() const {
+    return provider_;
+  }
+
  private:
   SbDecodeTargetGraphicsContextProvider* provider_;
 };
diff --git a/src/starboard/shared/starboard/media/media_util.cc b/src/starboard/shared/starboard/media/media_util.cc
index 50a26ac..03bbdfa 100644
--- a/src/starboard/shared/starboard/media/media_util.cc
+++ b/src/starboard/shared/starboard/media/media_util.cc
@@ -115,15 +115,19 @@
 
   std::string eotf = mime_type.GetParamStringValue("eotf", "");
   if (!eotf.empty()) {
-    SB_LOG_IF(WARNING, transfer_id != kSbMediaTransferIdUnspecified)
-        << "transfer_id " << transfer_id << " set by the codec string \""
-        << codec << "\" will be overwritten by the eotf attribute " << eotf;
-    transfer_id = GetTransferIdFromString(eotf);
+    SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf);
     // If the eotf is not known, reject immediately - without checking with
     // the platform.
-    if (transfer_id == kSbMediaTransferIdUnknown) {
+    if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) {
       return false;
     }
+    if (transfer_id != kSbMediaTransferIdUnspecified &&
+        transfer_id != transfer_id_from_eotf) {
+      SB_LOG_IF(WARNING, transfer_id != kSbMediaTransferIdUnspecified)
+          << "transfer_id " << transfer_id << " set by the codec string \""
+          << codec << "\" will be overwritten by the eotf attribute " << eotf;
+    }
+    transfer_id = transfer_id_from_eotf;
 #if !SB_HAS(MEDIA_IS_VIDEO_SUPPORTED_REFINEMENT)
     if (!SbMediaIsTransferCharacteristicsSupported(transfer_id)) {
       return false;
@@ -222,17 +226,20 @@
   }
 
   if (primary_id != kSbMediaPrimaryIdBt709 &&
-      primary_id != kSbMediaPrimaryIdUnspecified) {
+      primary_id != kSbMediaPrimaryIdUnspecified &&
+      primary_id != kSbMediaPrimaryIdSmpte170M) {
     return false;
   }
 
   if (transfer_id != kSbMediaTransferIdBt709 &&
-      transfer_id != kSbMediaTransferIdUnspecified) {
+      transfer_id != kSbMediaTransferIdUnspecified &&
+      transfer_id != kSbMediaTransferIdSmpte170M) {
     return false;
   }
 
   if (matrix_id != kSbMediaMatrixIdBt709 &&
-      matrix_id != kSbMediaMatrixIdUnspecified) {
+      matrix_id != kSbMediaMatrixIdUnspecified &&
+      matrix_id != kSbMediaMatrixIdSmpte170M) {
     return false;
   }
 
diff --git a/src/starboard/shared/starboard/player/filter/cpu_video_frame.cc b/src/starboard/shared/starboard/player/filter/cpu_video_frame.cc
index 1d79355..3aecfd4 100644
--- a/src/starboard/shared/starboard/player/filter/cpu_video_frame.cc
+++ b/src/starboard/shared/starboard/player/filter/cpu_video_frame.cc
@@ -77,10 +77,21 @@
   return s_clamp_table[component + 512];
 }
 
-void Copy10bitsPlane(uint8_t* destination, const uint8_t* source, int pixels) {
+void CopyPlane(int bit_depth,
+               uint8_t* destination,
+               const uint8_t* source,
+               int pixels) {
+  if (bit_depth == 8) {
+    SbMemoryCopy(destination, source, pixels);
+    return;
+  }
+  SB_DCHECK(bit_depth == 10 || bit_depth == 12);
+
+  const int conversion_factor = 1 << (bit_depth - 8);
+
   const uint16_t* source_in_uint16 = reinterpret_cast<const uint16_t*>(source);
   while (pixels > 0) {
-    *destination = static_cast<uint8_t>(*source_in_uint16 / 4);
+    *destination = static_cast<uint8_t>(*source_in_uint16 / conversion_factor);
     ++source_in_uint16;
     ++destination;
     --pixels;
@@ -174,7 +185,7 @@
     const uint8_t* y,
     const uint8_t* u,
     const uint8_t* v) {
-  SB_DCHECK(bit_depth == 8 || bit_depth == 10);
+  SB_DCHECK(bit_depth == 8 || bit_depth == 10 || bit_depth == 12);
 
   scoped_refptr<CpuVideoFrame> frame(new CpuVideoFrame(timestamp));
   frame->format_ = kYV12;
@@ -182,7 +193,7 @@
   frame->height_ = height;
 
   auto destination_pitch_in_bytes = source_pitch_in_bytes;
-  if (bit_depth == 10) {
+  if (bit_depth == 10 || bit_depth == 12) {
     // Reduce destination pitch to half as it will be converted into 8 bits.
     destination_pitch_in_bytes /= 2;
   }
@@ -200,21 +211,13 @@
   frame->pixel_buffer_.reset(
       new uint8_t[y_plane_size_in_bytes + uv_plane_size_in_bytes * 2]);
 
-  if (bit_depth == 8) {
-    SbMemoryCopy(frame->pixel_buffer_.get(), y, y_plane_size_in_bytes);
-    SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes, u,
-                 uv_plane_size_in_bytes);
-    SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes +
-                     uv_plane_size_in_bytes,
-                 v, uv_plane_size_in_bytes);
-  } else {
-    Copy10bitsPlane(frame->pixel_buffer_.get(), y, y_plane_size_in_bytes);
-    Copy10bitsPlane(frame->pixel_buffer_.get() + y_plane_size_in_bytes, u,
-                    uv_plane_size_in_bytes);
-    Copy10bitsPlane(frame->pixel_buffer_.get() + y_plane_size_in_bytes +
-                        uv_plane_size_in_bytes,
-                    v, uv_plane_size_in_bytes);
-  }
+  CopyPlane(bit_depth, frame->pixel_buffer_.get(), y, y_plane_size_in_bytes);
+  CopyPlane(bit_depth, frame->pixel_buffer_.get() + y_plane_size_in_bytes, u,
+            uv_plane_size_in_bytes);
+  CopyPlane(bit_depth,
+            frame->pixel_buffer_.get() + y_plane_size_in_bytes +
+                uv_plane_size_in_bytes,
+            v, uv_plane_size_in_bytes);
 
   frame->planes_.push_back(Plane(width, height, destination_pitch_in_bytes,
                                  frame->pixel_buffer_.get()));
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index 6f5593e..ea3a192 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -493,9 +493,14 @@
 }
 
 SbDecodeTarget FilterBasedPlayerWorkerHandler::GetCurrentDecodeTarget() {
-  ::starboard::ScopedLock lock(video_renderer_existence_mutex_);
-  return video_renderer_ ? video_renderer_->GetCurrentDecodeTarget()
-                         : kSbDecodeTargetInvalid;
+  SbDecodeTarget decode_target = kSbDecodeTargetInvalid;
+  if (video_renderer_existence_mutex_.AcquireTry()) {
+    if (video_renderer_) {
+      decode_target = video_renderer_->GetCurrentDecodeTarget();
+    }
+    video_renderer_existence_mutex_.Release();
+  }
+  return decode_target;
 }
 
 MediaTimeProvider* FilterBasedPlayerWorkerHandler::GetMediaTimeProvider()
diff --git a/src/starboard/shared/starboard/player/filter/video_render_algorithm_impl.cc b/src/starboard/shared/starboard/player/filter/video_render_algorithm_impl.cc
index 0c4634a..717eec3 100644
--- a/src/starboard/shared/starboard/player/filter/video_render_algorithm_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/video_render_algorithm_impl.cc
@@ -26,7 +26,9 @@
     const GetRefreshRateFn& get_refresh_rate_fn)
     : get_refresh_rate_fn_(get_refresh_rate_fn) {
   if (get_refresh_rate_fn_) {
-    SB_LOG(INFO) << "VideoRenderAlgorithmImpl will render with cadence control";
+    SB_LOG(INFO) << "VideoRenderAlgorithmImpl will render with cadence control "
+                 << "with display refresh rate set at "
+                 << get_refresh_rate_fn_();
   }
 }
 
@@ -154,6 +156,12 @@
     return;
   }
 
+  auto refresh_rate = get_refresh_rate_fn_();
+  SB_DCHECK(refresh_rate >= 1);
+  if (refresh_rate < 1) {
+    refresh_rate = 60;
+  }
+
   bool is_audio_playing;
   bool is_audio_eos_played;
   bool is_underflow;
@@ -165,28 +173,38 @@
     frame_rate_estimate_.Update(*frames);
     auto frame_rate = frame_rate_estimate_.frame_rate();
     SB_DCHECK(frame_rate != VideoFrameRateEstimator::kInvalidFrameRate);
-    cadence_pattern_generator_.UpdateRefreshRateAndMaybeReset(
-        get_refresh_rate_fn_());
+    cadence_pattern_generator_.UpdateRefreshRateAndMaybeReset(refresh_rate);
     cadence_pattern_generator_.UpdateFrameRate(frame_rate);
     SB_DCHECK(cadence_pattern_generator_.has_cadence());
 
-    if (current_frame_rendered_times_ >=
-        cadence_pattern_generator_.GetNumberOfTimesCurrentFrameDisplays()) {
-      frames->pop_front();
-      cadence_pattern_generator_.AdvanceToNextFrame();
-      break;
-    }
-
     auto second_iter = frames->begin();
     ++second_iter;
 
-    if ((*second_iter)->is_end_of_stream() ||
-        (*second_iter)->timestamp() > media_time) {
+    if ((*second_iter)->is_end_of_stream()) {
       break;
     }
 
     auto frame_duration =
-        static_cast<SbTime>(kSbTimeSecond / get_refresh_rate_fn_());
+        static_cast<SbTime>(kSbTimeSecond / refresh_rate);
+
+    if (current_frame_rendered_times_ >=
+        cadence_pattern_generator_.GetNumberOfTimesCurrentFrameDisplays()) {
+      if (current_frame_rendered_times_ == 0) {
+        ++dropped_frames_;
+      }
+      frames->pop_front();
+      cadence_pattern_generator_.AdvanceToNextFrame();
+      current_frame_rendered_times_ = 0;
+      if (cadence_pattern_generator_.GetNumberOfTimesCurrentFrameDisplays()
+          == 0) {
+        continue;
+      }
+      if (frames->front()->timestamp() <= media_time - frame_duration) {
+        continue;
+      }
+      break;
+    }
+
     if ((*second_iter)->timestamp() > media_time - frame_duration) {
       break;
     }
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
index 396d832..0eae09d 100644
--- a/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
@@ -318,8 +318,19 @@
   {
     ScopedLock scoped_lock_decoder_frames(decoder_frames_mutex_);
     sink_frames_mutex_.Acquire();
-    sink_frames_.insert(sink_frames_.end(), decoder_frames_.begin(),
-                        decoder_frames_.end());
+    for (auto decoder_frame : decoder_frames_) {
+      if (sink_frames_.empty()) {
+        sink_frames_.push_back(decoder_frame);
+        continue;
+      }
+      if (sink_frames_.back()->is_end_of_stream()) {
+        continue;
+      }
+      if (decoder_frame->is_end_of_stream() ||
+          decoder_frame->timestamp() > sink_frames_.back()->timestamp()) {
+        sink_frames_.push_back(decoder_frame);
+      }
+    }
     decoder_frames_.clear();
   }
   size_t number_of_sink_frames = sink_frames_.size();
diff --git a/src/starboard/starboard_all.gyp b/src/starboard/starboard_all.gyp
index a5b3291..76bb448 100644
--- a/src/starboard/starboard_all.gyp
+++ b/src/starboard/starboard_all.gyp
@@ -66,6 +66,7 @@
         '<(DEPTH)/starboard/nplb/blitter_pixel_tests/blitter_pixel_tests.gyp:*',
         '<(DEPTH)/starboard/nplb/nplb.gyp:*',
         '<(DEPTH)/starboard/starboard.gyp:*',
+        '<(DEPTH)/starboard/tools/tools.gyp:*',
       ],
       'conditions': [
         ['gl_type != "none"', {
diff --git a/src/starboard/tools/app_launcher_packager.py b/src/starboard/tools/app_launcher_packager.py
index 092b387..9be2843 100644
--- a/src/starboard/tools/app_launcher_packager.py
+++ b/src/starboard/tools/app_launcher_packager.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-#
 # Copyright 2017 The Cobalt Authors. All Rights Reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,54 +19,21 @@
 that the app launcher can be run independent of the Cobalt source tree.
 """
 
-
-################################################################################
-#                                   API                                        #
-################################################################################
-
-
-def CopyAppLauncherTools(repo_root, dest_root,
-                         additional_glob_patterns=None,
-                         include_black_box_tests=True):
-  """Copies app launcher related files to the destination root.
-  repo_root: The 'src' path that will be used for packaging.
-  dest_root: The directory where the src files will be stored.
-  additional_glob_patterns: Some platforms may need to include certain
-    dependencies beyond the default include file patterns. The results here will
-    be merged in with _INCLUDE_FILE_PATTERNS.
-  include_black_box_tests: If True then the resources for the black box tests
-    are included."""
-  _CopyAppLauncherTools(repo_root, dest_root,
-                        additional_glob_patterns,
-                        include_black_box_tests=include_black_box_tests)
-
-
-def MakeZipArchive(src, output_zip):
-  """Convenience function to zip up all files in the src directory (produced
-  as dest_root argument in CopyAppLauncherTools()) which will create a zip
-  file with the relative root being the src directory."""
-  _MakeZipArchive(src, output_zip)
-
-
-################################################################################
-#                                  IMPL                                        #
-################################################################################
-
-
 import argparse
 import fnmatch
 import logging
 import os
 import shutil
 import sys
+import tempfile
 
 import _env  # pylint: disable=unused-import
 from paths import REPOSITORY_ROOT
 from paths import THIRD_PARTY_ROOT
 sys.path.append(THIRD_PARTY_ROOT)
-import starboard.tools.platform
+# pylint: disable=g-import-not-at-top,g-bad-import-order
 import jinja2
-
+import starboard.tools.platform
 
 # Default python directories to app launcher resources.
 _INCLUDE_FILE_PATTERNS = [
@@ -79,11 +44,10 @@
     ('lbshell', '*.py'),
     ('starboard', '*.py'),
     # jinja2 required by this app_launcher_packager.py script.
-    ('third_party/jinja2',  '*.py'),
-    ('third_party/markupsafe', '*.py'), # Required by third_party/jinja2
+    ('third_party/jinja2', '*.py'),
+    ('third_party/markupsafe', '*.py'),  # Required by third_party/jinja2
 ]
 
-
 _INCLUDE_BLACK_BOX_TESTS_PATTERNS = [
     # Black box and web platform tests have non-py assets, so everything
     # is picked up.
@@ -95,6 +59,20 @@
 # Do not allow .git directories to make it into the build.
 _EXCLUDE_DIRECTORY_PATTERNS = ['.git']
 
+_IS_WINDOWS = sys.platform in ['win32', 'cygwin']
+
+
+def _ToWinUncPath(dos_path, encoding=None):
+  """Windows supports long file names when using a UNC path."""
+  assert _IS_WINDOWS
+  do_convert = (not isinstance(dos_path, unicode) and encoding is not None)
+  if do_convert:
+    dos_path = dos_path.decode(encoding)
+  path = os.path.abspath(dos_path)
+  if path.startswith(u'\\\\'):
+    return u'\\\\?\\UNC\\' + path[2:]
+  return u'\\\\?\\' + path
+
 
 def _MakeDir(d):
   """Make the specified directory and any parents in the path.
@@ -114,18 +92,24 @@
   Args:
     source_root: Absolute path to the root of the files to be copied.
     d: Directory to be checked.
+
+  Returns:
+    true if d in in source_root/out.
   """
   out_dir = os.path.join(source_root, 'out')
   return out_dir in d
 
 
-def _FindFilesRecursive(src_root, glob_pattern):
+def _FindFilesRecursive(  # pylint: disable=missing-docstring
+    src_root, glob_pattern):
   src_root = os.path.normpath(src_root)
   logging.info('Searching in %s for %s type files.', src_root, glob_pattern)
   file_list = []
   for root, dirs, files in os.walk(src_root, topdown=True):
     # Prunes when using os.walk with topdown=True
-    [dirs.remove(d) for d in list(dirs) if d in _EXCLUDE_DIRECTORY_PATTERNS]
+    for d in list(dirs):
+      if d in _EXCLUDE_DIRECTORY_PATTERNS:
+        dirs.remove(d)
     # Eliminate any locally built files under the out directory.
     if _IsOutDir(src_root, root):
       continue
@@ -166,12 +150,47 @@
   logging.info('Finished baking in platform info files.')
 
 
-def _CopyAppLauncherTools(repo_root, dest_root, additional_glob_patterns,
-                          include_black_box_tests):
-  # Step 1: Remove previous output directory if it exists
+def CopyAppLauncherTools(repo_root,
+                         dest_root,
+                         additional_glob_patterns=None,
+                         include_black_box_tests=True):
+  """Copies app launcher related files to the destination root.
+
+  Args:
+    repo_root: The 'src' path that will be used for packaging.
+    dest_root: The directory where the src files will be stored.
+    additional_glob_patterns: Some platforms may need to include certain
+      dependencies beyond the default include file patterns. The results here
+      will be merged in with _INCLUDE_FILE_PATTERNS.
+    include_black_box_tests: If True then the resources for the black box tests
+      are included.
+  """
+  dest_root = _PrepareDestination(dest_root)
+  copy_list = _GetSourceFilesList(repo_root, additional_glob_patterns,
+                                  include_black_box_tests)
+  _CopyFiles(repo_root, dest_root, copy_list)
+
+
+def _PrepareDestination(dest_root):  # pylint: disable=missing-docstring
+  # Make sure dest_root is an absolute path.
+  logging.info('Copying App Launcher tools to = %s', dest_root)
+  dest_root = os.path.normpath(dest_root)
+  if not os.path.isabs(dest_root):
+    dest_root = os.path.join(os.getcwd(), dest_root)
+  if _IS_WINDOWS:
+    dest_root = _ToWinUncPath(dest_root)
+  logging.info('Absolute destination path = %s', dest_root)
+  # Remove previous output directory if it exists
   if os.path.isdir(dest_root):
     shutil.rmtree(dest_root)
-  # Step 2: Find all glob files from specified search directories.
+  return dest_root
+
+
+def _GetSourceFilesList(  # pylint: disable=missing-docstring
+    repo_root,
+    additional_glob_patterns=None,
+    include_black_box_tests=True):
+  # Find all glob files from specified search directories.
   include_glob_patterns = _INCLUDE_FILE_PATTERNS
   if additional_glob_patterns:
     include_glob_patterns += additional_glob_patterns
@@ -189,8 +208,13 @@
   # Order by file path string and remove any duplicate paths.
   copy_list = list(set(copy_list))
   copy_list.sort()
+  return copy_list
+
+
+def _CopyFiles(  # pylint: disable=missing-docstring
+    repo_root, dest_root, copy_list):
+  # Copy the src files to the destination directory.
   folders_logged = set()
-  # Step 3: Copy the src files to the destination directory.
   for src in copy_list:
     tail_path = os.path.relpath(src, repo_root)
     dst = os.path.join(dest_root, tail_path)
@@ -198,15 +222,24 @@
     if not os.path.isdir(d):
       os.makedirs(d)
     src_folder = os.path.dirname(src)
-    if not src_folder in folders_logged:
+    if src_folder not in folders_logged:
       folders_logged.add(src_folder)
       logging.info(src_folder + ' -> ' + os.path.dirname(dst))
     shutil.copy2(src, dst)
-  # Step 4: Re-write the platform infos file in the new repo copy.
+  # Re-write the platform infos file in the new repo copy.
   _WritePlatformsInfo(repo_root, dest_root)
 
 
-def _MakeZipArchive(src, output_zip):
+def MakeZipArchive(src, output_zip):
+  """Convenience function to zip up all files in the src directory.
+
+  Intended for use with the dest_root output from CopyAppLauncherTools() to
+  create a zip file with the relative root being the src directory.
+
+  Args:
+    src: Path to the directory of files to zip up.
+    output_zip: Path to the zip file to create.
+  """
   if os.path.isfile(output_zip):
     os.unlink(output_zip)
   logging.info('Creating a zip file of the app launcher package')
@@ -218,14 +251,49 @@
 def main(command_args):
   logging.basicConfig(level=logging.INFO)
   parser = argparse.ArgumentParser()
-  parser.add_argument(
+  dest_group = parser.add_mutually_exclusive_group(required=True)
+  dest_group.add_argument(
       '-d',
       '--destination_root',
-      required=True,
       help='The path to the root of the destination folder into which the '
-           'application resources are packaged.')
+      'application resources are packaged.')
+  dest_group.add_argument(
+      '-z',
+      '--zip_file',
+      help='The path to a zip file into which the application resources are '
+      'packaged.')
+  dest_group.add_argument(
+      '-l',
+      '--list',
+      action='store_true',
+      help='List to stdout the application resources relative to the current '
+      'directory.')
+  parser.add_argument(
+      '-v', '--verbose', action='store_true', help='Verbose logging output.')
   args = parser.parse_args(command_args)
-  CopyAppLauncherTools(REPOSITORY_ROOT, args.destination_root)
+
+  if not args.verbose:
+    logging.disable(logging.INFO)
+
+  if args.destination_root:
+    CopyAppLauncherTools(REPOSITORY_ROOT, args.destination_root)
+  elif args.zip_file:
+    try:
+      temp_dir = tempfile.mkdtemp(prefix='cobalt_app_launcher_')
+      CopyAppLauncherTools(REPOSITORY_ROOT, temp_dir)
+      MakeZipArchive(temp_dir, args.zip_file)
+    finally:
+      shutil.rmtree(temp_dir)
+  elif args.list:
+    for src_file in _GetSourceFilesList(REPOSITORY_ROOT):
+      # Skip paths with '$' since they won't get through the Ninja generator.
+      if '$' in src_file:
+        continue
+      # Relative to CWD where gyp ran this; same as '<(DEPTH)' in gyp file.
+      src_file = os.path.relpath(src_file)
+      # Forward slashes for gyp, even on Windows.
+      src_file = src_file.replace('\\', '/')
+      print src_file
   return 0
 
 
diff --git a/src/starboard/tools/testing/build_tests.py b/src/starboard/tools/testing/build_tests.py
index b02470e..c9f7082 100644
--- a/src/starboard/tools/testing/build_tests.py
+++ b/src/starboard/tools/testing/build_tests.py
@@ -1,7 +1,25 @@
+# Copyright 2018 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Common code for building various types of targets, typically tests."""
+
 import logging
 import os
 import subprocess
 
+APP_LAUNCHER_TARGET = 'app_launcher_zip'
+
+
 def BuildTargets(targets, out_directory, dry_run=False, extra_build_flags=[]):
   """Builds all specified targets.
 
@@ -16,6 +34,7 @@
   if dry_run:
     args_list.append("-n")
 
+  args_list.append(APP_LAUNCHER_TARGET)
   args_list.extend(["{}_deploy".format(test_name) for test_name in targets])
   args_list.extend(extra_build_flags)
 
diff --git a/src/starboard/tools/tools.gyp b/src/starboard/tools/tools.gyp
new file mode 100644
index 0000000..a80103f
--- /dev/null
+++ b/src/starboard/tools/tools.gyp
@@ -0,0 +1,47 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# The Starboard "all" target, which includes all interesting targets for the
+# Starboard project.
+
+{
+  'targets': [
+    {
+      'target_name': 'app_launcher_zip',
+      'type': 'none',
+      'variables': {
+        'app_launcher_packager_path': 'app_launcher_packager.py',
+        'app_launcher_zip_file': '<(PRODUCT_DIR)/app_launcher.zip',
+      },
+      'actions': [
+        {
+          'action_name': 'package_app_launcher',
+          'message': 'Zipping <(app_launcher_zip_file)',
+          'inputs': [
+            '<!@(["python", "<(app_launcher_packager_path)", "-l"])',
+          ],
+          'outputs': [
+            '<(app_launcher_zip_file)',
+          ],
+          'action': [
+            'python',
+            '<(app_launcher_packager_path)',
+            '-z',
+            '<(app_launcher_zip_file)',
+          ],
+        },
+      ],
+    },
+  ]
+}
diff --git a/src/third_party/mozjs-45/js/src/jit/x86-shared/Encoding-x86-shared.h b/src/third_party/mozjs-45/js/src/jit/x86-shared/Encoding-x86-shared.h
index 2457f85..99d8859 100644
--- a/src/third_party/mozjs-45/js/src/jit/x86-shared/Encoding-x86-shared.h
+++ b/src/third_party/mozjs-45/js/src/jit/x86-shared/Encoding-x86-shared.h
@@ -244,6 +244,16 @@
       case OP2_MOVSD_WsdVsd: // also OP2_MOVPS_WpsVps
       case OP2_MOVAPS_WsdVsd:
       case OP2_MOVDQ_WdqVdq:
+        return true;
+      default:
+        break;
+    }
+    return false;
+}
+
+inline bool IsXMMReversedOperands(ThreeByteOpcodeID opcode)
+{
+    switch (opcode) {
       case OP3_PEXTRD_EdVdqIb:
         return true;
       default: