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),