Import Cobalt 20.lts.3.238772
diff --git a/src/cobalt/black_box_tests/README.md b/src/cobalt/black_box_tests/README.md
index 10ee071..82adffe 100644
--- a/src/cobalt/black_box_tests/README.md
+++ b/src/cobalt/black_box_tests/README.md
@@ -77,6 +77,6 @@
   1. Add a python test script in tests/.
   2. Add target web page(s) and associated resources(if any) to testdata/.
   3. Add the test name(name of the python test script) to black_box_tests.py
-     to automate new test. Add the name to either the list of tests requiring
+     to automate new test. Add the name to the list of tests requiring
      app launcher support for system signals(e.g. suspend/resume), or the list
-     of tests that don't.
+     of tests requiring deep link support, or the list of tests that don't.
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index 108972e..0154522 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
@@ -54,6 +53,11 @@
     'web_debugger',
     'web_platform_tests',
 ]
+# These tests can only be run on platforms whose app launcher can send deep
+# links.
+_TESTS_NEEDING_DEEP_LINK = [
+    'fire_deep_link_before_load',
+]
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
 # Platform dependent device parameters.
@@ -112,10 +116,13 @@
       output_file=None,
       out_directory=out_directory)
 
+  test_targets = _TESTS_NO_SIGNAL
+
   if launcher.SupportsSuspendResume():
-    test_targets = _TESTS_NEEDING_SYSTEM_SIGNAL + _TESTS_NO_SIGNAL
-  else:
-    test_targets = _TESTS_NO_SIGNAL
+    test_targets += _TESTS_NEEDING_SYSTEM_SIGNAL
+
+  if launcher.SupportsDeepLink():
+    test_targets += _TESTS_NEEDING_DEEP_LINK
 
   test_suite = unittest.TestSuite()
   for test in test_targets:
diff --git a/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.html b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.html
new file mode 100644
index 0000000..6be8649
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.html
@@ -0,0 +1,7 @@
+<HTML>
+  <HEAD></HEAD>
+  <BODY>
+    <script src='black_box_js_test_utils.js'></script>
+    <script src='fire_deep_link_before_load.js'></script>
+  </BODY>
+</HTML>
diff --git a/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js
new file mode 100644
index 0000000..cc2128c
--- /dev/null
+++ b/src/cobalt/black_box_tests/testdata/fire_deep_link_before_load.js
@@ -0,0 +1,35 @@
+// 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.
+
+// Fail if the deep link is not received within 15 seconds.
+var kTimeout = 15 * 1000;
+var failTimer = setTimeout(fail, kTimeout);
+
+function fail() {
+    console.log("Failing due to timeout!");
+    assertTrue(false);
+}
+
+// The test sends "link 1", "link 2", "link 3" before load & so only "link 3"
+// should be handled.
+function listener(link) {
+    console.log("Received link: " + link.toString());
+    assertEqual("link 3", link);
+    console.log("Ending test");
+    onEndTest();
+    clearTimeout(failTimer);
+}
+
+h5vcc.runtime.onDeepLink.addListener(listener);
+console.log("Listener added");
\ No newline at end of file
diff --git a/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py b/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py
new file mode 100644
index 0000000..1a3daba
--- /dev/null
+++ b/src/cobalt/black_box_tests/tests/fire_deep_link_before_load.py
@@ -0,0 +1,119 @@
+# 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.
+"""Tests sending deep links before load."""
+
+# This test script works by splitting the work over 3 threads, so that they
+# can each make progress even if they come across blocking operations.
+# The three threads are:
+#   1. Main thread, runs BlackBoxTestCase, sends suspend/resume signals, etc.
+#   2. HTTP Server, responsible for slowly responding to a fetch of a javascript
+#      file.
+#   3. Webdriver thread, instructs Cobalt to navigate to a URL
+#
+# Steps in ~ chronological order:
+#   1. Create a TCP socket and listen on all interfaces.
+#   2. Start Cobalt, and point it to the socket created in Step 1.
+#   3. Send 3 deep links.
+#   4. Load & run the javascript resource.
+#   5. Check to see if JSTestsSucceeded().
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import _env  # pylint: disable=unused-import,g-bad-import-order
+
+import os
+import SimpleHTTPServer
+import threading
+import traceback
+import urlparse
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+_FIRE_DEEP_LINK_BEFORE_LOAD_HTML = 'fire_deep_link_before_load.html'
+_FIRE_DEEP_LINK_BEFORE_LOAD_JS = 'fire_deep_link_before_load.js'
+_MAX_ALLOTTED_TIME_SECONDS = 60
+
+_links_fired = threading.Event()
+
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class JavascriptRequestDetector(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Proxies everything to SimpleHTTPRequestHandler, except some paths."""
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+
+    parsed_path = urlparse.urlparse(self.path)
+    if parsed_path.path == '/testdata/' + _FIRE_DEEP_LINK_BEFORE_LOAD_JS:
+      # It is important not to send any response back, so we block.
+      print('Waiting on links to be fired.')
+      _links_fired.wait()
+      print('Links have been fired. Getting JS.')
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+class FireDeepLinkBeforeLoad(black_box_tests.BlackBoxTestCase):
+  """Tests firing deep links before web module is loaded."""
+
+  def _LoadPage(self, webdriver, url):
+    """Instructs webdriver to navigate to url."""
+    try:
+      # Note: The following is a blocking request, and returns only when the
+      # page has fully loaded.  In this test, the page will not fully load
+      # so, this does not return until Cobalt exits.
+      webdriver.get(url)
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+
+  def test_simple(self):
+
+    # Step 2. Start Cobalt, and point it to the socket created in Step 1.
+    try:
+      with ThreadedWebServer(JavascriptRequestDetector,
+                             self.GetBindingAddress()) as server:
+        with self.CreateCobaltRunner(url='about:blank') as runner:
+          target_url = server.GetURL(file_name='../testdata/' +
+                                     _FIRE_DEEP_LINK_BEFORE_LOAD_HTML)
+          cobalt_launcher_thread = threading.Thread(
+              target=FireDeepLinkBeforeLoad._LoadPage,
+              args=(self, runner.webdriver, target_url))
+          cobalt_launcher_thread.start()
+
+          # Step 3. Send 3 deep links
+          for i in range(1, 4):
+            link = 'link ' + str(i)
+            print('Sending link : ' + link)
+            self.assertTrue(runner.SendDeepLink(link) == 0)
+          print('Links fired.')
+          # Step 4. Load & run the javascript resource.
+          _links_fired.set()
+
+          # Step 5. Check to see if JSTestsSucceeded().
+          # Note that this call will check the DOM multiple times for a period
+          # of time (current default is 30 seconds).
+          self.assertTrue(runner.JSTestsSucceeded())
+    except:  # pylint: disable=bare-except
+      traceback.print_exc()
+      # Consider an exception being thrown as a test failure.
+      self.assertTrue(False)
+    finally:
+      print('Cleaning up.')
+      _links_fired.set()
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index cb3cc35..223db5c 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -169,20 +169,23 @@
   }
 
 #if SB_API_VERSION >= 11
-  // Append the device authentication query parameters based on the platform's
-  // certification secret to the initial URL.
-  std::string query = initial_url.query();
-  std::string device_authentication_query_string =
-      GetDeviceAuthenticationSignedURLQueryString();
-  if (!query.empty() && !device_authentication_query_string.empty()) {
-    query += "&";
-  }
-  query += device_authentication_query_string;
+  if (!command_line->HasSwitch(
+          switches::kOmitDeviceAuthenticationQueryParameters)) {
+    // Append the device authentication query parameters based on the platform's
+    // certification secret to the initial URL.
+    std::string query = initial_url.query();
+    std::string device_authentication_query_string =
+        GetDeviceAuthenticationSignedURLQueryString();
+    if (!query.empty() && !device_authentication_query_string.empty()) {
+      query += "&";
+    }
+    query += device_authentication_query_string;
 
-  if (!query.empty()) {
-    GURL::Replacements replacements;
-    replacements.SetQueryStr(query);
-    initial_url = initial_url.ReplaceComponents(replacements);
+    if (!query.empty()) {
+      GURL::Replacements replacements;
+      replacements.SetQueryStr(query);
+      initial_url = initial_url.ReplaceComponents(replacements);
+    }
   }
 #endif  // SB_API_VERSION >= 11
 
@@ -633,6 +636,8 @@
   options.web_module_options.csp_enforcement_mode = dom::kCspEnforcementEnable;
 
   options.requested_viewport_size = requested_viewport_size;
+  options.web_module_loaded_callback =
+      base::Bind(&Application::DispatchEarlyDeepLink, base::Unretained(this));
   account_manager_.reset(new account::AccountManager());
   browser_module_.reset(
       new BrowserModule(initial_url,
@@ -812,6 +817,7 @@
 
 void Application::HandleStarboardEvent(const SbEvent* starboard_event) {
   DCHECK(starboard_event);
+  DCHECK_EQ(base::MessageLoop::current(), message_loop_);
 
   // Forward input events to |SystemWindow|.
   if (starboard_event->type == kSbEventTypeInput) {
@@ -865,7 +871,13 @@
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
     case kSbEventTypeLink: {
       const char* link = static_cast<const char*>(starboard_event->data);
-      DispatchEventInternal(new base::DeepLinkEvent(link));
+      if (browser_module_->IsWebModuleLoaded()) {
+        DLOG(INFO) << "Dispatching deep link " << link;
+        DispatchEventInternal(new base::DeepLinkEvent(link));
+      } else {
+        DLOG(INFO) << "Storing deep link " << link;
+        early_deep_link_ = link;
+      }
       break;
     }
     case kSbEventTypeAccessiblitySettingsChanged:
@@ -1146,5 +1158,14 @@
 }
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
+void Application::DispatchEarlyDeepLink() {
+  if (early_deep_link_.empty()) {
+    return;
+  }
+  DLOG(INFO) << "Dispatching early deep link " << early_deep_link_;
+  DispatchEventInternal(new base::DeepLinkEvent(early_deep_link_.c_str()));
+  early_deep_link_ = "";
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 66ff3c7..751a956 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -208,6 +208,13 @@
   void OnMemoryTrackerCommand(const std::string& message);
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
+  // The latest link received before the Web Module is loaded is stored here.
+  std::string early_deep_link_;
+
+  // Dispach events for early deeplink. This should be called once the Web
+  // Module is loaded.
+  void DispatchEarlyDeepLink();
+
   DISALLOW_COPY_AND_ASSIGN(Application);
 };
 
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 18aeeca..79456a7 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -307,7 +307,8 @@
       main_web_module_generation_(0),
       next_timeline_id_(1),
       current_splash_screen_timeline_id_(-1),
-      current_main_web_module_timeline_id_(-1) {
+      current_main_web_module_timeline_id_(-1),
+      web_module_loaded_callback_(options_.web_module_loaded_callback) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()");
 
   // Apply platform memory setting adjustments and defaults.
@@ -570,9 +571,7 @@
       base::Bind(&BrowserModule::OnLoad, base::Unretained(this)));
 #if defined(ENABLE_FAKE_MICROPHONE)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kFakeMicrophone) ||
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kInputFuzzer)) {
+          switches::kFakeMicrophone)) {
     options.dom_settings_options.microphone_options.enable_fake_microphone =
         true;
   }
@@ -679,7 +678,12 @@
   on_error_retry_count_ = 0;
 
   on_load_event_time_ = base::TimeTicks::Now().ToInternalValue();
+
   web_module_loaded_.Signal();
+
+  if (!web_module_loaded_callback_.is_null()) {
+    web_module_loaded_callback_.Run();
+  }
 }
 
 bool BrowserModule::WaitForLoad(const base::TimeDelta& timeout) {
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 0bc7d1a..b2287e7 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -106,6 +106,7 @@
     base::Optional<cssom::ViewportSize> requested_viewport_size;
     bool enable_splash_screen_on_reloads;
     bool enable_on_screen_keyboard = true;
+    base::Closure web_module_loaded_callback;
   };
 
   // Type for a collection of URL handler callbacks that can potentially handle
@@ -208,6 +209,8 @@
       const base::AccessibilityCaptionSettingsChangedEvent* event);
 #endif  // SB_HAS(CAPTIONS)
 
+  bool IsWebModuleLoaded() { return web_module_loaded_.IsSignaled(); }
+
  private:
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   static void CoreDumpHandler(void* browser_module_as_void);
@@ -660,6 +663,9 @@
   // by automem.  We want this so that we can check that it never changes, since
   // we do not have the ability to modify it after startup.
   base::Optional<int64_t> javascript_gc_threshold_in_bytes_;
+
+  // Callback to run when the Web Module is loaded.
+  base::Closure web_module_loaded_callback_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/device_authentication.cc b/src/cobalt/browser/device_authentication.cc
index 7c8eeea..4a41510 100644
--- a/src/cobalt/browser/device_authentication.cc
+++ b/src/cobalt/browser/device_authentication.cc
@@ -146,6 +146,10 @@
   CHECK(!cert_scope.empty());
   CHECK(!start_time.empty());
 
+  if (base64_signature.empty()) {
+    return std::string();
+  }
+
   std::map<std::string, std::string> signed_query_components;
   signed_query_components["cert_scope"] = cert_scope;
   signed_query_components["start_time"] = start_time;
diff --git a/src/cobalt/browser/device_authentication_test.cc b/src/cobalt/browser/device_authentication_test.cc
index a820d94..5592d0c 100644
--- a/src/cobalt/browser/device_authentication_test.cc
+++ b/src/cobalt/browser/device_authentication_test.cc
@@ -140,6 +140,11 @@
           "yacs", "11111111", "11111111111111111111111111111111111111111111"));
 }
 
+TEST(DeviceAuthenticationTest, NoCertSignatureImpliesNoQueryParameters) {
+  EXPECT_EQ("", GetDeviceAuthenticationSignedURLQueryStringFromComponents(
+                    "my_cert_scope", "1234", ""));
+}
+
 #endif  // SB_API_VERSION >= 11
 
 }  // namespace browser
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index 0465c29..8161a27 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -286,6 +286,12 @@
     "limit allows. It is recommended that enough memory be reserved for two "
     "RGBA atlases about a quarter of the frame size.";
 
+const char kOmitDeviceAuthenticationQueryParameters[] =
+    "omit_device_authentication_query_parameters";
+const char kOmitDeviceAuthenticationQueryParametersHelp[] =
+    "When set, no device authentication parameters will be appended to the"
+    "initial URL.";
+
 const char kProxy[] = "proxy";
 const char kProxyHelp[] =
     "Specifies a proxy to use for network connections. "
@@ -431,6 +437,8 @@
         {kMaxCobaltGpuUsage, kMaxCobaltGpuUsageHelp},
         {kOffscreenTargetCacheSizeInBytes,
          kOffscreenTargetCacheSizeInBytesHelp},
+        {kOmitDeviceAuthenticationQueryParameters,
+         kOmitDeviceAuthenticationQueryParametersHelp},
         {kProxy, kProxyHelp}, {kQrCodeOverlay, kQrCodeOverlayHelp},
         {kReduceCpuMemoryBy, kReduceCpuMemoryByHelp},
         {kReduceGpuMemoryBy, kReduceGpuMemoryByHelp},
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h
index 6b3b163..55b5b4a 100644
--- a/src/cobalt/browser/switches.h
+++ b/src/cobalt/browser/switches.h
@@ -121,6 +121,8 @@
 extern const char kMaxCobaltGpuUsageHelp[];
 extern const char kOffscreenTargetCacheSizeInBytes[];
 extern const char kOffscreenTargetCacheSizeInBytesHelp[];
+extern const char kOmitDeviceAuthenticationQueryParameters[];
+extern const char kOmitDeviceAuthenticationQueryParametersHelp[];
 extern const char kProxy[];
 extern const char kProxyHelp[];
 extern const char kQrCodeOverlay[];
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 8907972..4bc47a5 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-234067
\ No newline at end of file
+238772
\ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.py b/src/cobalt/build/cobalt_configuration.py
index ddb656e..bd661d4 100644
--- a/src/cobalt/build/cobalt_configuration.py
+++ b/src/cobalt/build/cobalt_configuration.py
@@ -152,6 +152,7 @@
         'storage_upgrade_test',
         'web_animations_test',
         'webdriver_test',
+        'websocket_test',
         'xhr_test',
     ]
 
diff --git a/src/cobalt/dom/eme/media_key_session.cc b/src/cobalt/dom/eme/media_key_session.cc
index 8cf2253..25815f8 100644
--- a/src/cobalt/dom/eme/media_key_session.cc
+++ b/src/cobalt/dom/eme/media_key_session.cc
@@ -213,12 +213,10 @@
   // 5.2. Use CDM to close the key session associated with session.
   drm_system_session_->Close();
 
-  // Let |MediaKeys| know that the session should be removed from the list
-  // of open sessions.
-  closed_callback_.Run(this);
-
+#if !SB_HAS(DRM_SESSION_CLOSED)
   // 5.3.1. Run the Session Closed algorithm on the session.
   OnSessionClosed();
+#endif  // !SB_HAS(DRM_SESSION_CLOSED)
 
   // 5.3.2. Resolve promise.
   promise->Resolve();
@@ -414,6 +412,10 @@
   //    - TODO: Implement expiration.
   // 7. Resolve promise.
   closed_promise_reference_.value().Resolve();
+
+  // Let |MediaKeys| know that the session should be removed from the list
+  // of open sessions.
+  closed_callback_.Run(this);
 }
 
 }  // namespace eme
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index c52faae8..5426fdf 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -1153,10 +1153,11 @@
 void HTMLElement::OnUiNavFocus() {
   // Ensure the focusing steps do not trigger the UI navigation item to
   // force focus again.
-  scoped_refptr<ui_navigation::NavItem> temp_item = ui_nav_item_;
-  ui_nav_item_ = nullptr;
-  Focus();
-  ui_nav_item_ = temp_item;
+  if (!ui_nav_focusing_) {
+    ui_nav_focusing_ = true;
+    Focus();
+    ui_nav_focusing_ = false;
+  }
 }
 
 void HTMLElement::OnUiNavScroll() {
@@ -1344,7 +1345,7 @@
   ClearRuleMatchingState();
 
   // Set the focus item for the UI navigation system.
-  if (ui_nav_item_ && !ui_nav_item_->IsContainer()) {
+  if (ui_nav_item_ && !ui_nav_item_->IsContainer() && !ui_nav_focusing_) {
     // Only navigation items attached to the root container are interactable.
     // If the item is not registered with a container, then force a layout to
     // connect items to their containers and eventually to the root container.
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index 5cf5a48..cc45d59 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -483,6 +483,12 @@
   // boxes without requiring a new layout.
   scoped_refptr<ui_navigation::NavItem> ui_nav_item_;
 
+  // This temporary flag is used to avoid a cycle on focus changes. When the
+  // HTML element receives focus, it must inform the UI navigation item. When
+  // the UI navigation item receives focus (either by calling SetFocus or by an
+  // update from the UI engine), it will tell the HTML element it was focused.
+  bool ui_nav_focusing_ = false;
+
   // HTMLElement is a friend of Animatable so that animatable can insert and
   // remove animations into HTMLElement's set of animations.
   friend class DOMAnimatable;
diff --git a/src/cobalt/media/base/drm_system.cc b/src/cobalt/media/base/drm_system.cc
index ab0cedd..2980a58 100644
--- a/src/cobalt/media/base/drm_system.cc
+++ b/src/cobalt/media/base/drm_system.cc
@@ -25,8 +25,7 @@
 namespace media {
 
 DrmSystem::Session::Session(
-    DrmSystem* drm_system
-    ,
+    DrmSystem* drm_system,
     SessionUpdateKeyStatusesCallback update_key_statuses_callback
 #if SB_HAS(DRM_SESSION_CLOSED)
     ,
@@ -38,7 +37,8 @@
 #if SB_HAS(DRM_SESSION_CLOSED)
       session_closed_callback_(session_closed_callback),
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
-      closed_(false) {
+      closed_(false),
+      weak_factory_(this) {
   DCHECK(!update_key_statuses_callback_.is_null());
 #if SB_HAS(DRM_SESSION_CLOSED)
   DCHECK(!session_closed_callback_.is_null());
@@ -66,7 +66,7 @@
   update_request_generated_callback_ =
       session_update_request_generated_callback;
   drm_system_->GenerateSessionUpdateRequest(
-      this, type, init_data, init_data_length,
+      weak_factory_.GetWeakPtr(), type, init_data, init_data_length,
       session_update_request_generated_callback,
       session_update_request_did_not_generate_callback);
 }
@@ -160,8 +160,8 @@
 #endif  // SB_API_VERSION >= 10
 
 void DrmSystem::GenerateSessionUpdateRequest(
-    Session* session, const std::string& type, const uint8_t* init_data,
-    int init_data_length,
+    const base::WeakPtr<Session>& session, const std::string& type,
+    const uint8_t* init_data, int init_data_length,
     const SessionUpdateRequestGeneratedCallback&
         session_update_request_generated_callback,
     const SessionUpdateRequestDidNotGenerateCallback&
@@ -212,6 +212,8 @@
     SessionTicketAndOptionalId ticket_and_optional_id, SbDrmStatus status,
     SbDrmSessionRequestType type, const std::string& error_message,
     std::unique_ptr<uint8[]> message, int message_size) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   int ticket = ticket_and_optional_id.ticket;
   const base::Optional<std::string>& session_id = ticket_and_optional_id.id;
   if (SbDrmTicketIsValid(ticket)) {
@@ -228,21 +230,25 @@
     const SessionUpdateRequest& session_update_request =
         session_update_request_iterator->second;
 
-    // Interpret the result.
-    if (session_id) {
-      // Successful request generation.
+    // As DrmSystem::Session may be released, need to check it before using it.
+    if (session_update_request.session &&
+        !session_update_request.session->is_closed()) {
+      // Interpret the result.
+      if (session_id) {
+        // Successful request generation.
 
-      // Enable session lookup by id which is used by spontaneous callbacks.
-      session_update_request.session->set_id(*session_id);
-      id_to_session_map_.insert(
-          std::make_pair(*session_id, session_update_request.session));
+        // Enable session lookup by id which is used by spontaneous callbacks.
+        session_update_request.session->set_id(*session_id);
+        id_to_session_map_.insert(
+            std::make_pair(*session_id, session_update_request.session));
 
-      session_update_request.generated_callback.Run(type, std::move(message),
-                                                    message_size);
-    } else {
-      // Failure during request generation.
-      session_update_request.did_not_generate_callback.Run(status,
-                                                           error_message);
+        session_update_request.generated_callback.Run(type, std::move(message),
+                                                      message_size);
+      } else {
+        // Failure during request generation.
+        session_update_request.did_not_generate_callback.Run(status,
+                                                             error_message);
+      }
     }
 
     // Sweep the context of |GenerateSessionUpdateRequest| once license updated.
@@ -265,15 +271,19 @@
       LOG(ERROR) << "Unknown session id: " << *session_id << ".";
       return;
     }
-    Session* session = session_iterator->second;
 
-    session->update_request_generated_callback().Run(type, std::move(message),
-                                                     message_size);
+    // As DrmSystem::Session may be released, need to check it before using it.
+    if (session_iterator->second) {
+      session_iterator->second->update_request_generated_callback().Run(
+          type, std::move(message), message_size);
+    }
   }
 }
 
 void DrmSystem::OnSessionUpdated(int ticket, SbDrmStatus status,
                                  const std::string& error_message) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Restore the context of |UpdateSession|.
   TicketToSessionUpdateMap::iterator session_update_iterator =
       ticket_to_session_update_map_.find(ticket);
@@ -297,6 +307,8 @@
 void DrmSystem::OnSessionKeyStatusChanged(
     const std::string& session_id, const std::vector<std::string>& key_ids,
     const std::vector<SbDrmKeyStatus>& key_statuses) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Find the session by ID.
   IdToSessionMap::iterator session_iterator =
       id_to_session_map_.find(session_id);
@@ -304,13 +316,18 @@
     LOG(ERROR) << "Unknown session id: " << session_id << ".";
     return;
   }
-  Session* session = session_iterator->second;
 
-  session->update_key_statuses_callback().Run(key_ids, key_statuses);
+  // As DrmSystem::Session may be released, need to check it before using it.
+  if (session_iterator->second) {
+    session_iterator->second->update_key_statuses_callback().Run(key_ids,
+                                                                 key_statuses);
+  }
 }
 
 #if SB_HAS(DRM_SESSION_CLOSED)
 void DrmSystem::OnSessionClosed(const std::string& session_id) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   // Find the session by ID.
   IdToSessionMap::iterator session_iterator =
       id_to_session_map_.find(session_id);
@@ -318,9 +335,11 @@
     LOG(ERROR) << "Unknown session id: " << session_id << ".";
     return;
   }
-  Session* session = session_iterator->second;
 
-  session->session_closed_callback().Run();
+  // As DrmSystem::Session may be released, need to check it before using it.
+  if (session_iterator->second) {
+    session_iterator->second->session_closed_callback().Run();
+  }
   id_to_session_map_.erase(session_iterator);
 }
 #endif  // SB_HAS(DRM_SESSION_CLOSED)
@@ -328,6 +347,8 @@
 #if SB_API_VERSION >= 10
 void DrmSystem::OnServerCertificateUpdated(int ticket, SbDrmStatus status,
                                            const std::string& error_message) {
+  DCHECK(message_loop_->BelongsToCurrentThread());
+
   auto iter = ticket_to_server_certificate_updated_map_.find(ticket);
   if (iter == ticket_to_server_certificate_updated_map_.end()) {
     LOG(ERROR) << "Unknown ticket: " << ticket << ".";
diff --git a/src/cobalt/media/base/drm_system.h b/src/cobalt/media/base/drm_system.h
index d4290f8..a80bf34 100644
--- a/src/cobalt/media/base/drm_system.h
+++ b/src/cobalt/media/base/drm_system.h
@@ -132,6 +132,8 @@
     // Supports spontaneous invocations of |SbDrmSessionUpdateRequestFunc|.
     SessionUpdateRequestGeneratedCallback update_request_generated_callback_;
 
+    base::WeakPtrFactory<Session> weak_factory_;
+
     friend class DrmSystem;
 
     DISALLOW_COPY_AND_ASSIGN(Session);
@@ -160,14 +162,14 @@
  private:
   // Stores context of |GenerateSessionUpdateRequest|.
   struct SessionUpdateRequest {
-    Session* session;
+    base::WeakPtr<Session> session;
     SessionUpdateRequestGeneratedCallback generated_callback;
     SessionUpdateRequestDidNotGenerateCallback did_not_generate_callback;
   };
   typedef base::hash_map<int, SessionUpdateRequest>
       TicketToSessionUpdateRequestMap;
 
-  typedef base::hash_map<std::string, Session*> IdToSessionMap;
+  typedef base::hash_map<std::string, base::WeakPtr<Session>> IdToSessionMap;
 
   typedef base::hash_map<int, ServerCertificateUpdatedCallback>
       TicketToServerCertificateUpdatedMap;
@@ -188,8 +190,8 @@
 
   // Private API for |Session|.
   void GenerateSessionUpdateRequest(
-      Session* session, const std::string& type, const uint8_t* init_data,
-      int init_data_length,
+      const base::WeakPtr<Session>& session, const std::string& type,
+      const uint8_t* init_data, int init_data_length,
       const SessionUpdateRequestGeneratedCallback&
           session_update_request_generated_callback,
       const SessionUpdateRequestDidNotGenerateCallback&
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index c41e9ac..d0c7e92 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -268,6 +268,7 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   decoder_buffer_cache_.ClearAll();
+  seek_pending_ = false;
 
   if (state_ == kSuspended) {
     preroll_timestamp_ = time;
@@ -290,7 +291,6 @@
   SbPlayerSeek2(player_, time.InMicroseconds(), ticket_);
 #endif  // SB_API_VERSION < 10
 
-  seek_pending_ = false;
   SbPlayerSetPlaybackRate(player_, playback_rate_);
 }
 
@@ -434,13 +434,14 @@
 
   SbPlayerSetPlaybackRate(player_, 0.0);
 
+  set_bounds_helper_->SetPlayer(NULL);
+
   base::AutoLock auto_lock(lock_);
   GetInfo_Locked(&cached_video_frames_decoded_, &cached_video_frames_dropped_,
                  &preroll_timestamp_);
 
   state_ = kSuspended;
 
-  set_bounds_helper_->SetPlayer(NULL);
   video_frame_provider_->SetOutputMode(VideoFrameProvider::kOutputModeInvalid);
   video_frame_provider_->ResetGetCurrentSbDecodeTargetFunction();
 
diff --git a/src/cobalt/media/sandbox/format_guesstimator.cc b/src/cobalt/media/sandbox/format_guesstimator.cc
index 0194787..ad5c97f 100644
--- a/src/cobalt/media/sandbox/format_guesstimator.cc
+++ b/src/cobalt/media/sandbox/format_guesstimator.cc
@@ -17,7 +17,18 @@
 #include <algorithm>
 #include <vector>
 
+#include "base/bind.h"
 #include "base/path_service.h"
+#include "base/time/time.h"
+#include "cobalt/media/base/audio_codecs.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/demuxer_stream.h"
+#include "cobalt/media/base/media_tracks.h"
+#include "cobalt/media/base/video_codecs.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/filters/chunk_demuxer.h"
+#include "cobalt/media/sandbox/web_media_player_helper.h"
+#include "cobalt/render_tree/image.h"
 #include "net/base/filename_util.h"
 #include "net/base/url_util.h"
 #include "starboard/common/string.h"
@@ -31,6 +42,22 @@
 
 namespace {
 
+// Container to organize the pairs of supported mime types and their codecs.
+struct SupportedTypeCodecInfo {
+  std::string mime;
+  std::string codecs;
+};
+
+// The possible mime and codec configurations that are supported by cobalt.
+const std::vector<SupportedTypeCodecInfo> kSupportedTypesAndCodecs = {
+    {"audio/mp4", "ac-3"},          {"audio/mp4", "ec-3"},
+    {"audio/mp4", "mp4a.40.2"},     {"audio/webm", "opus"},
+
+    {"video/mp4", "av01.0.05M.08"}, {"video/mp4", "avc1.640028, mp4a.40.2"},
+    {"video/mp4", "avc1.640028"},   {"video/mp4", "hvc1.1.6.H150.90"},
+    {"video/webm", "vp9"},
+};
+
 // Can be called as:
 //   IsFormat("https://example.com/audio.mp4", ".mp4")
 //   IsFormat("cobalt/demos/video.webm", ".webm")
@@ -54,11 +81,12 @@
   return base::FilePath();
 }
 
-// Read the first 4096 bytes of the local file specified by |path|.  If the size
-// of the size is less than 4096 bytes, the function reads all of its content.
+// Read the first 256kb of the local file specified by |path|.  If the size
+// of the file is less than 256kb, the function reads all of its content.
 std::vector<uint8_t> ReadHeader(const base::FilePath& path) {
-  const int64_t kHeaderSize = 4096;
-
+  // Size of the input file to be read into memory for checking the validity
+  // of ChunkDemuxer::AppendData() calls.
+  const int64_t kHeaderSize = 256 * 1024;  // 256kb
   starboard::ScopedFile file(path.value().c_str(),
                              kSbFileOpenOnly | kSbFileRead);
   int64_t bytes_to_read = std::min(kHeaderSize, file.GetSize());
@@ -71,21 +99,14 @@
   return buffer;
 }
 
-// Find the first occurrence of |str| in |data|.  If there is no |str| contained
-// inside |data|, it returns -1.
-off_t FindString(const std::vector<uint8_t>& data, const char* str) {
-  size_t size_of_str = SbStringGetLength(str);
-  for (off_t offset = 0; offset + size_of_str < data.size(); ++offset) {
-    if (SbMemoryCompare(data.data() + offset, str, size_of_str) == 0) {
-      return offset;
-    }
-  }
-  return -1;
+void OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
+  SB_UNREFERENCED_PARAMETER(tracks);
 }
 
 }  // namespace
 
-FormatGuesstimator::FormatGuesstimator(const std::string& path_or_url) {
+FormatGuesstimator::FormatGuesstimator(const std::string& path_or_url,
+                                       MediaModule* media_module) {
   GURL url(path_or_url);
   if (url.is_valid()) {
     // If it is a url, assume that it is a progressive video.
@@ -96,11 +117,7 @@
   if (path.empty() || !SbFileCanOpen(path.value().c_str(), kSbFileRead)) {
     return;
   }
-  if (IsFormat(path_or_url, ".mp4")) {
-    InitializeAsMp4(path);
-  } else if (IsFormat(path_or_url, ".webm")) {
-    InitializeAsWebM(path);
-  }
+  InitializeAsAdaptive(path, media_module);
 }
 
 void FormatGuesstimator::InitializeAsProgressive(const GURL& url) {
@@ -113,49 +130,78 @@
   codecs_ = "avc1.640028, mp4a.40.2";
 }
 
-void FormatGuesstimator::InitializeAsMp4(const base::FilePath& path) {
+void FormatGuesstimator::InitializeAsAdaptive(const base::FilePath& path,
+                                              MediaModule* media_module) {
   std::vector<uint8_t> header = ReadHeader(path);
-  if (FindString(header, "ftyp") == -1) {
-    return;
-  }
-  if (FindString(header, "dash") == -1) {
-    progressive_url_ = net::FilePathToFileURL(path);
-    mime_ = "video/mp4";
-    codecs_ = "avc1.640028, mp4a.40.2";
-    return;
-  }
-  if (FindString(header, "vide") != -1) {
-    if (FindString(header, "avcC") != -1) {
-      adaptive_path_ = path;
-      mime_ = "video/mp4";
-      codecs_ = "avc1.640028";
-      return;
-    }
-    if (FindString(header, "hvcC") != -1) {
-      adaptive_path_ = path;
-      mime_ = "video/mp4";
-      codecs_ = "hvc1.1.6.H150.90";
-      return;
-    }
-    return;
-  }
-  if (FindString(header, "soun") != -1) {
-    adaptive_path_ = path;
-    mime_ = "audio/mp4";
-    codecs_ = "mp4a.40.2";
-    return;
-  }
-}
 
-void FormatGuesstimator::InitializeAsWebM(const base::FilePath& path) {
-  std::vector<uint8_t> header = ReadHeader(path);
-  adaptive_path_ = path;
-  if (FindString(header, "OpusHead") == -1) {
-    mime_ = "video/webm";
-    codecs_ = "vp9";
-  } else {
-    mime_ = "audio/webm";
-    codecs_ = "opus";
+  for (const auto& expected_supported_info : kSupportedTypesAndCodecs) {
+    DCHECK(mime_.empty());
+    DCHECK(codecs_.empty());
+
+    ChunkDemuxer* chunk_demuxer = NULL;
+    WebMediaPlayerHelper::ChunkDemuxerOpenCB open_cb = base::Bind(
+        [](ChunkDemuxer** handle, ChunkDemuxer* chunk_demuxer) -> void {
+          *handle = chunk_demuxer;
+        },
+        &chunk_demuxer);
+    // We create a new |web_media_player_helper| every iteration in order to
+    // obtain a handle to a new |ChunkDemuxer| without any accumulated state as
+    // a result of previous calls to |AddId| and |AppendData| methods.
+    WebMediaPlayerHelper web_media_player_helper(media_module, open_cb);
+
+    // |chunk_demuxer| will be set when |open_cb| is called asynchronously
+    // during initialization of |web_media_player_helper|. Wait until it is set
+    // before proceeding.
+    while (!chunk_demuxer) {
+      base::RunLoop().RunUntilIdle();
+    }
+
+    const std::string id = "stream";
+    if (chunk_demuxer->AddId(id, expected_supported_info.mime,
+                             expected_supported_info.codecs) !=
+        ChunkDemuxer::kOk) {
+      continue;
+    }
+
+    chunk_demuxer->SetTracksWatcher(id, base::Bind(OnInitSegmentReceived));
+
+    base::TimeDelta unused_timestamp;
+    if (!chunk_demuxer->AppendData(id, header.data(), header.size(),
+                                   base::TimeDelta(), media::kInfiniteDuration,
+                                   &unused_timestamp)) {
+      // Failing to |AppendData()| means the chosen format is not the file's
+      // true format.
+      continue;
+    }
+
+    // Succeeding |AppendData()| may be a false positive (i.e. the expected
+    // configuration does not match with the configuration determined by the
+    // ChunkDemuxer). To confirm, we check the decoder configuration determined
+    // by the ChunkDemuxer against the chosen format.
+    if (auto demuxer_stream =
+            chunk_demuxer->GetStream(DemuxerStream::Type::AUDIO)) {
+      const AudioDecoderConfig& decoder_config =
+          demuxer_stream->audio_decoder_config();
+      if (StringToAudioCodec(expected_supported_info.codecs) ==
+          decoder_config.codec()) {
+        adaptive_path_ = path;
+        mime_ = expected_supported_info.mime;
+        codecs_ = expected_supported_info.codecs;
+        break;
+      }
+      continue;
+    }
+    auto demuxer_stream = chunk_demuxer->GetStream(DemuxerStream::Type::VIDEO);
+    DCHECK(demuxer_stream);
+    const VideoDecoderConfig& decoder_config =
+        demuxer_stream->video_decoder_config();
+    if (StringToVideoCodec(expected_supported_info.codecs) ==
+        decoder_config.codec()) {
+      adaptive_path_ = path;
+      mime_ = expected_supported_info.mime;
+      codecs_ = expected_supported_info.codecs;
+      break;
+    }
   }
 }
 
diff --git a/src/cobalt/media/sandbox/format_guesstimator.h b/src/cobalt/media/sandbox/format_guesstimator.h
index 4fe4568..dd95e9c 100644
--- a/src/cobalt/media/sandbox/format_guesstimator.h
+++ b/src/cobalt/media/sandbox/format_guesstimator.h
@@ -19,6 +19,7 @@
 
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "cobalt/media/media_module.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -30,7 +31,7 @@
 // will be identified as progressive mp4.
 class FormatGuesstimator {
  public:
-  explicit FormatGuesstimator(const std::string& path_or_url);
+  FormatGuesstimator(const std::string& path_or_url, MediaModule* media_module);
 
   bool is_valid() const { return is_progressive() || is_adaptive(); }
   bool is_progressive() const { return progressive_url_.is_valid(); }
@@ -63,8 +64,8 @@
 
  private:
   void InitializeAsProgressive(const GURL& url);
-  void InitializeAsMp4(const base::FilePath& path);
-  void InitializeAsWebM(const base::FilePath& path);
+  void InitializeAsAdaptive(const base::FilePath& path,
+                            MediaModule* media_module);
 
   GURL progressive_url_;
   base::FilePath adaptive_path_;
diff --git a/src/cobalt/media/sandbox/web_media_player_sandbox.cc b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
index ed0ca18..7d28698 100644
--- a/src/cobalt/media/sandbox/web_media_player_sandbox.cc
+++ b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
@@ -114,8 +114,10 @@
                        base::FilePath(FILE_PATH_LITERAL(
                            "media_source_sandbox_trace.json"))) {
     if (argc > 1) {
-      FormatGuesstimator guesstimator1(argv[argc - 1]);
-      FormatGuesstimator guesstimator2(argv[argc - 2]);
+      FormatGuesstimator guesstimator1(argv[argc - 1],
+                                       media_sandbox_.GetMediaModule());
+      FormatGuesstimator guesstimator2(argv[argc - 2],
+                                       media_sandbox_.GetMediaModule());
 
       if (!guesstimator1.is_valid()) {
         SB_LOG(ERROR) << "Invalid path or url: " << argv[argc - 1];
diff --git a/src/cobalt/media_capture/get_user_media_test.cc b/src/cobalt/media_capture/get_user_media_test.cc
index 277379e..2d2824e 100644
--- a/src/cobalt/media_capture/get_user_media_test.cc
+++ b/src/cobalt/media_capture/get_user_media_test.cc
@@ -131,5 +131,27 @@
             media_stream_promise->State());
 }
 
+TEST_F(GetUserMediaTest, MultipleMicrophoneSuccessFulfilledPromise) {
+  media_stream::MediaStreamConstraints constraints;
+  constraints.set_audio(true);
+  std::vector<script::Handle<MediaDevices::MediaStreamPromise>>
+      media_stream_promises;
+
+  for (size_t i = 0; i < 2; ++i) {
+    media_stream_promises.push_back(media_devices_->GetUserMedia(constraints));
+    ASSERT_FALSE(media_stream_promises.back().IsEmpty());
+    EXPECT_EQ(cobalt::script::PromiseState::kPending,
+              media_stream_promises.back()->State());
+    media_devices_->OnMicrophoneSuccess();
+  }
+
+  media_devices_->audio_source_->StopSource();
+
+  for (size_t i = 0; i < media_stream_promises.size(); ++i) {
+    EXPECT_EQ(cobalt::script::PromiseState::kFulfilled,
+              media_stream_promises[i]->State());
+  }
+}
+
 }  // namespace media_capture
 }  // namespace cobalt
diff --git a/src/cobalt/media_capture/media_devices.cc b/src/cobalt/media_capture/media_devices.cc
index 8247a43..5b4f8dd 100644
--- a/src/cobalt/media_capture/media_devices.cc
+++ b/src/cobalt/media_capture/media_devices.cc
@@ -73,6 +73,7 @@
 
 script::Handle<MediaDevices::MediaInfoSequencePromise>
 MediaDevices::EnumerateDevices() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(settings_);
   DCHECK(script_value_factory_);
   script::Handle<MediaInfoSequencePromise> promise =
@@ -91,6 +92,7 @@
 }
 
 script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(script_value_factory_);
   script::Handle<MediaDevices::MediaStreamPromise> promise =
       script_value_factory_->CreateInterfacePromise<
@@ -108,6 +110,7 @@
 
 script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia(
     const media_stream::MediaStreamConstraints& constraints) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   script::Handle<MediaDevices::MediaStreamPromise> promise =
       script_value_factory_->CreateInterfacePromise<
           script::ScriptValueFactory::WrappablePromise>();
@@ -134,6 +137,8 @@
         base::Bind(&MediaDevices::OnMicrophoneSuccess, weak_this_),
         base::Closure(),  // TODO: remove this redundant callback.
         base::Bind(&MediaDevices::OnMicrophoneError, weak_this_));
+    audio_source_->SetStopCallback(
+        base::Bind(&MediaDevices::OnMicrophoneStopped, weak_this_));
   }
 
   std::unique_ptr<MediaStreamPromiseValue::Reference> promise_reference(
@@ -146,8 +151,6 @@
     audio_source_->ConnectToTrack(
         base::polymorphic_downcast<media_stream::MediaStreamAudioTrack*>(
             pending_microphone_track_.get()));
-    audio_source_->SetStopCallback(
-        base::Bind(&MediaDevices::OnMicrophoneStopped, weak_this_));
   }
 
   // Step 10, return promise.
@@ -156,24 +159,11 @@
 
 void MediaDevices::OnMicrophoneError(
     speech::MicrophoneManager::MicrophoneError error, std::string message) {
-  if (javascript_message_loop_->task_runner() !=
-      base::MessageLoop::current()->task_runner()) {
-    javascript_message_loop_->task_runner()->PostTask(
-        FROM_HERE, base::Bind(&MediaDevices::OnMicrophoneError, weak_this_,
-                              error, message));
-    return;
-  }
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
   DLOG(INFO) << "MediaDevices::OnMicrophoneError " << message;
-  pending_microphone_track_ = nullptr;
-  audio_source_ = nullptr;
 
-  for (auto& promise : pending_microphone_promises_) {
-    promise->value().Reject(
-        new dom::DOMException(dom::DOMException::kNotAllowedErr));
-  }
-  pending_microphone_promises_.clear();
+  // No special error handling logic besides logging the message above, so just
+  // delegate to the OnMicrophoneStopped() functionality.
+  OnMicrophoneStopped();
 }
 
 void MediaDevices::OnMicrophoneStopped() {
@@ -185,8 +175,9 @@
   }
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  audio_source_ = nullptr;
+  DLOG(INFO) << "MediaDevices::OnMicrophoneStopped()";
   pending_microphone_track_ = nullptr;
+  audio_source_ = nullptr;
 
   for (auto& promise : pending_microphone_promises_) {
     promise->value().Reject(
diff --git a/src/cobalt/media_capture/media_devices.h b/src/cobalt/media_capture/media_devices.h
index 8a0c84c..e042e04 100644
--- a/src/cobalt/media_capture/media_devices.h
+++ b/src/cobalt/media_capture/media_devices.h
@@ -71,10 +71,13 @@
   FRIEND_TEST_ALL_PREFIXES(GetUserMediaTest, MicrophoneStoppedRejectedPromise);
   FRIEND_TEST_ALL_PREFIXES(GetUserMediaTest, MicrophoneErrorRejectedPromise);
   FRIEND_TEST_ALL_PREFIXES(GetUserMediaTest, MicrophoneSuccessFulfilledPromise);
+  FRIEND_TEST_ALL_PREFIXES(GetUserMediaTest,
+                           MultipleMicrophoneSuccessFulfilledPromise);
 
   ~MediaDevices() override = default;
 
-  // Stop callback used with MediaStreamAudioSource.
+  // Stop callback used with MediaStreamAudioSource, and the OnMicrophoneError
+  // logic will also call in to this.
   void OnMicrophoneStopped();
 
   // Callbacks used with MicrophoneManager.
diff --git a/src/cobalt/media_capture/media_recorder.cc b/src/cobalt/media_capture/media_recorder.cc
index 77455bf..1468b3b 100644
--- a/src/cobalt/media_capture/media_recorder.cc
+++ b/src/cobalt/media_capture/media_recorder.cc
@@ -213,8 +213,10 @@
   // Step 5.5 from start(), defined at:
   // https://www.w3.org/TR/mediastream-recording/#mediarecorder-methods
   if (new_state == media_stream::MediaStreamTrack::kReadyStateEnded) {
-    audio_encoder_->Finish(base::TimeTicks::Now());
-    audio_encoder_.reset();
+    if (audio_encoder_) {
+      audio_encoder_->Finish(base::TimeTicks::Now());
+      audio_encoder_.reset();
+    }
     StopRecording();
     stream_ = nullptr;
   }
diff --git a/src/cobalt/media_capture/media_recorder_test.cc b/src/cobalt/media_capture/media_recorder_test.cc
index 6a62f2c..931e631 100644
--- a/src/cobalt/media_capture/media_recorder_test.cc
+++ b/src/cobalt/media_capture/media_recorder_test.cc
@@ -41,7 +41,7 @@
 
 namespace {
 void PushData(
-    const scoped_refptr<cobalt::media_capture::MediaRecorder>& media_recorder) {
+    cobalt::media_capture::MediaRecorder* media_recorder) {
   const int kSampleRate = 16000;
   cobalt::media_stream::AudioParameters params(1, kSampleRate, 16);
   media_recorder->OnSetFormat(params);
@@ -228,9 +228,8 @@
   // member functions with base::Unretained() or weak pointer.
   // Creates media_recorder_ref just to make it clear that no copy happened
   // during base::Bind().
-  const scoped_refptr<MediaRecorder>& media_recorder_ref = media_recorder_;
   t.message_loop()->task_runner()->PostBlockingTask(
-      FROM_HERE, base::Bind(&PushData, media_recorder_ref));
+      FROM_HERE, base::Bind(&PushData, base::Unretained(media_recorder_.get())));
   t.Stop();
 
   base::RunLoop().RunUntilIdle();
diff --git a/src/cobalt/media_stream/media_stream_audio_source.cc b/src/cobalt/media_stream/media_stream_audio_source.cc
index b4d359d..6c20d49 100644
--- a/src/cobalt/media_stream/media_stream_audio_source.cc
+++ b/src/cobalt/media_stream/media_stream_audio_source.cc
@@ -80,8 +80,11 @@
 
 void MediaStreamAudioSource::DoStopSource() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  EnsureSourceIsStopped();
   is_stopped_ = true;
+
+  // This function might result in the destruction of this object, so be
+  // careful not to reference members after this call.
+  EnsureSourceIsStopped();
 }
 
 void MediaStreamAudioSource::StopAudioDeliveryTo(MediaStreamAudioTrack* track) {
diff --git a/src/cobalt/media_stream/microphone_audio_source.cc b/src/cobalt/media_stream/microphone_audio_source.cc
index 614e894..291acc5 100644
--- a/src/cobalt/media_stream/microphone_audio_source.cc
+++ b/src/cobalt/media_stream/microphone_audio_source.cc
@@ -42,31 +42,35 @@
 std::unique_ptr<cobalt::speech::Microphone>
 MicrophoneAudioSource::CreateMicrophone(
     const cobalt::speech::Microphone::Options& options, int buffer_size_bytes) {
-#if defined(ENABLE_FAKE_MICROPHONE)
+#if !defined(ENABLE_MICROPHONE_IDL)
+  SB_UNREFERENCED_PARAMETER(options);
   SB_UNREFERENCED_PARAMETER(buffer_size_bytes);
+  return std::unique_ptr<speech::Microphone>();
+#else
+  std::unique_ptr<speech::Microphone> mic;
+
+#if defined(ENABLE_FAKE_MICROPHONE)
   if (options.enable_fake_microphone) {
-    return std::unique_ptr<speech::Microphone>(
-        new speech::MicrophoneFake(options));
+    mic.reset(new speech::MicrophoneFake(options));
   }
 #else
   SB_UNREFERENCED_PARAMETER(options);
 #endif  // defined(ENABLE_FAKE_MICROPHONE)
 
-  std::unique_ptr<speech::Microphone> mic;
+  if (!mic) {
+    mic.reset(new speech::MicrophoneStarboard(
+        speech::MicrophoneStarboard::kDefaultSampleRate, buffer_size_bytes));
+  }
 
-#if defined(ENABLE_MICROPHONE_IDL)
-  mic.reset(new speech::MicrophoneStarboard(
-      speech::MicrophoneStarboard::kDefaultSampleRate, buffer_size_bytes));
-
-  AudioParameters params(
-      1, speech::MicrophoneStarboard::kDefaultSampleRate,
-      speech::MicrophoneStarboard::kSbMicrophoneSampleSizeInBytes * 8);
-  SetFormat(params);
-#else
-  SB_UNREFERENCED_PARAMETER(buffer_size_bytes);
-#endif  // defined(ENABLE_MICROPHONE_IDL)
+  if (mic) {
+    AudioParameters params(
+        1, speech::MicrophoneStarboard::kDefaultSampleRate,
+        speech::MicrophoneStarboard::kSbMicrophoneSampleSizeInBytes * 8);
+    SetFormat(params);
+  }
 
   return mic;
+#endif  // defined(ENABLE_MICROPHONE_IDL)
 }
 
 MicrophoneAudioSource::MicrophoneAudioSource(
diff --git a/src/cobalt/network/local_network.cc b/src/cobalt/network/local_network.cc
index 279aa3b..a51964d 100644
--- a/src/cobalt/network/local_network.cc
+++ b/src/cobalt/network/local_network.cc
@@ -80,7 +80,7 @@
       return true;
     }
     if ((ip.address[0] == 172) &&
-        ((ip.address[1] >= 16) || (ip.address[1] <= 31))) {
+        ((ip.address[1] >= 16) && (ip.address[1] <= 31))) {
       // IP is in range 172.16.0.0 - 172.31.255.255 (172.16/12 prefix).
       return true;
     }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
index 4400f4b..04eaa9c 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
@@ -1,9 +1,8 @@
 precision mediump float;

 varying vec2 v_tex_coord_rgba;

 uniform sampler2D texture_rgba;

-uniform mat4 to_rgb_color_matrix;

 

 void main() {

   vec4 untransformed_color = vec4(texture2D(texture_rgba, v_tex_coord_rgba).rgba);

-  gl_FragColor = untransformed_color * to_rgb_color_matrix;

+  gl_FragColor = untransformed_color;

 }
\ No newline at end of file
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 8d6e121..20c5e49 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -109,6 +109,12 @@
 }
 
 bool ImageNodeSupportedNatively(render_tree::ImageNode* image_node) {
+  // The image node may contain nothing. For example, when it represents a video
+  // element before any frame is decoded.
+  if (!image_node->data().source) {
+    return true;
+  }
+
   // 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.
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 696a733..66a2fc5 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -71,11 +71,6 @@
   out_vec4[3] = translate_y;
 }
 
-// Used for RGB images.
-const float kIdentityColorMatrix[16] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-                                        0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-                                        0.0f, 0.0f, 0.0f, 1.0f};
-
 // Used for YUV images.
 const float kBT601FullRangeColorMatrix[16] = {
     1.0f, 0.0f,   1.402f, -0.701,  1.0f, -0.34414f, -0.71414f, 0.529f,
@@ -108,7 +103,8 @@
     TexturedMeshRenderer::Image::Type type) {
   switch (type) {
     case TexturedMeshRenderer::Image::RGBA: {
-      return kIdentityColorMatrix;
+      // No color matrix needed for RGBA to RGBA.
+      return nullptr;
     } break;
     case TexturedMeshRenderer::Image::YUV_3PLANE_BT601_FULL_RANGE: {
       return kBT601FullRangeColorMatrix;
@@ -312,7 +308,8 @@
 
 // static
 uint32 TexturedMeshRenderer::CreateFragmentShader(
-    uint32 texture_target, const std::vector<TextureInfo>& textures) {
+    uint32 texture_target, const std::vector<TextureInfo>& textures,
+    const float* color_matrix) {
   SamplerInfo sampler_info = GetSamplerInfo(texture_target);
 
   std::string blit_fragment_shader_source = sampler_info.preamble;
@@ -327,8 +324,10 @@
         base::StringPrintf("uniform %s texture_%s;", sampler_info.type.c_str(),
                            textures[i].name.c_str());
   }
+  if (color_matrix) {
+    blit_fragment_shader_source += "uniform mat4 to_rgb_color_matrix;";
+  }
   blit_fragment_shader_source +=
-      "uniform mat4 to_rgb_color_matrix;"
       "void main() {"
       "  vec4 untransformed_color = vec4(";
   int components_used = 0;
@@ -345,10 +344,17 @@
     // Add an alpha component of 1.
     blit_fragment_shader_source += ", 1.0";
   }
-  blit_fragment_shader_source +=
-      ");"
-      "  gl_FragColor = untransformed_color * to_rgb_color_matrix;"
-      "}";
+  if (color_matrix) {
+    blit_fragment_shader_source +=
+        ");"
+        "  gl_FragColor = untransformed_color * to_rgb_color_matrix;"
+        "}";
+  } else {
+    blit_fragment_shader_source +=
+        ");"
+        "  gl_FragColor = untransformed_color;"
+        "}";
+  }
 
   return CompileShader(blit_fragment_shader_source);
 }
@@ -475,11 +481,13 @@
   }
 
   // Upload the color matrix right away since it won't change from draw to draw.
-  GL_CALL(glUseProgram(result.gl_program_id));
-  uint32 to_rgb_color_matrix_uniform = GL_CALL_SIMPLE(
-      glGetUniformLocation(result.gl_program_id, "to_rgb_color_matrix"));
-  GL_CALL(glUniformMatrix4fv(to_rgb_color_matrix_uniform, 1, GL_FALSE,
-                             color_matrix));
+  if (color_matrix) {
+    GL_CALL(glUseProgram(result.gl_program_id));
+    uint32 to_rgb_color_matrix_uniform = GL_CALL_SIMPLE(
+        glGetUniformLocation(result.gl_program_id, "to_rgb_color_matrix"));
+    GL_CALL(glUniformMatrix4fv(to_rgb_color_matrix_uniform, 1, GL_FALSE,
+                               color_matrix));
+  }
   GL_CALL(glUseProgram(0));
 
   GL_CALL(glDeleteShader(blit_fragment_shader));
@@ -514,7 +522,7 @@
         texture_infos.push_back(TextureInfo("rgba", "rgba"));
         result = MakeBlitProgram(
             color_matrix, texture_infos,
-            CreateFragmentShader(texture_target, texture_infos));
+            CreateFragmentShader(texture_target, texture_infos, color_matrix));
       } break;
       case Image::YUV_2PLANE_BT709: {
         std::vector<TextureInfo> texture_infos;
@@ -549,7 +557,7 @@
 #endif  // SB_API_VERSION >= 7
         result = MakeBlitProgram(
             color_matrix, texture_infos,
-            CreateFragmentShader(texture_target, texture_infos));
+            CreateFragmentShader(texture_target, texture_infos, color_matrix));
       } break;
       case Image::YUV_3PLANE_BT601_FULL_RANGE:
       case Image::YUV_3PLANE_BT709:
@@ -578,7 +586,7 @@
 #endif  // SB_API_VERSION >= 7 && defined(GL_RED_EXT)
         result = MakeBlitProgram(
             color_matrix, texture_infos,
-            CreateFragmentShader(texture_target, texture_infos));
+            CreateFragmentShader(texture_target, texture_infos, color_matrix));
       } break;
       case Image::YUV_UYVY_422_BT709: {
         std::vector<TextureInfo> texture_infos;
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index 99abdd9..e607bd7 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -134,7 +134,8 @@
                                      uint32 blit_fragment_shader);
 
   static uint32 CreateFragmentShader(uint32 texture_target,
-                                     const std::vector<TextureInfo>& textures);
+                                     const std::vector<TextureInfo>& textures,
+                                     const float* color_matrix);
   static uint32 CreateVertexShader(const std::vector<TextureInfo>& textures);
 
   // UYVY textures need a special fragment shader to handle the unique aspect
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index d6bf989..a8b327a 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -4003,6 +4003,12 @@
   TestTree(new ImageNode(nullptr, math::RectF(output_surface_size())));
 }
 
+TEST_F(PixelTest, DrawNullImageInRoundedFilter) {
+  TestTree(new FilterNode(
+      ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)),
+      new ImageNode(nullptr)));
+}
+
 TEST_F(PixelTest, ClearRectNodeTest) {
   CompositionNode::Builder composition_node_builder;
   composition_node_builder.AddChild(new RectNode(
diff --git a/src/cobalt/renderer/rasterizer/testdata/DrawNullImageInRoundedFilter-expected.png b/src/cobalt/renderer/rasterizer/testdata/DrawNullImageInRoundedFilter-expected.png
new file mode 100644
index 0000000..7f0bfcf
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/DrawNullImageInRoundedFilter-expected.png
Binary files differ
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py
index f9b1034..c710a3a 100644
--- a/src/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -139,6 +139,10 @@
     """Sends a system signal to put Cobalt into suspend state."""
     self.launcher.SendSuspend()
 
+  def SendDeepLink(self, link):
+    """Sends a deep link to Cobalt."""
+    return self.launcher.SendDeepLink(link)
+
   def GetURL(self):
     return self.url
 
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index e8b74f9..98d2e93 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "20.lts.2"
+#define COBALT_VERSION "20.lts.3"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/websocket/cobalt_web_socket_event_handler.cc b/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
index 1d3db2e..399c19c 100644
--- a/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
+++ b/src/cobalt/websocket/cobalt_web_socket_event_handler.cc
@@ -132,5 +132,9 @@
   creator_->OnWriteDone(bytes_written);
 }
 
+void CobaltWebSocketEventHandler::OnFlowControl(int64_t quota) {
+  creator_->OnFlowControl(quota);
+}
+
 }  // namespace websocket
 }  // namespace cobalt
\ No newline at end of file
diff --git a/src/cobalt/websocket/cobalt_web_socket_event_handler.h b/src/cobalt/websocket/cobalt_web_socket_event_handler.h
index 6cb92fb..b974895 100644
--- a/src/cobalt/websocket/cobalt_web_socket_event_handler.h
+++ b/src/cobalt/websocket/cobalt_web_socket_event_handler.h
@@ -57,7 +57,7 @@
   // Called to provide more send quota for this channel to the renderer
   // process. Currently the quota units are always bytes of message body
   // data. In future it might depend on the type of multiplexing in use.
-  virtual void OnFlowControl(int64_t /*quota*/) override {}
+  virtual void OnFlowControl(int64_t quota) override;
 
   // Called when the remote server has Started the WebSocket Closing
   // Handshake. The client should not attempt to send any more messages after
diff --git a/src/cobalt/websocket/mock_websocket_channel.cc b/src/cobalt/websocket/mock_websocket_channel.cc
new file mode 100644
index 0000000..8fc17cf
--- /dev/null
+++ b/src/cobalt/websocket/mock_websocket_channel.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cobalt/websocket/mock_websocket_channel.h"
+#include "cobalt/websocket/cobalt_web_socket_event_handler.h"
+
+// Generated constructors and destructors for GMock objects are very large. By
+// putting them in a separate file we can speed up compile times.
+
+namespace cobalt {
+namespace websocket {
+
+MockWebSocketChannel::MockWebSocketChannel(
+    WebSocketImpl* impl, network::NetworkModule* network_module)
+    : net::WebSocketChannel(std::unique_ptr<net::WebSocketEventInterface>(
+                                new CobaltWebSocketEventHandler(impl)),
+                            network_module->url_request_context()) {}
+
+MockWebSocketChannel::~MockWebSocketChannel() = default;
+
+}  // namespace websocket
+}  // namespace cobalt
\ No newline at end of file
diff --git a/src/cobalt/websocket/mock_websocket_channel.h b/src/cobalt/websocket/mock_websocket_channel.h
new file mode 100644
index 0000000..85bb534
--- /dev/null
+++ b/src/cobalt/websocket/mock_websocket_channel.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/synchronization/lock.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/websocket/web_socket_impl.h"
+#include "net/websockets/websocket_channel.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace cobalt {
+namespace websocket {
+
+class SourceLocation;
+
+class MockWebSocketChannel : public net::WebSocketChannel {
+ public:
+  MockWebSocketChannel(WebSocketImpl* impl,
+                       network::NetworkModule* network_module);
+  ~MockWebSocketChannel();
+
+  MOCK_METHOD4(MockSendFrame,
+               net::WebSocketChannel::ChannelState(
+                   bool fin, net::WebSocketFrameHeader::OpCode op_code,
+                   scoped_refptr<net::IOBuffer> buffer, size_t buffer_size));
+  net::WebSocketChannel::ChannelState SendFrame(
+      bool fin, net::WebSocketFrameHeader::OpCode op_code,
+      scoped_refptr<net::IOBuffer> buffer, size_t buffer_size) override {
+    base::AutoLock scoped_lock(lock_);
+    return MockSendFrame(fin, op_code, buffer, buffer_size);
+  }
+
+  base::Lock& lock() { return lock_; }
+
+ private:
+  base::Lock lock_;
+};
+
+}  // namespace websocket
+}  // namespace cobalt
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBSOCKETS_MOCK_WEBSOCKET_CHANNEL_H_
\ No newline at end of file
diff --git a/src/cobalt/websocket/web_socket.h b/src/cobalt/websocket/web_socket.h
index 2bed313..050652a 100644
--- a/src/cobalt/websocket/web_socket.h
+++ b/src/cobalt/websocket/web_socket.h
@@ -242,6 +242,7 @@
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, FailInvalidSubProtocols);
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, SubProtocols);
   FRIEND_TEST_ALL_PREFIXES(WebSocketTest, DuplicatedSubProtocols);
+  friend class WebSocketImplTest;
 
   DISALLOW_COPY_AND_ASSIGN(WebSocket);
 };
diff --git a/src/cobalt/websocket/web_socket_impl.cc b/src/cobalt/websocket/web_socket_impl.cc
index 844d8f7..19e2062 100644
--- a/src/cobalt/websocket/web_socket_impl.cc
+++ b/src/cobalt/websocket/web_socket_impl.cc
@@ -27,9 +27,6 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/websocket/web_socket.h"
 #include "net/http/http_util.h"
-#include "net/websockets/websocket_errors.h"
-#include "net/websockets/websocket_frame.h"
-#include "net/websockets/websocket_handshake_stream_create_helper.h"
 #include "starboard/memory.h"
 
 namespace cobalt {
@@ -72,6 +69,7 @@
   // priority thread might be required.  Investigation is needed.
   delegate_task_runner_ =
       network_module_->url_request_context_getter()->GetNetworkTaskRunner();
+  DCHECK(delegate_task_runner_);
   base::WaitableEvent channel_created_event(
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED);
@@ -163,6 +161,12 @@
                             selected_subprotocol));
 }
 
+void WebSocketImpl::OnFlowControl(int64_t quota) {
+  DCHECK(current_quota_ >= 0);
+  current_quota_ += quota;
+  ProcessSendQueue();
+}
+
 void WebSocketImpl::OnWebSocketConnected(
     const std::string &selected_subprotocol) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -260,13 +264,40 @@
     DLOG(WARNING) << "Attempt to send over a closed channel.";
     return;
   }
+  SendQueueMessage new_message = {io_buffer, length, op_code};
+  send_queue_.push(std::move(new_message));
+  ProcessSendQueue();
+}
 
-  // this behavior is not just an optimization, but required in case
-  // we are closing the connection
-  auto channel_state =
-      websocket_channel_->SendFrame(true /*fin*/, op_code, io_buffer, length);
-  if (channel_state == net::WebSocketChannel::CHANNEL_DELETED) {
-    websocket_channel_.reset();
+void WebSocketImpl::ProcessSendQueue() {
+  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  while (current_quota_ > 0 && !send_queue_.empty()) {
+    SendQueueMessage message = send_queue_.front();
+    size_t current_message_length = message.length - sent_size_of_top_message_;
+    bool final = false;
+    if (current_quota_ < static_cast<int64_t>(current_message_length)) {
+      // quota is not enough to send the top message.
+      scoped_refptr<net::IOBuffer> new_io_buffer(
+          new net::IOBuffer(static_cast<size_t>(current_quota_)));
+      SbMemoryCopy(new_io_buffer->data(),
+                   message.io_buffer->data() + sent_size_of_top_message_,
+                   current_quota_);
+      sent_size_of_top_message_ += current_quota_;
+      message.io_buffer = new_io_buffer;
+      current_message_length = current_quota_;
+      current_quota_ = 0;
+    } else {
+      // Sent all of the remaining top message.
+      final = true;
+      send_queue_.pop();
+      sent_size_of_top_message_ = 0;
+      current_quota_ -= current_message_length;
+    }
+    auto channel_state = websocket_channel_->SendFrame(
+        final, message.op_code, message.io_buffer, current_message_length);
+    if (channel_state == net::WebSocketChannel::CHANNEL_DELETED) {
+      websocket_channel_.reset();
+    }
   }
 }
 
diff --git a/src/cobalt/websocket/web_socket_impl.h b/src/cobalt/websocket/web_socket_impl.h
index 5e5977d..6c65620 100644
--- a/src/cobalt/websocket/web_socket_impl.h
+++ b/src/cobalt/websocket/web_socket_impl.h
@@ -16,6 +16,7 @@
 #define COBALT_WEBSOCKET_WEB_SOCKET_IMPL_H_
 
 #include <memory>
+#include <queue>
 #include <string>
 #include <vector>
 
@@ -32,7 +33,10 @@
 #include "cobalt/websocket/web_socket_message_container.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/websockets/websocket_channel.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_frame.h"
 #include "net/websockets/websocket_frame_parser.h"
+#include "net/websockets/websocket_handshake_stream_create_helper.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -85,6 +89,8 @@
 
   void OnHandshakeComplete(const std::string& selected_subprotocol);
 
+  void OnFlowControl(int64_t quota);
+
   struct CloseInfo {
     CloseInfo(const net::WebSocketError code, const std::string& reason)
         : code(code), reason(reason) {}
@@ -108,6 +114,7 @@
   bool SendHelper(const net::WebSocketFrameHeader::OpCode op_code,
                   const char* data, std::size_t length,
                   std::string* error_message);
+  void ProcessSendQueue();
 
   void OnWebSocketConnected(const std::string& selected_subprotocol);
   void OnWebSocketDisconnected(bool was_clean, uint16 code,
@@ -125,11 +132,23 @@
   std::string origin_;
   GURL connect_url_;
 
+  // Data buffering and flow control.
+  // Should only be modified on delegate(network) thread.
+  int64_t current_quota_ = 0;
+  struct SendQueueMessage {
+    scoped_refptr<net::IOBuffer> io_buffer;
+    size_t length;
+    net::WebSocketFrameHeader::OpCode op_code;
+  };
+  std::queue<SendQueueMessage> send_queue_;
+  size_t sent_size_of_top_message_ = 0;
+
   scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> owner_task_runner_;
 
   ~WebSocketImpl();
   friend class base::RefCountedThreadSafe<WebSocketImpl>;
+  friend class WebSocketImplTest;
 
   DISALLOW_COPY_AND_ASSIGN(WebSocketImpl);
 };
diff --git a/src/cobalt/websocket/web_socket_impl_test.cc b/src/cobalt/websocket/web_socket_impl_test.cc
new file mode 100644
index 0000000..c8daf33
--- /dev/null
+++ b/src/cobalt/websocket/web_socket_impl_test.cc
@@ -0,0 +1,229 @@
+// Copyright 2017 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.
+
+#include "cobalt/websocket/web_socket_impl.h"
+#include "cobalt/websocket/web_socket.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/test/scoped_task_environment.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
+#include "cobalt/dom/window.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/script_exception.h"
+#include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/websocket/mock_websocket_channel.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+using ::testing::DefaultValue;
+using ::testing::Return;
+using cobalt::script::testing::MockExceptionState;
+
+namespace cobalt {
+namespace websocket {
+namespace {
+// These limits are copied from net::WebSocketChannel implementation.
+const int kDefaultSendQuotaHighWaterMark = 1 << 17;
+const int k800KB = 800;
+const int kTooMuch = kDefaultSendQuotaHighWaterMark + 1;
+const int kWayTooMuch = kDefaultSendQuotaHighWaterMark * 2 + 1;
+const int k512KB = 512;
+
+class FakeSettings : public dom::DOMSettings {
+ public:
+  FakeSettings()
+      : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                         NULL, NULL),
+        base_("https://127.0.0.1:1234") {
+    network_module_.reset(new network::NetworkModule());
+    this->set_network_module(network_module_.get());
+  }
+  const GURL& base_url() const override { return base_; }
+
+  // public members, so that they're easier for testing.
+  GURL base_;
+  std::unique_ptr<network::NetworkModule> network_module_;
+};
+}  // namespace
+
+class WebSocketImplTest : public ::testing::Test {
+ public:
+  dom::DOMSettings* settings() const { return settings_.get(); }
+  void AddQuota(int quota) {
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(&WebSocketImpl::OnFlowControl, websocket_impl_, quota));
+  }
+
+ protected:
+  WebSocketImplTest() : settings_(new FakeSettings()) {
+    std::vector<std::string> sub_protocols;
+    sub_protocols.push_back("chat");
+    // Use local URL so that WebSocket will not complain about URL format.
+    ws_ = new WebSocket(settings(), "wss://127.0.0.1:1234", sub_protocols,
+                        &exception_state_, false);
+
+    websocket_impl_ = ws_->impl_;
+    network_task_runner_ = settings_->network_module()
+                               ->url_request_context_getter()
+                               ->GetNetworkTaskRunner();
+    // The holder is only created to be base::Passed() on the next line, it will
+    // be empty so do not use it later.
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(
+            [](scoped_refptr<WebSocketImpl> websocket_impl,
+               MockWebSocketChannel** mock_channel_slot,
+               dom::DOMSettings* settings) {
+              *mock_channel_slot = new MockWebSocketChannel(
+                  websocket_impl.get(), settings->network_module());
+              websocket_impl->websocket_channel_ =
+                  std::unique_ptr<net::WebSocketChannel>(*mock_channel_slot);
+            },
+            websocket_impl_, &mock_channel_, settings()));
+  }
+  ~WebSocketImplTest() {
+    network_task_runner_->PostBlockingTask(
+        FROM_HERE,
+        base::Bind(&WebSocketImpl::OnClose, websocket_impl_, true /*was_clan*/,
+                   net::kWebSocketNormalClosure /*error_code*/,
+                   "" /*close_reason*/));
+  }
+
+  base::test::ScopedTaskEnvironment env_;
+
+  std::unique_ptr<FakeSettings> settings_;
+  scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+  scoped_refptr<WebSocket> ws_;
+  scoped_refptr<WebSocketImpl> websocket_impl_;
+  MockWebSocketChannel* mock_channel_;
+  StrictMock<MockExceptionState> exception_state_;
+};
+
+TEST_F(WebSocketImplTest, NormalSizeRequest) {
+  // Normally the high watermark quota is given at websocket connection success.
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    // mock_channel_ is created and used on network thread.
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, k800KB))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[k800KB];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, k800KB, &buffered_amount, &error);
+}
+
+TEST_F(WebSocketImplTest, LargeRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kDefaultSendQuotaHighWaterMark))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kDefaultSendQuotaHighWaterMark];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, kDefaultSendQuotaHighWaterMark,
+                            &buffered_amount, &error);
+}
+
+TEST_F(WebSocketImplTest, OverLimitRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kDefaultSendQuotaHighWaterMark))
+        .Times(2)
+        .WillRepeatedly(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kWayTooMuch];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendText(data, kWayTooMuch, &buffered_amount, &error);
+
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+}
+
+
+TEST_F(WebSocketImplTest, ReuseSocketForLargeRequest) {
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+
+  // mock_channel_ is created and used on network thread.
+  {
+    base::AutoLock scoped_lock(mock_channel_->lock());
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeBinary,
+                              _, kDefaultSendQuotaHighWaterMark))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(
+        *mock_channel_,
+        MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeBinary, _, 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
+                              k512KB - 1))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+    EXPECT_CALL(*mock_channel_,
+                MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
+                              kTooMuch - (k512KB - 1)))
+        .Times(1)
+        .WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
+  }
+
+  char data[kTooMuch];
+  int32 buffered_amount = 0;
+  std::string error;
+  websocket_impl_->SendBinary(data, kTooMuch, &buffered_amount, &error);
+  websocket_impl_->SendText(data, kTooMuch, &buffered_amount, &error);
+
+  AddQuota(k512KB);
+  AddQuota(kDefaultSendQuotaHighWaterMark);
+}
+
+}  // namespace websocket
+}  // namespace cobalt
diff --git a/src/cobalt/websocket/web_socket_test.cc b/src/cobalt/websocket/web_socket_test.cc
index f17c28f..fc96ced 100644
--- a/src/cobalt/websocket/web_socket_test.cc
+++ b/src/cobalt/websocket/web_socket_test.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "base/memory/ref_counted.h"
+#include "base/test/scoped_task_environment.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
@@ -35,19 +36,23 @@
 namespace cobalt {
 namespace websocket {
 
+namespace {
 class FakeSettings : public dom::DOMSettings {
  public:
   FakeSettings()
       : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
                          NULL),
         base_("https://example.com") {
-    this->set_network_module(NULL);
+    network_module_.reset(new network::NetworkModule());
+    this->set_network_module(network_module_.get());
   }
   const GURL& base_url() const override { return base_; }
 
   // public members, so that they're easier for testing.
   GURL base_;
+  std::unique_ptr<network::NetworkModule> network_module_;
 };
+}  // namespace
 
 class WebSocketTest : public ::testing::Test {
  public:
@@ -56,8 +61,7 @@
  protected:
   WebSocketTest() : settings_(new FakeSettings()) {}
 
-  // A nested message loop needs a non-nested message loop to exist.
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment env_;
 
   std::unique_ptr<FakeSettings> settings_;
   StrictMock<MockExceptionState> exception_state_;
diff --git a/src/cobalt/websocket/websocket.gyp b/src/cobalt/websocket/websocket.gyp
index bd7033d..8a4f401 100644
--- a/src/cobalt/websocket/websocket.gyp
+++ b/src/cobalt/websocket/websocket.gyp
@@ -36,6 +36,7 @@
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/cobalt/network/network.gyp:network',
         '<(DEPTH)/net/net.gyp:net',
         '<(DEPTH)/url/url.gyp:url',
       ],
@@ -46,6 +47,8 @@
       'type': '<(gtest_target_type)',
       'sources': [
         'web_socket_test.cc',
+        'mock_websocket_channel.cc',
+        'web_socket_impl_test.cc',
       ],
       'dependencies': [
         'websocket',
@@ -59,6 +62,14 @@
         #       ScriptValueFactory has non-virtual method CreatePromise().
         '<(DEPTH)/cobalt/script/engine.gyp:engine',
       ],
+      'conditions': [
+        # The network gyp targets depends on 'debug' in unit test builds.
+        ['enable_debugger == 1', {
+          'dependencies': [
+            '<(DEPTH)/cobalt/debug/debug.gyp:debug',
+          ],
+        }],
+      ],
     },
 
     {
diff --git a/src/nb/reuse_allocator_base.cc b/src/nb/reuse_allocator_base.cc
index cc2bfe4..03e9903 100644
--- a/src/nb/reuse_allocator_base.cc
+++ b/src/nb/reuse_allocator_base.cc
@@ -327,30 +327,33 @@
     std::size_t alignment) {
   void* ptr = NULL;
   std::size_t size_to_try = 0;
+  // We try to allocate in unit of |allocation_increment_| to minimize
+  // fragmentation.
   if (allocation_increment_ > size) {
-    size_to_try = std::max(size, allocation_increment_);
-    if (max_capacity_ && capacity_ + size_to_try > max_capacity_) {
-      return free_blocks_.end();
+    size_to_try = allocation_increment_;
+    if (!max_capacity_ || capacity_ + size_to_try <= max_capacity_) {
+      ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
     }
-    ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
   }
+  // |ptr| being null indicates the above allocation failed, or in the rare case
+  // |size| is larger than |allocation_increment_|. Try to allocate a block of
+  // |size| instead for both cases.
   if (ptr == NULL) {
     size_to_try = size;
-    if (max_capacity_ && capacity_ + size_to_try > max_capacity_) {
-      return free_blocks_.end();
+    if (!max_capacity_ || capacity_ + size_to_try <= max_capacity_) {
+      ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
     }
-    ptr = fallback_allocator_->AllocateForAlignment(&size_to_try, alignment);
   }
   if (ptr != NULL) {
     fallback_allocations_.push_back(ptr);
     capacity_ += size_to_try;
     return AddFreeBlock(MemoryBlock(ptr, size_to_try));
   }
-
   if (free_blocks_.empty()) {
     return free_blocks_.end();
   }
 
+  // If control reaches here, then the prior allocation attempts have failed.
   // We failed to allocate for |size| from the fallback allocator, try to
   // allocate the difference between |size| and the size of the right most block
   // in the hope that they are continuous and can be connect to a block that is
diff --git a/src/nb/reuse_allocator_benchmark.cc b/src/nb/reuse_allocator_benchmark.cc
index 86d82e2..4d93203 100644
--- a/src/nb/reuse_allocator_benchmark.cc
+++ b/src/nb/reuse_allocator_benchmark.cc
@@ -58,7 +58,7 @@
   SB_DCHECK(result);
 
   std::vector<char> buffer(file_info.size);
-  int bytes_read = SbFileRead(file, &buffer[0], buffer.size());
+  int bytes_read = SbFileReadAll(file, &buffer[0], buffer.size());
   SB_DCHECK(bytes_read == file_info.size);
   SbFileClose(file);
 
diff --git a/src/net/dial/dial_udp_server.cc b/src/net/dial/dial_udp_server.cc
index a963728..affd934 100644
--- a/src/net/dial/dial_udp_server.cc
+++ b/src/net/dial/dial_udp_server.cc
@@ -147,12 +147,14 @@
     // After optimization, some compiler will dereference and get response size
     // later than passing response.
     auto response_size = response->size();
-    auto sent_num = socket_->SendTo(
+    auto result = socket_->SendTo(
         fake_buffer.get(), response_size, client_address_,
         base::Bind([](scoped_refptr<WrappedIOBuffer>,
                       std::unique_ptr<std::string>, int /*rv*/) {},
                    fake_buffer, base::Passed(&response)));
-    DCHECK_EQ(sent_num, response_size);
+    if (result < 0) {
+      DLOG(WARNING) << "Socket SentTo error code: " << result;
+    }
   }
 
   // Register a watcher on the message loop and wait for the next dial message.
diff --git a/src/net/socket/transport_client_socket_pool.cc b/src/net/socket/transport_client_socket_pool.cc
index f5689ab..263e17c2 100644
--- a/src/net/socket/transport_client_socket_pool.cc
+++ b/src/net/socket/transport_client_socket_pool.cc
@@ -258,6 +258,15 @@
 int TransportConnectJob::DoResolveHostComplete(int result) {
   TRACE_EVENT0(kNetTracingCategory,
                "TransportConnectJob::DoResolveHostComplete");
+#ifdef STARBOARD
+    // Preferentially connect to an IPv4 address first, if available. Some
+    // hosts may have IPv6 addresses to which we can connect, but the read
+    // may still fail if the network is not properly configured. The existing
+    // code has a fallback mechanism to try different IPs in |addresses_|
+    // when connection fails. However, in this case, a connection can be made
+    // with the IPv6 address, but the read fails.
+    MakeAddressListStartWithIPv4(&addresses_);
+#endif
   connect_timing_.dns_end = base::TimeTicks::Now();
   // Overwrite connection start time, since for connections that do not go
   // through proxies, |connect_start| should not include dns lookup time.
diff --git a/src/net/socket/transport_client_socket_pool_unittest.cc b/src/net/socket/transport_client_socket_pool_unittest.cc
index 9d64c43..e8c7543 100644
--- a/src/net/socket/transport_client_socket_pool_unittest.cc
+++ b/src/net/socket/transport_client_socket_pool_unittest.cc
@@ -856,6 +856,10 @@
   handle.Reset();
 }
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test the case of the IPv6 address stalling, and falling back to the IPv4
 // socket which finishes first.
 TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv4FinishesFirst) {
@@ -903,7 +907,12 @@
 
   EXPECT_EQ(2, client_socket_factory_.allocation_count());
 }
+#endif
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test the case of the IPv6 address being slow, thus falling back to trying to
 // connect to the IPv4 address, but having the connect to the IPv6 address
 // finish first.
@@ -955,6 +964,7 @@
 
   EXPECT_EQ(2, client_socket_factory_.allocation_count());
 }
+#endif
 
 TEST_F(TransportClientSocketPoolTest, IPv6NoIPv4AddressesToFallbackTo) {
   // Create a pool without backup jobs.
@@ -1072,6 +1082,10 @@
   EXPECT_TRUE(socket_data.IsUsingTCPFastOpen());
 }
 
+// Disable this test since the TransportConnectJob::DoResolveHostComplete
+// customization causes the IPv4 address to be tried first, thus breaking
+// the assumptions of this test.
+#ifndef STARBOARD
 // Test that if TCP FastOpen is enabled, it does not do anything when there
 // is a IPv6 address with fallback to an IPv4 address. This test tests the case
 // when the IPv6 connect fails and the IPv4 one succeeds.
@@ -1141,6 +1155,7 @@
   // Verify that TCP FastOpen was not turned on for the socket.
   EXPECT_FALSE(socket_data.IsUsingTCPFastOpen());
 }
+#endif
 
 // Test that SocketTag passed into TransportClientSocketPool is applied to
 // returned sockets.
diff --git a/src/net/socket/udp_socket_starboard.cc b/src/net/socket/udp_socket_starboard.cc
index 707fb74..369cf68 100644
--- a/src/net/socket/udp_socket_starboard.cc
+++ b/src/net/socket/udp_socket_starboard.cc
@@ -441,7 +441,7 @@
 
   if (result != ERR_IO_PENDING) {
     IPEndPoint log_address;
-    if (log_address.FromSbSocketAddress(&sb_address)) {
+    if (result < 0 || !log_address.FromSbSocketAddress(&sb_address)) {
       LogRead(result, buf->data(), NULL);
     } else {
       LogRead(result, buf->data(), &log_address);
diff --git a/src/net/websockets/websocket_channel.h b/src/net/websockets/websocket_channel.h
index 2eae388..d914a08 100644
--- a/src/net/websockets/websocket_channel.h
+++ b/src/net/websockets/websocket_channel.h
@@ -90,10 +90,15 @@
   // character boundaries. Calling SendFrame may result in synchronous calls to
   // |event_interface_| which may result in this object being deleted. In that
   // case, the return value will be CHANNEL_DELETED.
+#if defined(STARBOARD)
+  // Make it virtual to enable mocking for unit tests.
+  virtual ChannelState SendFrame(bool fin,
+#else
   ChannelState SendFrame(bool fin,
-                         WebSocketFrameHeader::OpCode op_code,
-                         scoped_refptr<IOBuffer> buffer,
-                         size_t buffer_size);
+#endif
+                                 WebSocketFrameHeader::OpCode op_code,
+                                 scoped_refptr<IOBuffer> buffer,
+                                 size_t buffer_size);
 
   // Sends |quota| units of flow control to the remote side. If the underlying
   // transport has a concept of |quota|, then it permits the remote server to
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index 4619b57..ee85466 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -90,6 +90,35 @@
 
   private MediaCrypto mMediaCrypto;
 
+  // Return value type for calls to updateSession(), which contains whether or not the call
+  // succeeded, and optionally an error message (that is empty on success).
+  @UsedByNative
+  private static class UpdateSessionResult {
+    public enum Status {
+      SUCCESS,
+      FAILURE
+    }
+
+    // Whether or not the update session attempt succeeded or failed.
+    private boolean mIsSuccess;
+
+    // Descriptive error message or details, in the scenario where the update session call failed.
+    private String mErrorMessage;
+
+    public UpdateSessionResult(Status status, String errorMessage) {
+      this.mIsSuccess = status == Status.SUCCESS;
+      this.mErrorMessage = errorMessage;
+    }
+
+    public boolean isSuccess() {
+      return mIsSuccess;
+    }
+
+    public String getErrorMessage() {
+      return mErrorMessage;
+    }
+  }
+
   /**
    * Create a new MediaDrmBridge with the Widevine crypto scheme.
    *
@@ -220,16 +249,22 @@
    * @param response Response data from the server.
    */
   @UsedByNative
-  boolean updateSession(byte[] sessionId, byte[] response) {
+  UpdateSessionResult updateSession(int ticket, byte[] sessionId, byte[] response) {
     Log.d(TAG, "updateSession()");
     if (mMediaDrm == null) {
       Log.e(TAG, "updateSession() called when MediaDrm is null.");
-      return false;
+      return new UpdateSessionResult(
+          UpdateSessionResult.Status.FAILURE,
+          "Null MediaDrm object when calling updateSession(). StackTrace: "
+              + android.util.Log.getStackTraceString(new Throwable()));
     }
 
     if (!sessionExists(sessionId)) {
       Log.e(TAG, "updateSession tried to update a session that does not exist.");
-      return false;
+      return new UpdateSessionResult(
+          UpdateSessionResult.Status.FAILURE,
+          "Failed to update session because it does not exist. StackTrace: "
+              + android.util.Log.getStackTraceString(new Throwable()));
     }
 
     try {
@@ -246,16 +281,32 @@
         // Pass null to indicate that KeyStatus isn't supported.
         nativeOnKeyStatusChange(mNativeMediaDrmBridge, sessionId, null);
       }
-      return true;
+      return new UpdateSessionResult(UpdateSessionResult.Status.SUCCESS, "");
     } catch (NotProvisionedException e) {
       // TODO: Should we handle this?
-      Log.e(TAG, "failed to provide key response", e);
+      Log.e(TAG, "Failed to provide key response", e);
+      release();
+      return new UpdateSessionResult(
+          UpdateSessionResult.Status.FAILURE,
+          "Update session failed due to lack of provisioning. StackTrace: "
+              + android.util.Log.getStackTraceString(e));
     } catch (DeniedByServerException e) {
-      Log.e(TAG, "failed to provide key response", e);
+      Log.e(TAG, "Failed to provide key response.", e);
+      release();
+      return new UpdateSessionResult(
+          UpdateSessionResult.Status.FAILURE,
+          "Update session failed because we were denied by server. StackTrace: "
+              + android.util.Log.getStackTraceString(e));
+    } catch (Exception e) {
+      Log.e(TAG, "", e);
+      release();
+      return new UpdateSessionResult(
+          UpdateSessionResult.Status.FAILURE,
+          "Update session failed. Caught exception: "
+              + e.getMessage()
+              + " StackTrace: "
+              + android.util.Log.getStackTraceString(e));
     }
-    Log.e(TAG, "Update session failed.");
-    release();
-    return false;
   }
 
   /**
diff --git a/src/starboard/android/shared/drm_system.cc b/src/starboard/android/shared/drm_system.cc
index 943633e..87845d1 100644
--- a/src/starboard/android/shared/drm_system.cc
+++ b/src/starboard/android/shared/drm_system.cc
@@ -235,13 +235,22 @@
       ByteArrayFromRaw(session_id, session_id_size));
   ScopedLocalJavaRef<jbyteArray> j_response(ByteArrayFromRaw(key, key_size));
 
-  jboolean status = JniEnvExt::Get()->CallBooleanMethodOrAbort(
-      j_media_drm_bridge_, "updateSession", "([B[B)Z", j_session_id.Get(),
-      j_response.Get());
-  session_updated_callback_(
-      this, context_, ticket,
-      status == JNI_TRUE ? kSbDrmStatusSuccess : kSbDrmStatusUnknownError, NULL,
-      session_id, session_id_size);
+  auto env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jobject> update_result(env->CallObjectMethodOrAbort(
+      j_media_drm_bridge_, "updateSession",
+      "(I[B[B)Ldev/cobalt/media/MediaDrmBridge$UpdateSessionResult;",
+      static_cast<jint>(ticket), j_session_id.Get(), j_response.Get()));
+  jboolean update_success =
+      env->CallBooleanMethodOrAbort(update_result.Get(), "isSuccess", "()Z");
+  ScopedLocalJavaRef<jstring> error_msg_java(env->CallObjectMethodOrAbort(
+      update_result.Get(), "getErrorMessage", "()Ljava/lang/String;"));
+  std::string error_msg =
+      env->GetStringStandardUTFOrAbort(error_msg_java.Get());
+  session_updated_callback_(this, context_, ticket,
+                            update_success == JNI_TRUE
+                                ? kSbDrmStatusSuccess
+                                : kSbDrmStatusUnknownError,
+                            error_msg.c_str(), session_id, session_id_size);
 }
 
 void DrmSystem::CloseSession(const void* session_id, int session_id_size) {
diff --git a/src/starboard/file.h b/src/starboard/file.h
index 8fc57b5..cb780c6 100644
--- a/src/starboard/file.h
+++ b/src/starboard/file.h
@@ -276,7 +276,7 @@
   int rv;
   do {
     rv = SbFileRead(file, data + bytes_read, size - bytes_read);
-    if (bytes_read <= 0) {
+    if (rv <= 0) {
       break;
     }
     bytes_read += rv;
@@ -302,7 +302,7 @@
   int rv;
   do {
     rv = SbFileWrite(file, data + bytes_written, size - bytes_written);
-    if (bytes_written <= 0) {
+    if (rv <= 0) {
       break;
     }
     bytes_written += rv;
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index c77e593..50ee707 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -91,4 +91,8 @@
 
   __FILTERED_TESTS = {
       'nplb': ['SbDrmTest.AnySupportedKeySystems',],
+      # It seems that ffmpeg opus decoder's output frames has changed, the
+      # frames calculation in tests doesn't work properly. Temporarily disable
+      # the tests.
+      'player_filter_tests': ['*',],
   }
diff --git a/src/starboard/linux/shared/launcher.py b/src/starboard/linux/shared/launcher.py
index 8fe2616..dcb2355 100644
--- a/src/starboard/linux/shared/launcher.py
+++ b/src/starboard/linux/shared/launcher.py
@@ -24,6 +24,7 @@
 
 import _env  # pylint: disable=unused-import
 from starboard.tools import abstract_launcher
+from starboard.tools import send_link
 
 STATUS_CHANGE_TIMEOUT = 15
 
@@ -107,6 +108,15 @@
     else:
       sys.stderr.write("Cannot send suspend to executable; it is closed.\n")
 
+  def SupportsDeepLink(self):
+    return True
+
+  def SendDeepLink(self, link):
+    # The connect call in SendLink occassionally fails. Retry a few times if this happens.
+    connection_attempts = 3
+    return send_link.SendLink(
+        os.path.basename(self.executable), link, connection_attempts)
+
   def WaitForProcessStatus(self, target_status, timeout):
     """Wait for Cobalt to turn to target status within specified timeout limit.
 
diff --git a/src/starboard/nplb/file_helpers.cc b/src/starboard/nplb/file_helpers.cc
index 8e6f781..ff7753a 100644
--- a/src/starboard/nplb/file_helpers.cc
+++ b/src/starboard/nplb/file_helpers.cc
@@ -89,7 +89,7 @@
     data[i] = static_cast<char>(i & 0xFF);
   }
 
-  int bytes = SbFileWrite(file, data, length);
+  int bytes = SbFileWriteAll(file, data, length);
   EXPECT_EQ(bytes, length) << "Failed to write " << length << " bytes to "
                            << filename;
 
diff --git a/src/starboard/nplb/file_read_all_test.cc b/src/starboard/nplb/file_read_all_test.cc
new file mode 100644
index 0000000..6fcba6e
--- /dev/null
+++ b/src/starboard/nplb/file_read_all_test.cc
@@ -0,0 +1,61 @@
+// 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.
+
+#include "starboard/file.h"
+#include "starboard/nplb/file_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+class SbReadWriteAllTestWithBuffer
+    : public ::testing::TestWithParam<int> {
+ public:
+  int GetBufferSize() { return GetParam(); }
+};
+
+TEST_P(SbReadWriteAllTestWithBuffer, ReadFile) {
+  ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate);
+  const std::string& filename = random_file.filename();
+
+  SbFile file = SbFileOpen(
+      filename.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL);
+
+  std::vector<char> file_contents;
+  file_contents.reserve(GetBufferSize());
+  for (int i = 0; i < GetBufferSize(); ++i) {
+    file_contents.push_back(i % 255);
+  }
+  SbFileWriteAll(file, file_contents.data(), file_contents.size());
+
+  SbFileClose(file);
+
+  SbFile file = SbFileOpen(filename.c_str(), kSbFileRead, NULL, NULL);
+  std::vector<char> read_contents(GetBufferSize());
+  SbFileReadAll(file, read_contents.data(), read_contents.size());
+
+  EXPECT_EQ(file_contents, read_contents);
+
+  SbFileClose(file);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SbReadAllTestSbReadWriteAllTest,
+    SbReadWriteAllTestWithBuffer,
+    ::testing::Values(0, 1, 1024, 16 * 1024, 128 * 1024, 1024 * 1024));
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/file_read_write_all_test.cc b/src/starboard/nplb/file_read_write_all_test.cc
new file mode 100644
index 0000000..9782fde
--- /dev/null
+++ b/src/starboard/nplb/file_read_write_all_test.cc
@@ -0,0 +1,65 @@
+// 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.
+
+#include "starboard/file.h"
+#include "starboard/nplb/file_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+class SbReadWriteAllTestWithBuffer
+    : public ::testing::TestWithParam<int> {
+ public:
+  int GetBufferSize() { return GetParam(); }
+};
+
+TEST_P(SbReadWriteAllTestWithBuffer, ReadFile) {
+  ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate);
+  const std::string& filename = random_file.filename();
+
+  SbFile file = SbFileOpen(
+      filename.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL);
+
+  std::vector<char> file_contents;
+  file_contents.reserve(GetBufferSize());
+  for (int i = 0; i < GetBufferSize(); ++i) {
+    file_contents.push_back(i % 255);
+  }
+  int bytes_written =
+      SbFileWriteAll(file, file_contents.data(), file_contents.size());
+  EXPECT_EQ(GetBufferSize(), bytes_written);
+
+  SbFileClose(file);
+
+  file = SbFileOpen(
+      filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL);
+  std::vector<char> read_contents(GetBufferSize());
+  int bytes_read =
+      SbFileReadAll(file, read_contents.data(), read_contents.size());
+  EXPECT_EQ(GetBufferSize(), bytes_read);
+  EXPECT_EQ(file_contents, read_contents);
+
+  SbFileClose(file);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SbReadAllTestSbReadWriteAllTest,
+    SbReadWriteAllTestWithBuffer,
+    ::testing::Values(0, 1, 1024, 16 * 1024, 128 * 1024, 1024 * 1024));
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/file_truncate_test.cc b/src/starboard/nplb/file_truncate_test.cc
index 300aa96..ae2b3de 100644
--- a/src/starboard/nplb/file_truncate_test.cc
+++ b/src/starboard/nplb/file_truncate_test.cc
@@ -99,7 +99,7 @@
   }
 
   char buffer[kEndSize] = {0};
-  int bytes = SbFileRead(file, buffer, kEndSize);
+  int bytes = SbFileReadAll(file, buffer, kEndSize);
   EXPECT_EQ(kEndSize, bytes);
 
   ScopedRandomFile::ExpectPattern(0, buffer, kStartSize, __LINE__);
diff --git a/src/starboard/nplb/file_write_test.cc b/src/starboard/nplb/file_write_test.cc
index 576c842..4aca8d7 100644
--- a/src/starboard/nplb/file_write_test.cc
+++ b/src/starboard/nplb/file_write_test.cc
@@ -85,7 +85,8 @@
 
     int remaining = kFileSize - total;
     int to_write = remaining < kBufferLength ? remaining : kBufferLength;
-    int bytes_written = TypeParam::Write(file, buffer, to_write);
+    int bytes_written = TypeParam::Write(
+        file, buffer + (total % kBufferLength), to_write);
 
     // Check that we didn't write more than the buffer size.
     EXPECT_GE(to_write, bytes_written);
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 0dce66d..9043651 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -132,6 +132,7 @@
         'file_mode_string_to_flags_test.cc',
         'file_open_test.cc',
         'file_read_test.cc',
+        'file_read_write_all_test.cc',
         'file_seek_test.cc',
         'file_truncate_test.cc',
         'file_write_test.cc',
diff --git a/src/starboard/shared/posix/storage_write_record.cc b/src/starboard/shared/posix/storage_write_record.cc
index 6dfcb4d0..dfa8fa4 100644
--- a/src/starboard/shared/posix/storage_write_record.cc
+++ b/src/starboard/shared/posix/storage_write_record.cc
@@ -69,16 +69,17 @@
   SbFileFlush(temp_file);
 
   if (SbFileIsValid(record->file) && !SbFileClose(record->file)) {
+    SbFileClose(temp_file);
+    SbFileDelete(temp_file_path);
     return false;
   }
 
   record->file = kSbFileInvalid;
 
-  if (!SbFileDelete(original_file_path)) {
-    return false;
-  }
-
-  if (rename(temp_file_path, original_file_path) != 0) {
+  if ((!SbFileDelete(original_file_path)) ||
+   (rename(temp_file_path, original_file_path) != 0)) {
+    SbFileClose(temp_file);
+    SbFileDelete(temp_file_path);
     return false;
   }
 
diff --git a/src/starboard/shared/pulse/pulse_audio_sink_type.cc b/src/starboard/shared/pulse/pulse_audio_sink_type.cc
index 38e6a8c..0f68a98 100644
--- a/src/starboard/shared/pulse/pulse_audio_sink_type.cc
+++ b/src/starboard/shared/pulse/pulse_audio_sink_type.cc
@@ -30,6 +30,19 @@
 #include "starboard/thread.h"
 #include "starboard/time.h"
 
+#if defined(ADDRESS_SANITIZER)
+// By default, Leak Sanitizer and Address Sanitizer is expected to exist
+// together. However, this is not true for all platforms.
+// HAS_LEAK_SANTIZIER=0 explicitly removes the Leak Sanitizer from code.
+#ifndef HAS_LEAK_SANITIZER
+#define HAS_LEAK_SANITIZER 1
+#endif  // HAS_LEAK_SANITIZER
+#endif  // defined(ADDRESS_SANITIZER)
+
+#if HAS_LEAK_SANITIZER
+#include <sanitizer/lsan_interface.h>
+#endif  // HAS_LEAK_SANITIZER
+
 namespace starboard {
 namespace shared {
 namespace pulse {
@@ -406,7 +419,13 @@
     return false;
   }
   // Create pulse context.
+#if HAS_LEAK_SANITIZER
+  __lsan_disable();
+#endif
   context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "pulse_audio");
+#if HAS_LEAK_SANITIZER
+  __lsan_enable();
+#endif
   if (!context_) {
     SB_LOG(WARNING) << "Pulse audio error: cannot create context.";
     return false;
diff --git a/src/starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc b/src/starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc
index 0ee50f5..d9d2909 100644
--- a/src/starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc
+++ b/src/starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc
@@ -28,7 +28,7 @@
   // Use define forwarded from GYP variable.
   return COBALT_MEDIA_BUFFER_ALLOCATION_UNIT;
 #else   // defined(COBALT_MEDIA_BUFFER_ALLOCATION_UNIT
-  return 1 * 1024 * 1024;
+  return 4 * 1024 * 1024;
 #endif  // defined(COBALT_MEDIA_BUFFER_ALLOCATION_UNIT
 }
 #endif  // SB_API_VERSION >= 10
diff --git a/src/starboard/shared/starboard/media/media_get_max_buffer_capacity.cc b/src/starboard/shared/starboard/media/media_get_max_buffer_capacity.cc
index b0f1415..1410b43 100644
--- a/src/starboard/shared/starboard/media/media_get_max_buffer_capacity.cc
+++ b/src/starboard/shared/starboard/media/media_get_max_buffer_capacity.cc
@@ -18,8 +18,8 @@
 
 #if SB_API_VERSION >= 10
 // These are the legacy default values of the GYP variables.
-#define LEGACY_MAX_CAPACITY_1080P 36 * 1024 * 1024
-#define LEGACY_MAX_CAPACITY_4K 65 * 1024 * 1024
+#define LEGACY_MAX_CAPACITY_1080P 50 * 1024 * 1024
+#define LEGACY_MAX_CAPACITY_4K 140 * 1024 * 1024
 
 int SbMediaGetMaxBufferCapacity(SbMediaVideoCodec codec,
                                 int resolution_width,
diff --git a/src/starboard/shared/starboard/media/media_get_video_buffer_budget.cc b/src/starboard/shared/starboard/media/media_get_video_buffer_budget.cc
index 05e0b5a..bb8352e 100644
--- a/src/starboard/shared/starboard/media/media_get_video_buffer_budget.cc
+++ b/src/starboard/shared/starboard/media/media_get_video_buffer_budget.cc
@@ -18,8 +18,8 @@
 
 #if SB_API_VERSION >= 10
 // These are the legacy default values of the GYP variables.
-#define LEGACY_VIDEO_BUDGET_1080P 16 * 1024 * 1024
-#define LEGACY_VIDEO_BUDGET_4K 60 * 1024 * 1024
+#define LEGACY_VIDEO_BUDGET_1080P 30 * 1024 * 1024
+#define LEGACY_VIDEO_BUDGET_4K 100 * 1024 * 1024
 
 int SbMediaGetVideoBufferBudget(SbMediaVideoCodec codec,
                                 int resolution_width,
diff --git a/src/starboard/shared/starboard/player/video_dmp_reader.cc b/src/starboard/shared/starboard/player/video_dmp_reader.cc
index 250a0f7..4483cc6 100644
--- a/src/starboard/shared/starboard/player/video_dmp_reader.cc
+++ b/src/starboard/shared/starboard/player/video_dmp_reader.cc
@@ -103,7 +103,7 @@
   SB_CHECK(file_size >= 0);
 
   file_cache_.resize(file_size);
-  int bytes_read = file.Read(file_cache_.data(), file_size);
+  int bytes_read = file.ReadAll(file_cache_.data(), file_size);
   SB_CHECK(bytes_read == file_size);
 
   Parse();
diff --git a/src/starboard/shared/widevine/widevine3.gyp b/src/starboard/shared/widevine/widevine3.gyp
index 3575419..fe8b7cf 100644
--- a/src/starboard/shared/widevine/widevine3.gyp
+++ b/src/starboard/shared/widevine/widevine3.gyp
@@ -34,6 +34,7 @@
     'platform_oem_sources': [
       '<(DEPTH)/starboard/keyboxes/<(sb_widevine_platform)/<(sb_widevine_platform).h',
       '<(DEPTH)/starboard/keyboxes/<(sb_widevine_platform)/<(sb_widevine_platform)_client.c',
+      '<(DEPTH)/starboard/shared/widevine/widevine_keybox_hash.cc',
       '<(DEPTH)/starboard/shared/widevine/wv_keybox.cc',
     ],
    },
diff --git a/src/starboard/shared/widevine/widevine_keybox_hash.cc b/src/starboard/shared/widevine/widevine_keybox_hash.cc
new file mode 100644
index 0000000..281b4b8
--- /dev/null
+++ b/src/starboard/shared/widevine/widevine_keybox_hash.cc
@@ -0,0 +1,48 @@
+// 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.
+
+#include "starboard/shared/widevine/widevine_keybox_hash.h"
+
+#include <sstream>
+
+#include "starboard/common/murmurhash2.h"
+#include "third_party/ce_cdm/oemcrypto/mock/src/wv_keybox.h"
+
+namespace wvoec_mock {
+namespace {
+#if defined(COBALT_WIDEVINE_KEYBOX_INCLUDE)
+#include COBALT_WIDEVINE_KEYBOX_INCLUDE
+#else  // COBALT_WIDEVINE_KEYBOX_INCLUDE
+#error "COBALT_WIDEVINE_KEYBOX_INCLUDE is not defined."
+#endif  // COBALT_WIDEVINE_KEYBOX_INCLUDE
+}  // namespace
+}  // namespace wvoec_mock
+
+namespace starboard {
+namespace shared {
+namespace widevine {
+
+std::string GetWidevineKeyboxHash() {
+  // Note: not a cryptographic hash.
+  uint32_t value =
+      MurmurHash2_32(reinterpret_cast<const void*>(&wvoec_mock::kKeybox),
+                     sizeof(wvoec_mock::WidevineKeybox));
+  std::stringstream ss;
+  ss << value;
+  return ss.str();
+}
+
+}  // namespace widevine
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/shared/widevine/widevine_keybox_hash.h b/src/starboard/shared/widevine/widevine_keybox_hash.h
new file mode 100644
index 0000000..e9af759
--- /dev/null
+++ b/src/starboard/shared/widevine/widevine_keybox_hash.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef STARBOARD_SHARED_WIDEVINE_WIDEVINE_KEYBOX_HASH_H_
+#define STARBOARD_SHARED_WIDEVINE_WIDEVINE_KEYBOX_HASH_H_
+
+#include <string>
+
+#include "starboard/types.h"
+
+namespace starboard {
+namespace shared {
+namespace widevine {
+
+// Computes the checksum of the Widevine Keybox.
+// NOTE: this is not a cryptographic hash, but serves our purposes here.
+std::string GetWidevineKeyboxHash();
+
+}  // namespace widevine
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_WIDEVINE_WIDEVINE_KEYBOX_HASH_H_
diff --git a/src/starboard/shared/widevine/widevine_storage.cc b/src/starboard/shared/widevine/widevine_storage.cc
index 9d0d6e6..91819e7 100644
--- a/src/starboard/shared/widevine/widevine_storage.cc
+++ b/src/starboard/shared/widevine/widevine_storage.cc
@@ -16,12 +16,17 @@
 
 #include "starboard/common/log.h"
 #include "starboard/file.h"
+#include "starboard/shared/widevine/widevine_keybox_hash.h"
 #include "starboard/types.h"
 
 namespace starboard {
 namespace shared {
 namespace widevine {
 
+// Reserved key name for referring to the Widevine Keybox checksum value.
+const char WidevineStorage::kCobaltWidevineKeyboxChecksumKey[] =
+    "cobalt_widevine_keybox_checksum";
+
 namespace {
 
 void ReadFile(const std::string& path_name, std::vector<uint8_t>* content) {
@@ -121,48 +126,49 @@
   }
 
   SB_LOG(INFO) << "Loaded " << cache_.size() << " records from " << path_name_;
+
+  // Not a cryptographic hash but is sufficient for this problem space.
+  std::string keybox_checksum = GetWidevineKeyboxHash();
+  if (existsInternal(kCobaltWidevineKeyboxChecksumKey)) {
+    std::string cached_checksum;
+    readInternal(kCobaltWidevineKeyboxChecksumKey, &cached_checksum);
+    if (keybox_checksum == cached_checksum) {
+      return;
+    }
+    SB_LOG(INFO) << "Cobalt widevine keybox checksums do not match. Clearing "
+                    "to force re-provisioning.";
+    cache_.clear();
+    // Save the new keybox checksum to be used after provisioning completes.
+    writeInternal(kCobaltWidevineKeyboxChecksumKey, keybox_checksum);
+    return;
+  }
+  SB_LOG(INFO) << "Widevine checksum is not stored on disk. Writing the "
+                  "computed checksum to disk.";
+  writeInternal(kCobaltWidevineKeyboxChecksumKey, keybox_checksum);
 }
 
 bool WidevineStorage::read(const std::string& name, std::string* data) {
-  SB_DCHECK(data);
-  ScopedLock scoped_lock(lock_);
-  auto iter = cache_.find(name);
-  if (iter == cache_.end()) {
-    return false;
-  }
-  *data = iter->second;
-  return true;
+  SB_DCHECK(name != kCobaltWidevineKeyboxChecksumKey);
+  return readInternal(name, data);
 }
 
 bool WidevineStorage::write(const std::string& name, const std::string& data) {
-  ScopedLock scoped_lock(lock_);
-  cache_[name] = data;
-
-  std::vector<uint8_t> content;
-  for (auto iter : cache_) {
-    WriteString(iter.first, &content);
-    WriteString(iter.second, &content);
-  }
-
-  return WriteFile(path_name_, content);
+  SB_DCHECK(name != kCobaltWidevineKeyboxChecksumKey);
+  return writeInternal(name, data);
 }
 
 bool WidevineStorage::exists(const std::string& name) {
-  ScopedLock scoped_lock(lock_);
-  return cache_.find(name) != cache_.end();
+  SB_DCHECK(name != kCobaltWidevineKeyboxChecksumKey);
+  return existsInternal(name);
 }
 
 bool WidevineStorage::remove(const std::string& name) {
-  ScopedLock scoped_lock(lock_);
-  auto iter = cache_.find(name);
-  if (iter == cache_.end()) {
-    return false;
-  }
-  cache_.erase(iter);
-  return true;
+  SB_DCHECK(name != kCobaltWidevineKeyboxChecksumKey);
+  return removeInternal(name);
 }
 
 int32_t WidevineStorage::size(const std::string& name) {
+  SB_DCHECK(name != kCobaltWidevineKeyboxChecksumKey);
   ScopedLock scoped_lock(lock_);
   auto iter = cache_.find(name);
   return iter == cache_.end() ? -1 : static_cast<int32_t>(iter->second.size());
@@ -173,11 +179,54 @@
   ScopedLock scoped_lock(lock_);
   records->clear();
   for (auto item : cache_) {
+    if (item.first == kCobaltWidevineKeyboxChecksumKey) {
+      continue;
+    }
     records->push_back(item.first);
   }
   return !records->empty();
 }
 
+bool WidevineStorage::readInternal(const std::string& name,
+                                   std::string* data) const {
+  SB_DCHECK(data);
+  ScopedLock scoped_lock(lock_);
+  auto iter = cache_.find(name);
+  if (iter == cache_.end()) {
+    return false;
+  }
+  *data = iter->second;
+  return true;
+}
+
+bool WidevineStorage::writeInternal(const std::string& name,
+                                    const std::string& data) {
+  ScopedLock scoped_lock(lock_);
+  cache_[name] = data;
+
+  std::vector<uint8_t> content;
+  for (auto iter : cache_) {
+    WriteString(iter.first, &content);
+    WriteString(iter.second, &content);
+  }
+  return WriteFile(path_name_, content);
+}
+
+bool WidevineStorage::existsInternal(const std::string& name) const {
+  ScopedLock scoped_lock(lock_);
+  return cache_.find(name) != cache_.end();
+}
+
+bool WidevineStorage::removeInternal(const std::string& name) {
+  ScopedLock scoped_lock(lock_);
+  auto iter = cache_.find(name);
+  if (iter == cache_.end()) {
+    return false;
+  }
+  cache_.erase(iter);
+  return true;
+}
+
 }  // namespace widevine
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/shared/widevine/widevine_storage.h b/src/starboard/shared/widevine/widevine_storage.h
index 218b118..5ee9ba8 100644
--- a/src/starboard/shared/widevine/widevine_storage.h
+++ b/src/starboard/shared/widevine/widevine_storage.h
@@ -30,8 +30,13 @@
 // Widevine to store persistent data like device provisioning.
 class WidevineStorage : public ::widevine::Cdm::IStorage {
  public:
+  // This key is restricted to internal use only.
+  static const char kCobaltWidevineKeyboxChecksumKey[];
+
   explicit WidevineStorage(const std::string& path_name);
 
+  // For these accessor methods the |name| field cannot be
+  // |kCobaltWidevineKeyboxChecksumKey|.
   bool read(const std::string& name, std::string* data) override;
   bool write(const std::string& name, const std::string& data) override;
   bool exists(const std::string& name) override;
@@ -44,6 +49,11 @@
   bool list(std::vector<std::string>* records) override;
 
  private:
+  bool readInternal(const std::string& name, std::string* data) const;
+  bool writeInternal(const std::string& name, const std::string& data);
+  bool existsInternal(const std::string& name) const;
+  bool removeInternal(const std::string& name);
+
   Mutex lock_;
   std::string path_name_;
   std::map<std::string, std::string> cache_;
diff --git a/src/starboard/tools/abstract_launcher.py b/src/starboard/tools/abstract_launcher.py
index 30fbeb4..78394e8 100644
--- a/src/starboard/tools/abstract_launcher.py
+++ b/src/starboard/tools/abstract_launcher.py
@@ -166,6 +166,23 @@
 
     raise RuntimeError("Suspend not supported for this platform.")
 
+  def SupportsDeepLink(self):
+    return False
+
+  def SendDeepLink(self, link):
+    """Sends deep link to the launcher's executable.
+
+    Args:
+      link:  Link to send to the executable.
+
+    Raises:
+      RuntimeError: Deep link not supported on platform.
+    """
+
+    raise RuntimeError(
+        "Deep link not supported for this platform (link {} sent).".format(
+            link))
+
   def GetStartupTimeout(self):
     """Gets the number of seconds to wait before assuming a launcher timeout."""
 
diff --git a/src/starboard/tools/send_link.py b/src/starboard/tools/send_link.py
index 74d47f4..10a5a5d 100755
--- a/src/starboard/tools/send_link.py
+++ b/src/starboard/tools/send_link.py
@@ -34,6 +34,7 @@
 import sys
 import tempfile
 import textwrap
+import time
 
 
 def _Uncase(text):
@@ -76,7 +77,20 @@
   return None
 
 
-def _SendLink(executable, link):
+def _ConnectWithRetry(s, port, num_attempts):
+  for attempt in range(num_attempts):
+    if attempt > 0:
+      time.sleep(1)
+    try:
+      s.connect(('localhost', port))
+      return True
+    except (RuntimeError, IOError):
+      logging.error('Could not connect to port %d, attempt %d / %d', port,
+                    attempt, num_attempts)
+  return False
+
+
+def SendLink(executable, link, connection_attempts=1):
   """Sends a link to the process starting with the given executable name."""
 
   pids = _GetPids(executable)
@@ -109,7 +123,9 @@
 
   try:
     with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
-      s.connect(('localhost', port))
+      if not _ConnectWithRetry(s, port, connection_attempts):
+        logging.exception('Could not connect to port: %d', port)
+        return 1
       terminated_link = link + '\x00'
       bytes_sent = 0
       while bytes_sent < len(terminated_link):
@@ -138,7 +154,7 @@
   parser.add_argument(
       'link', type=str, help='The link content to send to the executable.')
   arguments = parser.parse_args()
-  return _SendLink(arguments.executable, arguments.link)
+  return SendLink(arguments.executable, arguments.link)
 
 
 if __name__ == '__main__':
diff --git a/src/third_party/icu/source/common/umapfile.c b/src/third_party/icu/source/common/umapfile.c
index 96c198c..b40dd6c 100644
--- a/src/third_party/icu/source/common/umapfile.c
+++ b/src/third_party/icu/source/common/umapfile.c
@@ -318,7 +318,7 @@
         }
 
         /* read the file */
-        if (fileLength != SbFileRead(file, p, fileLength)) {
+        if (fileLength != SbFileReadAll(file, p, fileLength)) {
             uprv_free(p);
             SbFileClose(file);
             return FALSE;
diff --git a/src/third_party/libvpx/vp9/common/vp9_loopfilter.c b/src/third_party/libvpx/vp9/common/vp9_loopfilter.c
index 183dec4..0d48194 100644
--- a/src/third_party/libvpx/vp9/common/vp9_loopfilter.c
+++ b/src/third_party/libvpx/vp9/common/vp9_loopfilter.c
@@ -1213,7 +1213,7 @@
     }
 
     // Disable filtering on the leftmost column
-    border_mask = ~(mi_col == 0);
+    border_mask = ~(mi_col == 0 ? 1 : 0);
 #if CONFIG_VP9_HIGHBITDEPTH
     if (cm->use_highbitdepth) {
       highbd_filter_selectively_vert(CONVERT_TO_SHORTPTR(dst->buf),