Import Cobalt 20.master.0.239287
Includes the following patches:
https://cobalt-review.googlesource.com/c/cobalt/+/5590
by n1214.hwang@samsung.com
https://cobalt-review.googlesource.com/c/cobalt/+/5530
by errong.leng@samsung.com
diff --git a/src/base/base.gyp b/src/base/base.gyp
index 5eccf13..e442340 100644
--- a/src/base/base.gyp
+++ b/src/base/base.gyp
@@ -692,24 +692,6 @@
'sources': [
],
},
-
-
- # Include this target for a main() function that simply instantiates
- # and runs a base::TestSuite.
- {
- 'target_name': 'run_all_unittests',
- 'type': 'static_library',
- 'dependencies': [
- 'test_support_base',
- ],
- 'sources': [
- 'test/run_all_unittests.cc',
- ],
- 'dependencies': [
- '<(DEPTH)/testing/gmock.gyp:gmock',
- '<(DEPTH)/testing/gtest.gyp:gtest',
- ],
- },
{
'target_name': 'base_unittests',
'type': '<(gtest_target_type)',
@@ -948,7 +930,6 @@
'base',
'base_i18n',
'base_static',
- 'run_all_unittests',
'test_support_base',
'third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
],
@@ -969,6 +950,7 @@
# Destructor is explicitly deleted.
4624,
],
+ 'includes': ['<(DEPTH)/base/test/test.gypi'],
},
{
'target_name': 'test_support_base',
diff --git a/src/base/files/file_enumerator_starboard.cc b/src/base/files/file_enumerator_starboard.cc
index 1c2b3f0..70e9590 100644
--- a/src/base/files/file_enumerator_starboard.cc
+++ b/src/base/files/file_enumerator_starboard.cc
@@ -107,10 +107,23 @@
};
std::vector<FileEnumerator::FileInfo> ret;
- SbDirectoryEntry entry;
// We test if SbDirectoryGetNext returns parent directory file descriptor(..)
// because the definition of SbDirectoryGetNext does not guarantee that.
bool found_dot_dot = false;
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME);
+
+ while (SbDirectoryGetNext(dir, entry.data(), entry.size())) {
+ const char dot_dot_str[] = "..";
+ if (!SbStringCompare(entry.data(), dot_dot_str, sizeof(dot_dot_str))) {
+ found_dot_dot = true;
+ }
+ ret.push_back(GenerateEntry(entry.data()));
+ }
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ SbDirectoryEntry entry;
+
while (SbDirectoryGetNext(dir, &entry)) {
const char dot_dot_str[] = "..";
if (!SbStringCompare(entry.name, dot_dot_str, sizeof(dot_dot_str))) {
@@ -118,6 +131,8 @@
}
ret.push_back(GenerateEntry(entry.name));
}
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
if (!found_dot_dot) {
ret.push_back(GenerateEntry(".."));
}
diff --git a/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc b/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
index 02454ed..da61163 100644
--- a/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/src/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -50,7 +50,7 @@
"TaskScheduler.NumTasksBetweenWaits.";
constexpr char kNumThreadsHistogramPrefix[] = "TaskScheduler.NumWorkers.";
#ifdef STARBOARD
-constexpr size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
#else
constexpr size_t kMaxNumberOfWorkers = 256;
#endif
diff --git a/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
index 0fad634..816415f 100644
--- a/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
+++ b/src/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -1415,7 +1415,7 @@
// leaves the pool in a valid state with regards to max tasks.
TEST_F(TaskSchedulerWorkerPoolBlockingTest, MaximumWorkersTest) {
#ifdef STARBOARD
- constexpr size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
+ const size_t kMaxNumberOfWorkers = SB_MAX_THREADS;
#else
constexpr size_t kMaxNumberOfWorkers = 256;
#endif
@@ -1665,7 +1665,7 @@
// test for https://crbug.com/810464.
TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, RacyCleanup) {
#ifdef STARBOARD
- constexpr size_t kLocalMaxTasks = SB_MAX_THREADS;
+ const size_t kLocalMaxTasks = SB_MAX_THREADS;
#else
#if defined(OS_FUCHSIA)
// Fuchsia + QEMU doesn't deal well with *many* threads being
diff --git a/src/base/test/test.gypi b/src/base/test/test.gypi
new file mode 100644
index 0000000..c99d14d
--- /dev/null
+++ b/src/base/test/test.gypi
@@ -0,0 +1,16 @@
+# Copyright (c) 2012 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 this file for a main() function that simply instantiates and runs a
+# base::TestSuite.
+{
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:test_support_base',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ '<(DEPTH)/base/test/run_all_unittests.cc',
+ ],
+}
diff --git a/src/base/time/time_unittest.cc b/src/base/time/time_unittest.cc
index 87d4cc5..a8a4a58 100644
--- a/src/base/time/time_unittest.cc
+++ b/src/base/time/time_unittest.cc
@@ -1000,14 +1000,22 @@
// static
ThreadTicks ThreadTicksOverride::now_ticks_;
-#if SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION || \
+ SB_HAS(TIME_THREAD_NOW)
// IOS doesn't support ThreadTicks::Now().
-#if defined(OS_IOS) || SB_HAS(TIME_THREAD_NOW)
+#if defined(OS_IOS)
#define MAYBE_NowOverride DISABLED_NowOverride
#else
#define MAYBE_NowOverride NowOverride
#endif
TEST(ThreadTicks, MAYBE_NowOverride) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ if (!SbTimeIsTimeThreadNowSupported()) {
+ SB_LOG(INFO) << "Time thread now not supported. Test skipped.";
+ return;
+ }
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
ThreadTicksOverride::now_ticks_ = ThreadTicks::Min();
// Override is not active. All Now() methods should return a sensible value.
@@ -1043,7 +1051,8 @@
EXPECT_LE(initial_thread_ticks, subtle::ThreadTicksNowIgnoringOverride());
EXPECT_GT(ThreadTicks::Max(), subtle::ThreadTicksNowIgnoringOverride());
}
-#endif // SB_HAS(TIME_THREAD_NOW)
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION ||
+ // SB_HAS(TIME_THREAD_NOW)
TEST(ThreadTicks, ThreadNow) {
if (ThreadTicks::IsSupported()) {
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 06fe9c7..cce7b0f 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -6,16 +6,28 @@
- **DevTools and WebDriver listen to ANY interface, except on Linux.**
- DevTools and WebDriver servers listen to connections on any network interface
- by default, except on Linux where they listen only to loopback (localhost) by
- default. A new "--dev_servers_listen_ip" command line parameter can be used to
- specify a different interface for both of them to listen to.
+ DevTools and WebDriver servers listen to connections on any network interface
+ by default, except on Linux where they listen only to loopback (localhost) by
+ default. A new "--dev_servers_listen_ip" command line parameter can be used
+ to specify a different interface for both of them to listen to.
- **DevTools shows asynchronous stack traces.**
- When stopped at a breakpoint within the handler function for an asynchronous
- operation, the call stack in DevTools now shows both the current function as
- well as the function where the asynchronous operation was initiated.
+ When stopped at a breakpoint within the handler function for an asynchronous
+ operation, the call stack in DevTools now shows both the current function as
+ well as the function where the asynchronous operation was initiated.
+
+ - **Optimized network buffer management and notification handling.**
+
+ Reduced unnecessary buffer copying during network downloading which results
+ in the reduction of CPU usage on both the NetworkModule thread and the
+ MainWebModule thread. Peak memory usage during downloading is also reduced.
+ Also reduced redundant notifications from the NetworkModule thread to the
+ MainWebModule thread on downloading progresses.
+ CPU utilization of both threads is reduced by more than 10% with the above
+ optimizations on some less powerful platforms during high bitrate content
+ playback. The lower CPU utilization of the MainWebModule thread allows it to
+ process other tasks (like Javascript execution) more responsively.
## Version 20
diff --git a/src/cobalt/audio/audio_test.gyp b/src/cobalt/audio/audio_test.gyp
index c354a85..c4e3979 100644
--- a/src/cobalt/audio/audio_test.gyp
+++ b/src/cobalt/audio/audio_test.gyp
@@ -26,7 +26,6 @@
'dependencies': [
'<(DEPTH)/cobalt/dom/dom.gyp:dom',
'<(DEPTH)/cobalt/media/media.gyp:media',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
@@ -34,6 +33,7 @@
# ScriptValueFactory has non-virtual method CreatePromise().
'<(DEPTH)/cobalt/script/engine.gyp:engine',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index a34c59f..f849ff4 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -111,10 +111,10 @@
],
'dependencies': [
'base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'base_test_deploy',
diff --git a/src/cobalt/bindings/testing/testing.gyp b/src/cobalt/bindings/testing/testing.gyp
index cfa6c3d..2898f07 100644
--- a/src/cobalt/bindings/testing/testing.gyp
+++ b/src/cobalt/bindings/testing/testing.gyp
@@ -187,12 +187,12 @@
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/script/engine.gyp:engine',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'bindings',
'bindings_test_implementation',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/black_box_tests/README.md b/src/cobalt/black_box_tests/README.md
index d830906..6a696ea 100644
--- a/src/cobalt/black_box_tests/README.md
+++ b/src/cobalt/black_box_tests/README.md
@@ -84,6 +84,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 c209f91..bfc8ce5 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.
@@ -108,10 +112,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:
@@ -123,8 +130,12 @@
class BlackBoxTests(object):
"""Helper class to run all black box tests and return results."""
- def __init__(self, server_binding_address, proxy_address=None,
- proxy_port=None, test_name=None, wpt_http_port=None):
+ def __init__(self,
+ server_binding_address,
+ proxy_address=None,
+ proxy_port=None,
+ test_name=None,
+ wpt_http_port=None):
logging.basicConfig(level=logging.DEBUG)
# Setup global variables used by test cases
@@ -159,22 +170,21 @@
# Test domains used in web platform tests to be resolved to the server
# binding address.
hosts = [
- 'web-platform.test',
- 'www.web-platform.test',
- 'www1.web-platform.test',
- 'www2.web-platform.test',
- 'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
+ 'web-platform.test', 'www.web-platform.test', 'www1.web-platform.test',
+ 'www2.web-platform.test', 'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
'xn--lve-6lad.web-platform.test'
]
- self.host_resolve_map = dict([(host, server_binding_address) for host in hosts])
+ self.host_resolve_map = dict([
+ (host, server_binding_address) for host in hosts
+ ])
def Run(self):
if self.proxy_port == '-1':
return 1
logging.info('Using proxy port: %s', self.proxy_port)
- with ProxyServer(port=self.proxy_port,
- host_resolve_map=self.host_resolve_map):
+ with ProxyServer(
+ port=self.proxy_port, host_resolve_map=self.host_resolve_map):
if self.test_name:
suite = unittest.TestLoader().loadTestsFromModule(
importlib.import_module(_TEST_DIR_PATH + self.test_name))
@@ -199,7 +209,8 @@
socks.append((address, socket.socket(socket.AF_INET, socket.SOCK_STREAM)))
try:
for _ in range(_PORT_SELECTION_RETRY_LIMIT):
- port = random.randint(_PORT_SELECTION_RANGE[0], _PORT_SELECTION_RANGE[1])
+ port = random.randint(_PORT_SELECTION_RANGE[0],
+ _PORT_SELECTION_RANGE[1])
unused = True
for sock in socks:
result = sock[1].connect_ex((sock[0], port))
@@ -208,9 +219,8 @@
break
if unused:
return port
- logging.error(
- 'Can not find unused port on addresses within %s attempts.' %
- _PORT_SELECTION_RETRY_LIMIT)
+ logging.error('Can not find unused port on addresses within %s attempts.',
+ _PORT_SELECTION_RETRY_LIMIT)
return -1
finally:
for sock in socks:
@@ -219,28 +229,33 @@
def main():
parser = argparse.ArgumentParser()
- parser.add_argument('--server_binding_address',
- default='127.0.0.1',
- help='Binding address used to create the test server.')
- parser.add_argument('--proxy_address',
- default=None,
- help=('Address to the proxy server that all black box'
- 'tests are run through. If not specified, the'
- 'server binding address is used.'))
- parser.add_argument('--proxy_port',
- default=None,
- help=('Port used to create the proxy server that all'
- 'black box tests are run through. If not'
- 'specified, a random free port is used.'))
- parser.add_argument('--test_name',
- default=None,
- help=('Name of test to be run. If not specified, all '
- 'tests are run.'))
- parser.add_argument('--wpt_http_port',
- default=None,
- help=('Port used to create the web platform test http'
- 'server. If not specified, a random free port is'
- 'used.'))
+ parser.add_argument(
+ '--server_binding_address',
+ default='127.0.0.1',
+ help='Binding address used to create the test server.')
+ parser.add_argument(
+ '--proxy_address',
+ default=None,
+ help=('Address to the proxy server that all black box'
+ 'tests are run through. If not specified, the'
+ 'server binding address is used.'))
+ parser.add_argument(
+ '--proxy_port',
+ default=None,
+ help=('Port used to create the proxy server that all'
+ 'black box tests are run through. If not'
+ 'specified, a random free port is used.'))
+ parser.add_argument(
+ '--test_name',
+ default=None,
+ help=('Name of test to be run. If not specified, all '
+ 'tests are run.'))
+ parser.add_argument(
+ '--wpt_http_port',
+ default=None,
+ help=('Port used to create the web platform test http'
+ 'server. If not specified, a random free port is'
+ 'used.'))
args, _ = parser.parse_known_args()
test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
diff --git a/src/cobalt/black_box_tests/proxy_server.py b/src/cobalt/black_box_tests/proxy_server.py
index ee29a39..df6c03b 100644
--- a/src/cobalt/black_box_tests/proxy_server.py
+++ b/src/cobalt/black_box_tests/proxy_server.py
@@ -32,7 +32,7 @@
class ProxyServer(object):
- def __init__(self, hostname='0.0.0.0', port='8000', host_resolve_map=None):
+ def __init__(self, hostname='127.0.0.1', port='8000', host_resolve_map=None):
self.command = [
'python',
os.path.join(SRC_DIR, 'third_party', 'proxy_py', 'proxy.py'),
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 9b76975..2684cb9 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -678,12 +678,18 @@
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,
(should_preload ? base::kApplicationStatePreloading
: base::kApplicationStateStarted),
&event_dispatcher_, account_manager_.get(), options));
+#if SB_IS(EVERGREEN)
+ updater_module_.reset(new updater::UpdaterModule(
+ message_loop_, browser_module_->GetNetworkModule()));
+#endif
UpdateUserAgent();
app_status_ = (should_preload ? kPreloadingAppStatus : kRunningAppStatus);
@@ -785,6 +791,11 @@
base::TimeDelta::FromSeconds(duration_in_seconds));
}
#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+#if SB_IS(EVERGREEN)
+ // Run the first update check after the application is started.
+ updater_module_->Update();
+#endif
}
Application::~Application() {
@@ -861,6 +872,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) {
@@ -916,7 +928,13 @@
// 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:
@@ -1201,5 +1219,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 db55875..731f42f 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -28,6 +28,9 @@
#include "cobalt/browser/browser_module.h"
#include "cobalt/browser/memory_tracker/tool.h"
#include "cobalt/system_window/system_window.h"
+#if SB_IS(EVERGREEN)
+#include "cobalt/updater/updater_module.h"
+#endif
#include "starboard/event.h"
#if defined(ENABLE_WEBDRIVER)
@@ -101,6 +104,11 @@
// Main components of the Cobalt browser application.
std::unique_ptr<BrowserModule> browser_module_;
+#if SB_IS(EVERGREEN)
+ // Cobalt Updater.
+ std::unique_ptr<updater::UpdaterModule> updater_module_;
+#endif
+
// Event callbacks.
base::EventCallback network_event_callback_;
base::EventCallback deep_link_event_callback_;
@@ -212,6 +220,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.gyp b/src/cobalt/browser/browser.gyp
index 5ac69e0..666c6e9 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -15,6 +15,7 @@
{
'variables': {
'sb_pedantic_warnings': 1,
+ 'has_updater%' : '<!(python ../../build/file_exists.py <(DEPTH)/cobalt/updater/updater.gyp)',
},
'targets': [
{
@@ -213,6 +214,11 @@
'COBALT_MESH_CACHE_SIZE_IN_BYTES=<(mesh_cache_size_in_bytes)',
],
}],
+ ['sb_evergreen == 1 and has_updater == "True"', {
+ 'dependencies': [
+ '<(DEPTH)/cobalt/updater/updater.gyp:updater',
+ ],
+ }],
],
},
@@ -241,11 +247,11 @@
'<(DEPTH)/cobalt/speech/speech.gyp:speech',
'<(DEPTH)/cobalt/storage/storage.gyp:storage',
'<(DEPTH)/cobalt/storage/storage.gyp:storage_upgrade_copy_test_data',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'browser',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 29cbc9d..beb8e78 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -232,6 +232,7 @@
'../audio/audio_node_channel_count_mode.idl',
'../audio/audio_node_channel_interpretation.idl',
'../debug/console/console_command.idl',
+ '../debug/console/debug_console_mode.idl',
'../dom/blob_property_bag.idl',
'../dom/captions/caption_character_edge_style.idl',
'../dom/captions/caption_color.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 6052b23..9b4b07b 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -310,7 +310,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.
@@ -345,7 +346,18 @@
#if defined(ENABLE_DEBUGGER)
debug_console_layer_ = render_tree_combiner_.CreateLayer(kDebugConsoleZIndex);
#endif
- if (command_line->HasSwitch(browser::switches::kQrCodeOverlay)) {
+
+ int qr_code_overlay_slots = 4;
+ if (command_line->HasSwitch(switches::kQrCodeOverlay)) {
+ auto slots_in_string =
+ command_line->GetSwitchValueASCII(switches::kQrCodeOverlay);
+ if (!slots_in_string.empty()) {
+ auto result = base::StringToInt(slots_in_string, &qr_code_overlay_slots);
+ DCHECK(result) << "Failed to convert value of --"
+ << switches::kQrCodeOverlay << ": "
+ << qr_code_overlay_slots << " to int.";
+ DCHECK_GT(qr_code_overlay_slots, 0);
+ }
qr_overlay_info_layer_ =
render_tree_combiner_.CreateLayer(kOverlayInfoZIndex);
} else {
@@ -430,7 +442,7 @@
if (qr_overlay_info_layer_) {
math::Size width_height = GetViewportSize().width_height();
qr_code_overlay_.reset(new overlay_info::QrCodeOverlay(
- width_height, GetResourceProvider(),
+ width_height, qr_code_overlay_slots, GetResourceProvider(),
base::Bind(&BrowserModule::QueueOnQrCodeOverlayRenderTreeProduced,
base::Unretained(this))));
}
@@ -680,7 +692,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 b44418a..f4f9aa5 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
@@ -119,6 +120,7 @@
const Options& options);
~BrowserModule();
+ network::NetworkModule* GetNetworkModule() { return &network_module_; }
std::string GetUserAgent() { return network_module_.GetUserAgent(); }
// Recreates web module with the given URL. In the case where Cobalt is
@@ -210,6 +212,8 @@
const base::AccessibilityCaptionSettingsChangedEvent* event);
#endif // SB_API_VERSION >= SB_CAPTIONS_REQUIRED_VERSION || 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);
@@ -666,6 +670,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/cobalt.gyp b/src/cobalt/browser/cobalt.gyp
index 8320f9b..471e9ec 100644
--- a/src/cobalt/browser/cobalt.gyp
+++ b/src/cobalt/browser/cobalt.gyp
@@ -15,7 +15,6 @@
{
'variables': {
'sb_pedantic_warnings': 1,
- 'has_updater%' : '<!(python ../../build/file_exists.py <(DEPTH)/cobalt/updater/updater.gyp)',
},
'targets': [
{
@@ -34,11 +33,6 @@
'<(DEPTH)/cobalt/browser/splash_screen/splash_screen.gyp:copy_splash_screen',
],
}],
- ['sb_evergreen == 1 and has_updater == "True"', {
- 'dependencies': [
- '<(DEPTH)/cobalt/updater/updater.gyp:updater',
- ],
- }],
],
},
{
@@ -91,7 +85,7 @@
},
]
}],
- ['final_executable_type == "shared_library"', {
+ ['final_executable_type == "shared_library" and sb_evergreen != 1', {
'targets': [
{
'target_name': 'cobalt_bin',
diff --git a/src/cobalt/browser/debug_console.cc b/src/cobalt/browser/debug_console.cc
index 56223d9..36b5caa 100644
--- a/src/cobalt/browser/debug_console.cc
+++ b/src/cobalt/browser/debug_console.cc
@@ -33,9 +33,11 @@
const char kInitialDebugConsoleUrl[] =
"file:///cobalt/debug/console/debug_console.html";
-const char kDebugConsoleOffString[] = "off";
-const char kDebugConsoleOnString[] = "on";
-const char kDebugConsoleHudString[] = "hud";
+const char kDebugConsoleModeOffString[] = "off";
+const char kDebugConsoleModeHudString[] = "hud";
+const char kDebugConsoleModeDebugString[] = "debug";
+const char kDebugConsoleModeDebugStringAlias[] = "on"; // Legacy name of mode.
+const char kDebugConsoleModeMediaString[] = "media";
// Convert from a debug console visibility setting string to an integer
// value specified by a constant defined in debug::console::DebugHub.
@@ -44,12 +46,16 @@
// Static casting is necessary in order to get around what appears to be a
// compiler error on Linux when implicitly constructing a base::Optional<int>
// from a static const int.
- if (mode_string == kDebugConsoleOffString) {
- return static_cast<int>(debug::console::DebugHub::kDebugConsoleOff);
- } else if (mode_string == kDebugConsoleHudString) {
- return static_cast<int>(debug::console::DebugHub::kDebugConsoleHud);
- } else if (mode_string == kDebugConsoleOnString) {
- return static_cast<int>(debug::console::DebugHub::kDebugConsoleOn);
+ if (mode_string == kDebugConsoleModeOffString) {
+ return static_cast<int>(debug::console::kDebugConsoleModeOff);
+ } else if (mode_string == kDebugConsoleModeHudString) {
+ return static_cast<int>(debug::console::kDebugConsoleModeHud);
+ } else if (mode_string == kDebugConsoleModeDebugString) {
+ return static_cast<int>(debug::console::kDebugConsoleModeDebug);
+ } else if (mode_string == kDebugConsoleModeDebugStringAlias) {
+ return static_cast<int>(debug::console::kDebugConsoleModeDebug);
+ } else if (mode_string == kDebugConsoleModeMediaString) {
+ return static_cast<int>(debug::console::kDebugConsoleModeMedia);
} else {
DLOG(WARNING) << "Debug console mode \"" << mode_string
<< "\" not recognized.";
@@ -82,7 +88,7 @@
}
// By default the debug console is off.
- return debug::console::DebugHub::kDebugConsoleOff;
+ return debug::console::kDebugConsoleModeOff;
}
// A function to create a DebugHub object, to be injected into WebModule.
@@ -147,8 +153,8 @@
bool DebugConsole::ShouldInjectInputEvents() {
switch (GetMode()) {
- case debug::console::DebugHub::kDebugConsoleOff:
- case debug::console::DebugHub::kDebugConsoleHud:
+ case debug::console::kDebugConsoleModeOff:
+ case debug::console::kDebugConsoleModeHud:
return false;
default:
return true;
@@ -197,12 +203,12 @@
void DebugConsole::CycleMode() {
base::AutoLock lock(mode_mutex_);
- mode_ = (mode_ + 1) % debug::console::DebugHub::kDebugConsoleNumModes;
+ mode_ = (mode_ + 1) % debug::console::kDebugConsoleModeCount;
}
-int DebugConsole::GetMode() {
+debug::console::DebugConsoleMode DebugConsole::GetMode() {
base::AutoLock lock(mode_mutex_);
- return mode_;
+ return static_cast<debug::console::DebugConsoleMode>(mode_);
}
} // namespace browser
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index bb1fe25..f368fc5 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -27,6 +27,7 @@
#include "cobalt/base/token.h"
#include "cobalt/browser/lifecycle_observer.h"
#include "cobalt/browser/web_module.h"
+#include "cobalt/debug/console/debug_console_mode.h"
#include "cobalt/debug/console/debug_hub.h"
#include "cobalt/dom/input_event_init.h"
#include "cobalt/dom/keyboard_event_init.h"
@@ -86,7 +87,7 @@
// Returns true iff the console is in a mode that is visible.
bool IsVisible() {
- return (GetMode() != debug::console::DebugHub::kDebugConsoleOff);
+ return (GetMode() != debug::console::kDebugConsoleModeOff);
}
void SetSize(const cssom::ViewportSize& window_dimensions,
@@ -114,7 +115,7 @@
}
// Returns the currently set debug console visibility mode.
- int GetMode();
+ debug::console::DebugConsoleMode GetMode();
// Returns true iff the debug console is in a state where it should route
// input events to its web module.
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc
index edb849b..127556b 100644
--- a/src/cobalt/browser/switches.cc
+++ b/src/cobalt/browser/switches.cc
@@ -315,7 +315,10 @@
const char kQrCodeOverlay[] = "qr_code_overlay";
const char kQrCodeOverlayHelp[] =
"Display QrCode based overlay information. These information can be used"
- " for performance tuning or playback quality check.";
+ " for performance tuning or playback quality check. By default qr code"
+ " will be displayed in 4 different locations on the screen alternatively,"
+ " and the number of locations can be overwritten by specifying it as the "
+ " value of the command line parameter, like '--qr_code_overlay=6'.";
const char kReduceCpuMemoryBy[] = "reduce_cpu_memory_by";
const char kReduceCpuMemoryByHelp[] =
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index e0d3b20..e484055 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -99,7 +99,7 @@
'<(DEPTH)/starboard/elf_loader/elf_loader.gyp:elf_loader_test_deploy',
],
}],
- ['has_loader_app == "True"', {
+ ['has_loader_app == "True" and sb_evergreen != 1', {
'dependencies': [
'<(DEPTH)/starboard/loader_app/loader_app.gyp:*',
],
@@ -114,6 +114,7 @@
['sb_evergreen==1', {
'dependencies': [
'<(DEPTH)/third_party/musl/musl.gyp:musl_unittests',
+ '<(DEPTH)/starboard/loader_app/installation_manager.gyp:*',
],
}],
],
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index a86f8a5..e411972 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-234144
\ No newline at end of file
+239287
\ No newline at end of file
diff --git a/src/cobalt/csp/csp.gyp b/src/cobalt/csp/csp.gyp
index eaeaf4b..564888d 100644
--- a/src/cobalt/csp/csp.gyp
+++ b/src/cobalt/csp/csp.gyp
@@ -57,12 +57,12 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'csp',
'csp_copy_test_data',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp
index 3c1f6c6..d498ca5 100644
--- a/src/cobalt/css_parser/css_parser.gyp
+++ b/src/cobalt/css_parser/css_parser.gyp
@@ -132,12 +132,12 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'css_grammar',
'css_parser',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h
index f2c0b9f..557742d 100644
--- a/src/cobalt/cssom/css_computed_style_data.h
+++ b/src/cobalt/cssom/css_computed_style_data.h
@@ -23,6 +23,7 @@
#include "base/containers/small_map.h"
#include "base/memory/ref_counted.h"
#include "cobalt/base/unused.h"
+#include "cobalt/cssom/keyword_value.h"
#include "cobalt/cssom/property_definitions.h"
#include "cobalt/cssom/property_value.h"
@@ -514,6 +515,18 @@
return is_inline_before_blockification_;
}
+ bool IsContainingBlockForPositionAbsoluteElements() const {
+ return IsPositioned() || IsTransformed();
+ }
+
+ bool IsPositioned() const {
+ return position() != cssom::KeywordValue::GetStatic();
+ }
+
+ bool IsTransformed() const {
+ return transform() != cssom::KeywordValue::GetNone();
+ }
+
protected:
void SetPropertyValue(const PropertyKey key,
const scoped_refptr<PropertyValue>& value);
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp
index d08f3d8..92fb5d6 100644
--- a/src/cobalt/cssom/cssom_test.gyp
+++ b/src/cobalt/cssom/cssom_test.gyp
@@ -58,10 +58,10 @@
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
'<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/debug/backend/content/dom_agent.js b/src/cobalt/debug/backend/content/dom_agent.js
index 2dcf369..f702565 100644
--- a/src/cobalt/debug/backend/content/dom_agent.js
+++ b/src/cobalt/debug/backend/content/dom_agent.js
@@ -82,13 +82,6 @@
return JSON.stringify(result);
}
-// Returns the bounding box of a node. This pseudo-command in the DOM domain is
-// a helper for the C++ |DOMAgent::HighlightNode|.
-commands._getBoundingClientRect = function(params) {
- var node = commands._findNode(params);
- return JSON.stringify(node.getBoundingClientRect());
-}
-
// Creates and returns a Node object that represents the specified node.
// Adds the node's children up to the specified depth. A negative depth will
// cause all descendants to be added.
diff --git a/src/cobalt/debug/backend/content/overlay_agent.js b/src/cobalt/debug/backend/content/overlay_agent.js
new file mode 100644
index 0000000..ef0f82c
--- /dev/null
+++ b/src/cobalt/debug/backend/content/overlay_agent.js
@@ -0,0 +1,179 @@
+// 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.
+
+(function(debugBackend) {
+
+// Attach methods to handle commands in the 'Overlay' devtools domain.
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay
+let commands = debugBackend.Overlay = {};
+
+// Returns non-overlapping rectangles to highlight for the box model of a node.
+// This pseudo-command in the Overlay domain is a helper for the C++
+// |OverlayAgent::HighlightNode|.
+commands._highlightNodeRects = function(params) {
+ let node = debugBackend.DOM._findNode(params);
+ let config = params.highlightConfig || {};
+ let highlights = [];
+ if (node && node.getBoundingClientRect) {
+ let styles = window.getComputedStyle(node);
+ let content = node.getBoundingClientRect();
+ let color;
+ let box;
+
+ let transformed = isNodeTransformed(node);
+
+ if (!transformed) {
+ // Margin
+ color = config.marginColor;
+ box = styleBox(styles, 'margin');
+ if (color) {
+ boxRects(content, box).forEach(
+ rect => highlights.push(highlightParams(rect, color)));
+ }
+
+ // Border
+ color = config.borderColor;
+ box = styleBox(styles, 'border');
+ if (color) {
+ boxRects(content, box).forEach(
+ rect => highlights.push(highlightParams(rect, color)));
+ }
+ content = insetRect(content, box);
+
+ // Padding
+ color = config.paddingColor;
+ box = styleBox(styles, 'padding');
+ if (color) {
+ boxRects(content, box).forEach(
+ rect => highlights.push(highlightParams(rect, color)));
+ }
+ content = insetRect(content, box);
+ }
+
+ // Content
+ color = config.contentColor;
+ if (color) {
+ let highlight = highlightParams(content, color);
+ if (transformed) {
+ highlight.outlineColor = {r: 255, g: 0, b: 255, a: 1.0};
+ }
+ highlights.push(highlight);
+ }
+ }
+ return JSON.stringify({highlightRects: highlights});
+}
+
+// Returns the inset width of the 4 sides of a box in the computed style, with
+// margin being negative to place it outside the rect.
+function styleBox(styles, boxName) {
+ let suffix = boxName == 'border' ? '-width' : '';
+ let sign = boxName == 'margin' ? -1 : 1;
+ let box = {};
+ ['top', 'right', 'bottom', 'left'].forEach(side => {
+ let width = styles.getPropertyValue(`${boxName}-${side}${suffix}`);
+ box[side] = sign * parseFloat(width) || 0;
+ });
+ return box;
+}
+
+function isNodeTransformed(node) {
+ while (node) {
+ if (window.getComputedStyle(node).transform !== 'none') return true;
+ node = node.offsetParent;
+ }
+ return false;
+}
+
+// Returns an array of non-overlapping rectangles for the box around the inside
+// of |rect|, but the rectangle for any box side with negative width will be
+// on the outside of |rect|.
+function boxRects(rect, box) {
+ // Start out assuming box widths are all positive.
+ let outerT = rect.y;
+ let outerB = rect.y + rect.height;
+ let outerL = rect.x;
+ let outerR = rect.x + rect.width;
+ let innerT = outerT + box.top;
+ let innerB = outerB - box.bottom;
+ let innerL = outerL + box.left;
+ let innerR = outerR - box.right;
+
+ // Swap any inner/outer "inverted" by a negative box side.
+ if (outerT > innerT) [outerT, innerT] = [innerT, outerT];
+ if (outerB < innerB) [outerB, innerB] = [innerB, outerB];
+ if (outerL > innerL) [outerL, innerL] = [innerL, outerL];
+ if (outerR < innerR) [outerR, innerR] = [innerR, outerR];
+
+ // +--------------+
+ // | |
+ // +--+--------+--+
+ // | | | |
+ // | | | |
+ // +--+--------+--+
+ // | |
+ // +--------------+
+ return [
+ // top
+ { x: outerL,
+ y: outerT,
+ width: outerR - outerL,
+ height: innerT - outerT },
+ // bottom
+ { x: outerL,
+ y: innerB,
+ width: outerR - outerL,
+ height: outerB - innerB },
+ // left
+ { x: outerL,
+ y: innerT,
+ width: innerL - outerL,
+ height: innerB - innerT },
+ // right
+ { x: innerR,
+ y: innerT,
+ width: outerR - innerR,
+ height: innerB - innerT },
+ ];
+}
+
+// Returns the rectangle with the box around the insides removed.
+function insetRect(rect, box) {
+ return {
+ x: rect.x + box.left,
+ y: rect.y + box.top,
+ width: rect.width - box.left - box.right,
+ height: rect.height - box.top - box.bottom };
+}
+
+// Returns parameters matching the DevTools protocol "Overlay.highlightRect"
+// parameters, as expected by the native overlay agent.
+function highlightParams(rect, color) {
+ // Copy each property rather than whole objects to ensure this can be
+ // converted with JSON.serialize().
+ return {
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height,
+ color: {
+ r: color.r,
+ g: color.g,
+ b: color.b,
+ a: color.a,
+ },
+ };
+}
+
+// TODO: Pass debugBackend from C++ instead of getting it from the window.
+})(window.debugBackend);
diff --git a/src/cobalt/debug/backend/css_agent.cc b/src/cobalt/debug/backend/css_agent.cc
index 38914e5..1de669c 100644
--- a/src/cobalt/debug/backend/css_agent.cc
+++ b/src/cobalt/debug/backend/css_agent.cc
@@ -14,6 +14,7 @@
#include "cobalt/debug/backend/css_agent.h"
+#include "cobalt/dom/document.h"
#include "cobalt/dom/html_element.h"
namespace cobalt {
@@ -65,7 +66,8 @@
CSSStyleRuleSequence css_rules;
auto html_element = element->AsHTMLElement();
if (html_element) {
- html_element->UpdateMatchingRules();
+ html_element->node_document()->UpdateComputedStyleOnElementAndAncestor(
+ html_element.get());
for (const auto& matching_rule : *html_element->matching_rules()) {
css_rules.push_back(matching_rule.first);
}
diff --git a/src/cobalt/debug/backend/debug_module.cc b/src/cobalt/debug/backend/debug_module.cc
index 0a854a2..5434789 100644
--- a/src/cobalt/debug/backend/debug_module.cc
+++ b/src/cobalt/debug/backend/debug_module.cc
@@ -29,6 +29,7 @@
constexpr char kLogAgent[] = "LogAgent";
constexpr char kDomAgent[] = "DomAgent";
constexpr char kCssAgent[] = "CssAgent";
+constexpr char kOverlayAgent[] = "OverlayAgent";
constexpr char kPageAgent[] = "PageAgent";
constexpr char kTracingAgent[] = "TracingAgent";
@@ -152,7 +153,7 @@
std::unique_ptr<RenderLayer> page_render_layer(new RenderLayer(base::Bind(
&RenderOverlay::SetOverlay, base::Unretained(data.render_overlay))));
- std::unique_ptr<RenderLayer> dom_render_layer(new RenderLayer(
+ std::unique_ptr<RenderLayer> overlay_render_layer(new RenderLayer(
base::Bind(&RenderLayer::SetBackLayer, page_render_layer->AsWeakPtr())));
// Create the agents that implement the various devtools protocol domains by
@@ -167,9 +168,10 @@
}
console_agent_.reset(new ConsoleAgent(debug_dispatcher_.get(), data.console));
log_agent_.reset(new LogAgent(debug_dispatcher_.get()));
- dom_agent_.reset(
- new DOMAgent(debug_dispatcher_.get(), std::move(dom_render_layer)));
+ dom_agent_.reset(new DOMAgent(debug_dispatcher_.get()));
css_agent_ = WrapRefCounted(new CSSAgent(debug_dispatcher_.get()));
+ overlay_agent_.reset(new OverlayAgent(debug_dispatcher_.get(),
+ std::move(overlay_render_layer)));
page_agent_.reset(new PageAgent(debug_dispatcher_.get(), data.window,
std::move(page_render_layer),
data.resource_provider));
@@ -201,6 +203,7 @@
log_agent_->Thaw(RemoveAgentState(kLogAgent, agents_state));
dom_agent_->Thaw(RemoveAgentState(kDomAgent, agents_state));
css_agent_->Thaw(RemoveAgentState(kCssAgent, agents_state));
+ overlay_agent_->Thaw(RemoveAgentState(kOverlayAgent, agents_state));
page_agent_->Thaw(RemoveAgentState(kPageAgent, agents_state));
tracing_agent_->Thaw(RemoveAgentState(kTracingAgent, agents_state));
@@ -228,6 +231,7 @@
StoreAgentState(agents_state, kLogAgent, log_agent_->Freeze());
StoreAgentState(agents_state, kDomAgent, dom_agent_->Freeze());
StoreAgentState(agents_state, kCssAgent, css_agent_->Freeze());
+ StoreAgentState(agents_state, kOverlayAgent, overlay_agent_->Freeze());
StoreAgentState(agents_state, kPageAgent, page_agent_->Freeze());
StoreAgentState(agents_state, kTracingAgent, tracing_agent_->Freeze());
diff --git a/src/cobalt/debug/backend/debug_module.h b/src/cobalt/debug/backend/debug_module.h
index 07bb642..11f34bd 100644
--- a/src/cobalt/debug/backend/debug_module.h
+++ b/src/cobalt/debug/backend/debug_module.h
@@ -29,6 +29,7 @@
#include "cobalt/debug/backend/debugger_state.h"
#include "cobalt/debug/backend/dom_agent.h"
#include "cobalt/debug/backend/log_agent.h"
+#include "cobalt/debug/backend/overlay_agent.h"
#include "cobalt/debug/backend/page_agent.h"
#include "cobalt/debug/backend/render_overlay.h"
#include "cobalt/debug/backend/runtime_agent.h"
@@ -146,6 +147,7 @@
std::unique_ptr<LogAgent> log_agent_;
std::unique_ptr<DOMAgent> dom_agent_;
scoped_refptr<CSSAgent> css_agent_;
+ std::unique_ptr<OverlayAgent> overlay_agent_;
std::unique_ptr<PageAgent> page_agent_;
std::unique_ptr<RuntimeAgent> runtime_agent_;
std::unique_ptr<ScriptDebuggerAgent> script_debugger_agent_;
diff --git a/src/cobalt/debug/backend/dom_agent.cc b/src/cobalt/debug/backend/dom_agent.cc
index 268e26b..f3d5322 100644
--- a/src/cobalt/debug/backend/dom_agent.cc
+++ b/src/cobalt/debug/backend/dom_agent.cc
@@ -14,16 +14,6 @@
#include "cobalt/debug/backend/dom_agent.h"
-#include <memory>
-#include <string>
-
-#include "base/bind.h"
-#include "cobalt/math/matrix3_f.h"
-#include "cobalt/math/transform_2d.h"
-#include "cobalt/render_tree/brush.h"
-#include "cobalt/render_tree/color_rgba.h"
-#include "cobalt/render_tree/rect_node.h"
-
namespace cobalt {
namespace debug {
namespace backend {
@@ -37,17 +27,13 @@
constexpr char kScriptFile[] = "dom_agent.js";
} // namespace
-DOMAgent::DOMAgent(DebugDispatcher* dispatcher,
- std::unique_ptr<RenderLayer> render_layer)
+DOMAgent::DOMAgent(DebugDispatcher* dispatcher)
: dispatcher_(dispatcher),
- render_layer_(std::move(render_layer)),
ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
DCHECK(dispatcher_);
commands_["disable"] = &DOMAgent::Disable;
commands_["enable"] = &DOMAgent::Enable;
- commands_["highlightNode"] = &DOMAgent::HighlightNode;
- commands_["hideHighlight"] = &DOMAgent::HideHighlight;
}
void DOMAgent::Thaw(JSONObject agent_state) {
@@ -72,77 +58,6 @@
void DOMAgent::Disable(const Command& command) { command.SendResponse(); }
-// Unlike most other DOM command handlers, this one is not fully implemented
-// in JavaScript. Instead, the JS object is used to look up the node from the
-// parameters and return its bounding client rect, then the highlight itself
-// is rendered by calling the C++ function |RenderHighlight| to set the render
-// overlay.
-void DOMAgent::HighlightNode(const Command& command) {
- // Get the bounding rectangle of the specified node.
- JSONObject json_dom_rect = dispatcher_->RunScriptCommand(
- "dom._getBoundingClientRect", command.GetParams());
- double x = 0.0;
- double y = 0.0;
- double width = 0.0;
- double height = 0.0;
- json_dom_rect->GetDouble("result.x", &x);
- json_dom_rect->GetDouble("result.y", &y);
- json_dom_rect->GetDouble("result.width", &width);
- json_dom_rect->GetDouble("result.height", &height);
-
- scoped_refptr<dom::DOMRect> dom_rect(
- new dom::DOMRect(static_cast<float>(x), static_cast<float>(y),
- static_cast<float>(width), static_cast<float>(height)));
-
- // |highlight_config_value| still owned by |params|.
- JSONObject params = JSONParse(command.GetParams());
- base::DictionaryValue* highlight_config_value = NULL;
- bool got_highlight_config =
- params->GetDictionary("highlightConfig", &highlight_config_value);
- DCHECK(got_highlight_config);
- DCHECK(highlight_config_value);
-
- RenderHighlight(dom_rect, highlight_config_value);
-
- command.SendResponse();
-}
-
-void DOMAgent::HideHighlight(const Command& command) {
- render_layer_->SetFrontLayer(scoped_refptr<render_tree::Node>());
- command.SendResponse();
-}
-
-void DOMAgent::RenderHighlight(
- const scoped_refptr<dom::DOMRect>& bounding_rect,
- const base::DictionaryValue* highlight_config_value) {
- // TODO: Should also render borders, etc.
-
- // Content color is optional in the parameters, so use a fallback.
- int r = 112;
- int g = 168;
- int b = 219;
- double a = 0.66;
- const base::DictionaryValue* content_color = NULL;
- bool got_content_color =
- highlight_config_value->GetDictionary("contentColor", &content_color);
- if (got_content_color && content_color) {
- content_color->GetInteger("r", &r);
- content_color->GetInteger("g", &g);
- content_color->GetInteger("b", &b);
- content_color->GetDouble("a", &a);
- }
- render_tree::ColorRGBA color(r / 255.0f, g / 255.0f, b / 255.0f,
- static_cast<float>(a));
-
- std::unique_ptr<render_tree::Brush> background_brush(
- new render_tree::SolidColorBrush(color));
- scoped_refptr<render_tree::Node> rect = new render_tree::RectNode(
- math::RectF(bounding_rect->x(), bounding_rect->y(),
- bounding_rect->width(), bounding_rect->height()),
- std::move(background_brush));
- render_layer_->SetFrontLayer(rect);
-}
-
} // namespace backend
} // namespace debug
} // namespace cobalt
diff --git a/src/cobalt/debug/backend/dom_agent.h b/src/cobalt/debug/backend/dom_agent.h
index b1d5339..6e59ffb 100644
--- a/src/cobalt/debug/backend/dom_agent.h
+++ b/src/cobalt/debug/backend/dom_agent.h
@@ -14,16 +14,10 @@
#ifndef COBALT_DEBUG_BACKEND_DOM_AGENT_H_
#define COBALT_DEBUG_BACKEND_DOM_AGENT_H_
-#include <memory>
-#include <string>
-
-#include "base/memory/weak_ptr.h"
#include "cobalt/debug/backend/command_map.h"
#include "cobalt/debug/backend/debug_dispatcher.h"
-#include "cobalt/debug/backend/render_layer.h"
#include "cobalt/debug/command.h"
#include "cobalt/debug/json_object.h"
-#include "cobalt/dom/dom_rect.h"
namespace cobalt {
namespace debug {
@@ -31,8 +25,7 @@
class DOMAgent {
public:
- DOMAgent(DebugDispatcher* dispatcher,
- std::unique_ptr<RenderLayer> render_layer);
+ explicit DOMAgent(DebugDispatcher* dispatcher);
void Thaw(JSONObject agent_state);
JSONObject Freeze();
@@ -41,22 +34,8 @@
void Enable(const Command& command);
void Disable(const Command& command);
- // Highlights a specified node according to highlight parameters.
- void HighlightNode(const Command& command);
-
- // Hides the node highlighting.
- void HideHighlight(const Command& command);
-
- // Renders a highlight to the overlay.
- void RenderHighlight(const scoped_refptr<dom::DOMRect>& bounding_rect,
- const base::DictionaryValue* highlight_config_value);
-
- // Helper object to connect to the debug dispatcher, etc.
DebugDispatcher* dispatcher_;
- // Render layer owned by this object.
- std::unique_ptr<RenderLayer> render_layer_;
-
// Map of member functions implementing commands.
CommandMap<DOMAgent> commands_;
diff --git a/src/cobalt/debug/backend/overlay_agent.cc b/src/cobalt/debug/backend/overlay_agent.cc
new file mode 100644
index 0000000..449675c
--- /dev/null
+++ b/src/cobalt/debug/backend/overlay_agent.cc
@@ -0,0 +1,162 @@
+// 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 "cobalt/debug/backend/overlay_agent.h"
+
+#include "cobalt/math/clamp.h"
+#include "cobalt/math/rect_f.h"
+#include "cobalt/render_tree/brush.h"
+#include "cobalt/render_tree/color_rgba.h"
+#include "cobalt/render_tree/composition_node.h"
+#include "cobalt/render_tree/rect_node.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+using base::Value;
+using math::Clamp;
+using render_tree::ColorRGBA;
+
+namespace {
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay
+constexpr char kInspectorDomain[] = "Overlay";
+
+// File to load JavaScript Overlay DevTools domain implementation from.
+constexpr char kScriptFile[] = "overlay_agent.js";
+
+// Returns the float value of a param, or 0.0 if undefined or non-numeric.
+float GetFloatParam(const Value* params, base::StringPiece key) {
+ if (!params || !params->is_dict()) return 0.0f;
+ const Value* v = params->FindKey(key);
+ if (!v || !(v->is_double() || v->is_int())) return 0.0f;
+ return static_cast<float>(v->GetDouble());
+}
+
+// Returns an RGBA color defined by a param, or transparent if undefined.
+ColorRGBA RenderColor(const Value* params) {
+ float r = GetFloatParam(params, "r") / 255.0f;
+ float g = GetFloatParam(params, "g") / 255.0f;
+ float b = GetFloatParam(params, "b") / 255.0f;
+ float a = GetFloatParam(params, "a");
+ return ColorRGBA(Clamp(r, 0.0f, 1.0f), Clamp(g, 0.0f, 1.0f),
+ Clamp(b, 0.0f, 1.0f), Clamp(a, 0.0f, 1.0f));
+}
+
+// Returns a rectangle to render according to the params for the DevTools
+// "Overlay.highlightRect" command.
+// https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-highlightRect
+scoped_refptr<render_tree::RectNode> RenderHighlightRect(const Value* params) {
+ float x = GetFloatParam(params, "x");
+ float y = GetFloatParam(params, "y");
+ float width = GetFloatParam(params, "width");
+ float height = GetFloatParam(params, "height");
+ ColorRGBA color(RenderColor(params->FindKey("color")));
+ const Value* outline_param = params->FindKey("outlineColor");
+ ColorRGBA outline_color(RenderColor(outline_param));
+ float outline_width = outline_param ? 1.0f : 0.0f;
+ return base::MakeRefCounted<render_tree::RectNode>(
+ math::RectF(x, y, width, height),
+ std::make_unique<render_tree::SolidColorBrush>(color),
+ std::make_unique<render_tree::Border>(render_tree::BorderSide(
+ outline_width, render_tree::kBorderStyleSolid, outline_color)));
+}
+
+} // namespace
+
+OverlayAgent::OverlayAgent(DebugDispatcher* dispatcher,
+ std::unique_ptr<RenderLayer> render_layer)
+ : dispatcher_(dispatcher),
+ render_layer_(std::move(render_layer)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
+ DCHECK(dispatcher_);
+ DCHECK(render_layer_);
+
+ commands_["disable"] = &OverlayAgent::Disable;
+ commands_["enable"] = &OverlayAgent::Enable;
+ commands_["highlightNode"] = &OverlayAgent::HighlightNode;
+ commands_["highlightRect"] = &OverlayAgent::HighlightRect;
+ commands_["hideHighlight"] = &OverlayAgent::HideHighlight;
+}
+
+void OverlayAgent::Thaw(JSONObject agent_state) {
+ dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
+ script_loaded_ = dispatcher_->RunScriptFile(kScriptFile);
+ DLOG_IF(ERROR, !script_loaded_) << "Failed to load " << kScriptFile;
+}
+
+JSONObject OverlayAgent::Freeze() {
+ dispatcher_->RemoveDomain(kInspectorDomain);
+ return JSONObject();
+}
+
+void OverlayAgent::Enable(const Command& command) {
+ if (script_loaded_) {
+ enabled_ = true;
+ command.SendResponse();
+ } else {
+ command.SendErrorResponse(Command::kInternalError,
+ "Cannot create Overlay inspector.");
+ }
+}
+
+void OverlayAgent::Disable(const Command& command) {
+ enabled_ = false;
+ command.SendResponse();
+}
+
+void OverlayAgent::HighlightNode(const Command& command) {
+ if (!enabled_) {
+ command.SendErrorResponse(Command::kInvalidRequest,
+ "Overlay inspector not enabled.");
+ return;
+ }
+ // Use the injected JavaScript helper to get the rectangles to highlight for
+ // the specified node.
+ JSONObject rects_response = dispatcher_->RunScriptCommand(
+ "Overlay._highlightNodeRects", command.GetParams());
+ const Value* highlight_rects =
+ rects_response->FindPath({"result", "highlightRects"});
+ if (!highlight_rects) {
+ command.SendErrorResponse(Command::kInvalidParams,
+ "Can't get node highlights.");
+ return;
+ }
+
+ // Render all the highlight rects as children of a CompositionNode.
+ render_tree::CompositionNode::Builder builder;
+ for (const Value& rect_params : highlight_rects->GetList()) {
+ builder.AddChild(RenderHighlightRect(&rect_params));
+ }
+ render_layer_->SetFrontLayer(
+ base::MakeRefCounted<render_tree::CompositionNode>(builder));
+
+ command.SendResponse();
+}
+
+void OverlayAgent::HighlightRect(const Command& command) {
+ JSONObject params = JSONParse(command.GetParams());
+ render_layer_->SetFrontLayer(RenderHighlightRect(params.get()));
+ command.SendResponse();
+}
+
+void OverlayAgent::HideHighlight(const Command& command) {
+ render_layer_->SetFrontLayer(scoped_refptr<render_tree::Node>());
+ command.SendResponse();
+}
+
+} // namespace backend
+} // namespace debug
+} // namespace cobalt
diff --git a/src/cobalt/debug/backend/overlay_agent.h b/src/cobalt/debug/backend/overlay_agent.h
new file mode 100644
index 0000000..4b7425b
--- /dev/null
+++ b/src/cobalt/debug/backend/overlay_agent.h
@@ -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.
+#ifndef COBALT_DEBUG_BACKEND_OVERLAY_AGENT_H_
+#define COBALT_DEBUG_BACKEND_OVERLAY_AGENT_H_
+
+#include "cobalt/debug/backend/command_map.h"
+#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/backend/render_layer.h"
+#include "cobalt/debug/command.h"
+#include "cobalt/debug/json_object.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+class OverlayAgent {
+ public:
+ OverlayAgent(DebugDispatcher* dispatcher,
+ std::unique_ptr<RenderLayer> render_layer);
+
+ void Thaw(JSONObject agent_state);
+ JSONObject Freeze();
+
+ private:
+ void Enable(const Command& command);
+ void Disable(const Command& command);
+
+ void HighlightNode(const Command& command);
+ void HighlightRect(const Command& command);
+ void HideHighlight(const Command& command);
+
+ DebugDispatcher* dispatcher_;
+
+ // Render layer owned by this object.
+ std::unique_ptr<RenderLayer> render_layer_;
+
+ // Map of member functions implementing commands.
+ CommandMap<OverlayAgent> commands_;
+
+ // Whether we successfully loaded the agent's JavaScript implementation.
+ bool script_loaded_ = false;
+
+ bool enabled_ = false;
+};
+
+} // namespace backend
+} // namespace debug
+} // namespace cobalt
+
+#endif // COBALT_DEBUG_BACKEND_OVERLAY_AGENT_H_
diff --git a/src/cobalt/debug/console/content/console_manager.js b/src/cobalt/debug/console/content/console_manager.js
new file mode 100644
index 0000000..532e00e
--- /dev/null
+++ b/src/cobalt/debug/console/content/console_manager.js
@@ -0,0 +1,196 @@
+// 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.
+
+var consoleManager = null;
+
+function start() {
+ window.consoleManager = new ConsoleManager();
+}
+
+window.addEventListener('load', start);
+
+function ConsoleManager() {
+ // Handles communication with the debugger.
+ this.debuggerClient = new DebuggerClient();
+ // Number of animation frame samples since the last update.
+ this.animationFrameSamples = 0;
+ // A list of all the possible interactive consoles.
+ // Each item contains the mode and the reference to the actual console.
+ this.consoleRegistry = null;
+
+ this.initializeConsoles();
+
+ document.addEventListener('keydown', this.handleKeydown.bind(this));
+ document.addEventListener('keyup', this.handleKeyup.bind(this));
+ document.addEventListener('keypress', this.handleKeypress.bind(this));
+ document.addEventListener('wheel', this.handleWheel.bind(this));
+ document.addEventListener('input', this.handleInput.bind(this));
+ if (typeof window.onScreenKeyboard != 'undefined'
+ && window.onScreenKeyboard) {
+ window.onScreenKeyboard.onInput = this.handleInput.bind(this);
+ }
+ window.requestAnimationFrame(this.animate.bind(this));
+}
+
+ConsoleManager.prototype.initializeConsoles = function() {
+ this.consoleRegistry = [
+ {
+ console: new DebugConsole(this.debuggerClient),
+ mode: 'debug',
+ bodyClass: 'debugConsole hud',
+ },
+ {
+ console: new MediaConsole(this.debuggerClient),
+ mode: 'media',
+ bodyClass: 'mediaConsole',
+ },
+ ];
+
+ this.hudConsole = this.consoleRegistry[0].console;
+
+ this.consoleRegistry.forEach( entry => {
+ let ensureConsolesAreValid = function(method) {
+ if(typeof entry.console[method] != "function") {
+ console.warn(`Console "${entry.mode}" ${method}() is not implemented. \
+ Providing default empty implementation.`);
+ // Provide a default not-implemented warning message.
+ let consoleName = entry.mode;
+ let notImplementedMessage = function() {
+ console.log(
+ `Console "${consoleName}" ${method}() is not implemented.`);
+ };
+ entry.console[method] = notImplementedMessage;
+ }
+ };
+ ensureConsolesAreValid("update");
+ ensureConsolesAreValid("setVisible");
+ ensureConsolesAreValid("onKeydown");
+ ensureConsolesAreValid("onKeyup");
+ ensureConsolesAreValid("onKeypress");
+ ensureConsolesAreValid("onInput");
+ ensureConsolesAreValid("onWheel");
+ });
+}
+
+ConsoleManager.prototype.update = function() {
+ let mode = window.debugHub.getDebugConsoleMode();
+
+ if (mode !== 'off') {
+ this.debuggerClient.attach();
+ }
+
+ let activeConsole = this.getActiveConsole();
+ let bodyClass = '';
+ if (mode == 'hud') {
+ bodyClass = 'hud';
+ // The HUD is owned by the debug console, but since it has its own mode
+ // dedicated to it, it needs to be specifically updated when it is visible.
+ // TODO: Factor out hudConsole into its own console.
+ this.hudConsole.updateHud();
+ } else if (mode != 'off' && mode != 'hud') {
+ bodyClass = activeConsole.bodyClass;
+ }
+ document.body.className = bodyClass;
+
+ this.consoleRegistry.forEach((entry) => {
+ entry.console.setVisible(entry == activeConsole);
+ });
+
+ if (mode !== 'off') {
+ if (activeConsole) { activeConsole.console.update(); }
+ }
+}
+
+// Animation callback: updates state and animated nodes.
+ConsoleManager.prototype.animate = function(time) {
+ const subsample = 8;
+ this.animationFrameSamples = (this.animationFrameSamples + 1) % subsample;
+ if (this.animationFrameSamples == 0) {
+ this.update();
+ }
+ window.requestAnimationFrame(this.animate.bind(this));
+}
+
+ConsoleManager.prototype.getActiveConsole = function() {
+ let mode = window.debugHub.getDebugConsoleMode();
+ return this.consoleRegistry.find( entry => entry.mode === mode );
+}
+
+ConsoleManager.prototype.handleKeydown = function(event) {
+ // Map of 'Unidentified' additional Cobalt keyCodes to equivalent keys.
+ const unidentifiedCobaltKeyMap = {
+ // kSbKeyGamepad1
+ 0x8000: 'Enter',
+ // kSbKeyGamepad2
+ 0x8001: 'Esc',
+ // kSbKeyGamepad3
+ 0x8002: 'Home',
+ // kSbKeyGamepad5
+ 0x8008: 'Enter',
+ // kSbKeyGamepad6
+ 0x8009: 'Enter',
+ // kSbKeyGamepadDPadUp
+ 0x800C: 'ArrowUp',
+ // kSbKeyGamepadDPadDown
+ 0x800D: 'ArrowDown',
+ // kSbKeyGamepadDPadLeft
+ 0x800E: 'ArrowLeft',
+ // kSbKeyGamepadDPadRight
+ 0x800F: 'ArrowRight',
+ // kSbKeyGamepadLeftStickUp
+ 0x8011: 'ArrowUp',
+ // kSbKeyGamepadLeftStickDown
+ 0x8012: 'ArrowDown',
+ // kSbKeyGamepadLeftStickLeft
+ 0x8013: 'ArrowLeft',
+ // kSbKeyGamepadLeftStickRight
+ 0x8014: 'ArrowRight',
+ // kSbKeyGamepadRightStickUp
+ 0x8015: 'ArrowUp',
+ // kSbKeyGamepadRightStickDown
+ 0x8016: 'ArrowDown',
+ // kSbKeyGamepadRightStickLeft
+ 0x8017: 'ArrowLeft',
+ // kSbKeyGamepadRightStickRight
+ 0x8018: 'ArrowRight'
+ };
+
+ let key = event.key;
+ if (key == 'Unidentified') {
+ key = unidentifiedCobaltKeyMap[event.keyCode] || 'Unidentified';
+ }
+
+ let active = this.getActiveConsole();
+ if (active) { active.console.onKeydown(event); }
+}
+
+ConsoleManager.prototype.handleKeyup = function(event) {
+ let active = this.getActiveConsole();
+ if (active) { active.console.onKeyup(event); }
+}
+
+ConsoleManager.prototype.handleKeypress = function(event) {
+ let active = this.getActiveConsole();
+ if (active) { active.console.onKeypress(event); }
+}
+
+ConsoleManager.prototype.handleInput = function(event) {
+ let active = this.getActiveConsole();
+ if (active) { active.console.onInput(event); }
+}
+
+ConsoleManager.prototype.handleWheel = function(event) {
+ let active = this.getActiveConsole();
+ if (active) { active.console.onWheel(event); }
+}
diff --git a/src/cobalt/debug/console/content/debug_commands.js b/src/cobalt/debug/console/content/debug_commands.js
index e91754d..8c4456b 100644
--- a/src/cobalt/debug/console/content/debug_commands.js
+++ b/src/cobalt/debug/console/content/debug_commands.js
@@ -13,114 +13,117 @@
// limitations under the License.
function initDebugCommands() {
- debug = new Object();
- d = debug;
+ let debugCommands = {};
- debug.cvalList = function() {
+ debugCommands.cvalList = function() {
var result = consoleValues.listAll();
- printToMessageLog(messageLog.INTERACTIVE, result);
+ printToMessageLog(MessageLog.INTERACTIVE, result);
}
- debug.cvalList.shortHelp = 'List all registered console values.';
- debug.cvalList.longHelp =
+ debugCommands.cvalList.shortHelp = 'List all registered console values.';
+ debugCommands.cvalList.longHelp =
'List all registered console values that can be displayed.\n' +
'You can change what subset is displayed in the HUD using ' +
'the cvalAdd and cvalRemove debug methods.';
- debug.cvalAdd = function(substringToMatch) {
+ debugCommands.cvalAdd = function(substringToMatch) {
var result = consoleValues.addActive(substringToMatch);
- printToMessageLog(messageLog.INTERACTIVE, result);
+ printToMessageLog(MessageLog.INTERACTIVE, result);
// After each change, save the active set with the default key.
this.cvalSave();
}
- debug.cvalAdd.shortHelp = 'Adds one or more consoles value to the HUD.';
- debug.cvalAdd.longHelp =
+ debugCommands.cvalAdd.shortHelp =
+ 'Adds one or more consoles value to the HUD.';
+ debugCommands.cvalAdd.longHelp =
'Adds any of the registered consolve values (displayed with cvalList) ' +
'to the HUD whose name matches one of the specified space-separated '
'prefixes.';
- debug.cvalRemove = function(substringToMatch) {
+ debugCommands.cvalRemove = function(substringToMatch) {
var result = consoleValues.removeActive(substringToMatch);
- printToMessageLog(messageLog.INTERACTIVE, result);
+ printToMessageLog(MessageLog.INTERACTIVE, result);
// After each change, save the active set with the default key.
this.cvalSave();
}
- debug.cvalRemove.shortHelp =
+ debugCommands.cvalRemove.shortHelp =
'Removes one or more consoles value from the HUD.';
- debug.cvalRemove.longHelp =
+ debugCommands.cvalRemove.longHelp =
'Removes any of the consolve values displayed in the HUD ' +
'whose name matches one of the specified space-separated prefixes.';
- debug.cvalSave = function(key) {
+ debugCommands.cvalSave = function(key) {
var result = consoleValues.saveActiveSet(key);
- printToMessageLog(messageLog.INTERACTIVE, result);
+ printToMessageLog(MessageLog.INTERACTIVE, result);
}
- debug.cvalSave.shortHelp =
+ debugCommands.cvalSave.shortHelp =
'Saves the current set of console values displayed in the HUD.';
- debug.cvalSave.longHelp =
+ debugCommands.cvalSave.longHelp =
'Saves the set of console values currently displayed in the HUD ' +
'to web local storage using the specified key. Saved display sets can ' +
'be reloaded later using the cvalLoad debug method and the same key.\n' +
'If no key is specified, uses a default value.';
- debug.cvalLoad = function(key) {
+ debugCommands.cvalLoad = function(key) {
var result = consoleValues.loadActiveSet(key);
- printToMessageLog(messageLog.INTERACTIVE, result);
+ printToMessageLog(MessageLog.INTERACTIVE, result);
}
- debug.cvalLoad.shortHelp =
+ debugCommands.cvalLoad.shortHelp =
'Loads a previously stored set of console values displayed in the HUD.';
- debug.cvalLoad.longHelp =
+ debugCommands.cvalLoad.longHelp =
'Loads the set of console values currently displayed in the HUD ' +
'from a set previously saved in web local storage using the cvalSave ' +
'debug method and the same key.\n' +
'If no key is specified, uses a default value.';
- debug.history = history;
- debug.history.shortHelp = 'Display command history.';
- debug.history.longHelp =
+ debugCommands.history = history;
+ debugCommands.history.shortHelp = 'Display command history.';
+ debugCommands.history.longHelp =
'Display a list of all previously executed commands with an '+
'index. You can re-execute any of the commands from the ' +
'history by typing "!" followed by the index of that command.'
- debug.help = help;
- debug.help.shortHelp = 'Display this message, or detail for a specific command.';
- debug.help.longHelp =
+ debugCommands.help = help.bind(this, debugCommands);
+ debugCommands.help.shortHelp =
+ 'Display this message, or detail for a specific command.';
+ debugCommands.help.longHelp =
'With no arguments, displays a summary of all commands. If the name of ' +
'a command is specified, displays additional details about that command.';
- debug.dir = dir;
- debug.dir.shortHelp =
+ debugCommands.dir = dir;
+ debugCommands.dir.shortHelp =
'Lists the properties of an object in the main web module.';
- debug.dir.longHelp =
+ debugCommands.dir.longHelp =
'Lists the properties of the specified object in the main web module. ' +
'Remember to enclose the name of the object in quotes.';
- debug.debugger = function() {
+ debugCommands.debugger = function() {
return debuggerClient;
}
- debug.debugger.shortHelp =
+ debugCommands.debugger.shortHelp =
'Get the debugger client';
- debug.debugger.longHelp =
+ debugCommands.debugger.longHelp =
'Get the debugger client. The debugger client can be used to issue ' +
'JavaScript debugging commands to the main web module.';
- addConsoleCommands();
+ addConsoleCommands(debugCommands);
+
+ return debugCommands;
}
-function help(command) {
+function help(debugCommands, command) {
var helpString = '';
if (command) {
// Detailed help on a specific command.
- if (debug[command]) {
- helpString = debug[command].longHelp;
+ if (debugCommands[command]) {
+ helpString = debugCommands[command].longHelp;
} else {
helpString = 'Command "' + command + '" not found.';
}
} else {
// Summary help for all commands.
helpString = 'Cobalt Debug Console commands:\n\n';
- for (cmd in debug) {
+ for (cmd in debugCommands) {
helpString += 'debug.' + cmd + '() - ';
- helpString += debug[cmd].shortHelp + '\n';
+ helpString += debugCommands[cmd].shortHelp + '\n';
}
helpString +=
'\nYou are entering JavaScript, so remember to use parentheses, ' +
@@ -129,13 +132,13 @@
'All other text will be executed as JavaScript in the main web ' +
'module.\n';
}
- printToMessageLog(messageLog.INTERACTIVE, helpString);
+ printToMessageLog(MessageLog.INTERACTIVE, helpString);
}
function history() {
var history = commandInput.getHistory();
for (var i = 0; i < history.length; i += 1) {
- printToMessageLog(messageLog.INTERACTIVE, i + ' ' + history[i]);
+ printToMessageLog(MessageLog.INTERACTIVE, i + ' ' + history[i]);
}
}
@@ -148,18 +151,18 @@
executeMain(js);
}
-function addConsoleCommands() {
+function addConsoleCommands(debugCommands) {
var consoleCommands = window.debugHub.consoleCommands;
for (var i = 0; i < consoleCommands.length; i++) {
var c = consoleCommands[i];
- addOneConsoleCommand(c.command, c.shortHelp, c.longHelp);
+ addOneConsoleCommand(debugCommands, c.command, c.shortHelp, c.longHelp);
}
}
-function addOneConsoleCommand(command, shortHelp, longHelp) {
- debug[command] = function(message) {
+function addOneConsoleCommand(debugCommands, command, shortHelp, longHelp) {
+ debugCommands[command] = function(message) {
window.debugHub.sendConsoleCommand(command, message);
}
- debug[command].shortHelp = shortHelp;
- debug[command].longHelp = longHelp;
+ debugCommands[command].shortHelp = shortHelp;
+ debugCommands[command].longHelp = longHelp;
}
diff --git a/src/cobalt/debug/console/content/debug_console.css b/src/cobalt/debug/console/content/debug_console.css
index d0ccc4d..f087f24 100644
--- a/src/cobalt/debug/console/content/debug_console.css
+++ b/src/cobalt/debug/console/content/debug_console.css
@@ -12,6 +12,9 @@
color: #FFFFFF;
display: none;
}
+body.hud #hudFrame {
+ display: block;
+}
#hud {
position: absolute;
@@ -27,7 +30,7 @@
padding: 0.625em;
}
-#consoleFrame {
+#debugConsoleFrame {
position: absolute;
top: 0;
left: 0;
@@ -38,6 +41,9 @@
overflow: hidden;
display: none;
}
+body.debugConsole #debugConsoleFrame {
+ display: block;
+}
#messageContainerFrame {
position: absolute;
@@ -122,3 +128,33 @@
border-style: solid;
padding: 0.625em;
}
+
+#mediaConsoleFrame {
+ position: absolute;
+ top: 75%;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ width: 100%;
+ background-color: rgba(128, 128, 128, 0.6);
+ color: #FFFFFF;
+ display: none;
+}
+body.mediaConsole #mediaConsoleFrame {
+ display: block;
+}
+
+#mediaConsole {
+ position: absolute;
+ top: 0.625em;
+ left: 0.625em;
+ bottom: 0.625em;
+ right: 0.625em;
+ background-color: rgba(0, 0, 0, 0.6);
+ color: #FFFFFF;
+ border: 0.0625em;
+ border-color: #606060;
+ border-style: solid;
+ padding: 0.625em;
+ white-space: pre;
+}
diff --git a/src/cobalt/debug/console/content/debug_console.html b/src/cobalt/debug/console/content/debug_console.html
index a60c6e4..baed2fd 100644
--- a/src/cobalt/debug/console/content/debug_console.html
+++ b/src/cobalt/debug/console/content/debug_console.html
@@ -5,23 +5,28 @@
<link rel="stylesheet" type="text/css" href="debug_console.css">
</head>
+<script type="text/javascript" src="console_manager.js"></script>
<script type="text/javascript" src="debug_console.js"></script>
<script type="text/javascript" src="console_values.js"></script>
<script type="text/javascript" src="message_log.js"></script>
<script type="text/javascript" src="command_input.js"></script>
<script type="text/javascript" src="debug_commands.js"></script>
<script type="text/javascript" src="debugger_client.js"></script>
+<script type="text/javascript" src="media_console.js"></script>
<body>
<div id="hudFrame">
<div id="hud"></div>
</div>
- <div id="consoleFrame">
+ <div id="debugConsoleFrame">
<div id="messageContainerFrame">
- <div id = "messageContainer"></div>
+ <div id="messageContainer"></div>
</div>
<div id="in">> _</div>
</div>
+ <div id="mediaConsoleFrame">
+ <div id="mediaConsole"></div>
+ </div>
</body>
</html>
diff --git a/src/cobalt/debug/console/content/debug_console.js b/src/cobalt/debug/console/content/debug_console.js
index e1028d5..088b744 100644
--- a/src/cobalt/debug/console/content/debug_console.js
+++ b/src/cobalt/debug/console/content/debug_console.js
@@ -12,171 +12,78 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
-// Text the user has typed since last hitting Enter
-var inputText = '';
-// The DOM node used as a container for message nodes.
-var messageLog = null;
-// Stores and manipulates the set of console values.
-var consoleValues = null;
-// Handles command input, editing, history traversal, etc.
-var commandInput = null;
-// Object to store methods to be executed in the debug console.
-var debug = null;
-// Shorthand reference for the debug object.
-var d = null;
-// Handles communication with the debugger.
-var debuggerClient = null;
-// Number of animation frame samples since the last update.
-var animationFrameSamples = 0;
-
-// Map of 'Unidentified' additional Cobalt keyCodes to equivalent keys.
-var unidentifiedCobaltKeyMap = {
- // kSbKeyGamepad1
- 0x8000: 'Enter',
- // kSbKeyGamepad2
- 0x8001: 'Esc',
- // kSbKeyGamepad3
- 0x8002: 'Home',
- // kSbKeyGamepad5
- 0x8008: 'Enter',
- // kSbKeyGamepad6
- 0x8009: 'Enter',
- // kSbKeyGamepadDPadUp
- 0x800C: 'ArrowUp',
- // kSbKeyGamepadDPadDown
- 0x800D: 'ArrowDown',
- // kSbKeyGamepadDPadLeft
- 0x800E: 'ArrowLeft',
- // kSbKeyGamepadDPadRight
- 0x800F: 'ArrowRight',
- // kSbKeyGamepadLeftStickUp
- 0x8011: 'ArrowUp',
- // kSbKeyGamepadLeftStickDown
- 0x8012: 'ArrowDown',
- // kSbKeyGamepadLeftStickLeft
- 0x8013: 'ArrowLeft',
- // kSbKeyGamepadLeftStickRight
- 0x8014: 'ArrowRight',
- // kSbKeyGamepadRightStickUp
- 0x8015: 'ArrowUp',
- // kSbKeyGamepadRightStickDown
- 0x8016: 'ArrowDown',
- // kSbKeyGamepadRightStickLeft
- 0x8017: 'ArrowLeft',
- // kSbKeyGamepadRightStickRight
- 0x8018: 'ArrowRight'
-};
-
-function createMessageLog() {
- var messageContainer = document.getElementById('messageContainer');
- messageLog = new MessageLog(messageContainer);
-}
-
-function createCommandInput() {
- var inputElem = document.getElementById('in');
- this.commandInput = new CommandInput(inputElem);
-}
-
-function createConsoleValues() {
- // Create the console values and attempt to load the active set using the
- // default key. If this fails, it will leave the active set equal to the
- // all registered CVals.
- consoleValues = new ConsoleValues();
- var loadResult = consoleValues.loadActiveSet();
- printToMessageLog(messageLog.INTERACTIVE, loadResult);
-}
-
-function createDebuggerClient() {
- debuggerClient = new DebuggerClient();
-}
-
-function showBlockElem(elem, doShow) {
- if (elem) {
- var display = doShow ? 'block' : 'none';
- if (elem.style.display != display) {
- elem.style.display = display;
- }
+// Global for other modules to use.
+function printToMessageLog(severity, message) {
+ if (window.debugConsoleInstance) {
+ window.debugConsoleInstance.printToMessageLog(severity, message);
}
}
-function showConsole(doShow) {
- showBlockElem(document.getElementById('consoleFrame'), doShow);
- messageLog.setVisible(doShow);
+function DebugConsole(debuggerClient) {
+ window.debugConsoleInstance = this;
+
+ this.debuggerClient = debuggerClient;
+ // Text the user has typed since last hitting Enter
+ this.inputText = '';
+
+ this.commandInput = new CommandInput(document.getElementById('in'));
+ this.messageLog = new MessageLog(document.getElementById('messageContainer'));
+
+ this.consoleValues = new ConsoleValues();
+ let loadResult = this.consoleValues.loadActiveSet();
+ this.printToMessageLog(MessageLog.INTERACTIVE, loadResult);
+
+ this.debugCommands = initDebugCommands();
}
-function isConsoleVisible() {
- var mode = window.debugHub.getDebugConsoleMode();
- return mode >= window.debugHub.DEBUG_CONSOLE_ON;
+DebugConsole.prototype.printToMessageLog = function(severity, message) {
+ this.messageLog.addMessage(severity, message);
}
-function printToMessageLog(severity, message) {
- messageLog.addMessage(severity, message);
-}
-
-function showHud(doShow) {
- showBlockElem(document.getElementById('hudFrame'), doShow);
-}
-
-function printToHud(message) {
- var elem = document.getElementById('hud');
+DebugConsole.prototype.printToHud = function(message) {
+ let elem = document.getElementById('hud');
elem.textContent = message;
}
-function updateHud(time) {
- var mode = window.debugHub.getDebugConsoleMode();
- if (mode >= window.debugHub.DEBUG_CONSOLE_HUD) {
- consoleValues.update();
- var cvalString = consoleValues.toString();
- printToHud(cvalString);
- }
+DebugConsole.prototype.updateHud = function() {
+ let mode = window.debugHub.getDebugConsoleMode();
+ this.consoleValues.update();
+ let cvalString = this.consoleValues.toString();
+ this.printToHud(cvalString);
}
-function updateMode() {
- var mode = window.debugHub.getDebugConsoleMode();
- showConsole(mode >= window.debugHub.DEBUG_CONSOLE_ON);
- showHud(mode >= window.debugHub.DEBUG_CONSOLE_HUD);
+DebugConsole.prototype.setVisible = function(visible) {
+ this.messageLog.setVisible(visible);
}
-// Animation callback: updates state and animated nodes.
-function animate(time) {
- var subsample = 8;
- animationFrameSamples = (animationFrameSamples + 1) % subsample;
- if (animationFrameSamples == 0) {
- updateMode();
- updateHud(time);
- if (isConsoleVisible()) {
- commandInput.animateBlink();
- // This will do nothing if debugger is already attached.
- debuggerClient.attach();
- }
- }
- window.requestAnimationFrame(animate);
+DebugConsole.prototype.update = function() {
+ this.commandInput.animateBlink();
+ this.updateHud();
}
// Executes a command from the history buffer.
// Index should be an integer (positive is an absolute index, negative is a
// number of commands back from the current) or '!' to execute the last command.
-function executeCommandFromHistory(idx) {
+DebugConsole.prototype.executeCommandFromHistory = function(idx) {
if (idx == '!') {
idx = -1;
}
idx = parseInt(idx);
- commandInput.setCurrentCommandFromHistory(idx);
- executeCurrentCommand();
+ this.commandInput.setCurrentCommandFromHistory(idx);
+ this.executeCurrentCommand();
}
// Special commands that are executed immediately, not as JavaScript,
// e.g. !N to execute the Nth command in the history buffer.
// Returns true if the command is processed here, false otherwise.
-function executeImmediate(command) {
+DebugConsole.prototype.executeImmediate = function(command) {
if (command[0] == '!') {
- executeCommandFromHistory(command.substring(1));
+ this.executeCommandFromHistory(command.substring(1));
return true;
} else if (command.trim() == 'help') {
// Treat 'help' as a special case for users not expecting JS execution.
help();
- commandInput.clearCurrentCommand();
+ this.commandInput.clearCurrentCommand();
return true;
}
return false;
@@ -185,9 +92,11 @@
// JavaScript commands executed in this (debug console) web module.
// The only commands we execute here are methods of the debug object
// (or its shorthand equivalent).
-function executeDebug(command) {
+DebugConsole.prototype.executeDebug = function(command) {
if (command.trim().indexOf('debug.') == 0 ||
command.trim().indexOf('d.') == 0) {
+ let debug = this.debugCommands;
+ let d = this.debugCommands;
eval(command);
return true;
}
@@ -197,8 +106,9 @@
// Execute a command as JavaScript in the main web module.
// Use the debugger evaluate command, which gives us Command Line API access
// and rich results with object preview.
-function executeMain(command) {
- debuggerClient.evaluate(command);
+DebugConsole.prototype.executeMain = function(command) {
+ let callback = this.printToLogCallback.bind(this);
+ this.debuggerClient.evaluate(command, callback);
}
// Executes a command entered by the user.
@@ -208,118 +118,112 @@
// 3. If no matching command is found, pass to the Cobalt DebugHub.
// DebugHub will execute any commands recognized on the C++ side,
// or pass to the main web module to be executed as JavaScript.
-function executeCommand(command) {
- if (executeImmediate(command)) {
- printToMessageLog(messageLog.INTERACTIVE, '');
+DebugConsole.prototype.executeCommand = function(command) {
+ if (this.executeImmediate(command)) {
+ this.printToMessageLog(MessageLog.INTERACTIVE, '');
return;
}
- commandInput.storeAndClearCurrentCommand();
- if (executeDebug(command)) {
- printToMessageLog(messageLog.INTERACTIVE, '');
+ this.commandInput.storeAndClearCurrentCommand();
+ if (this.executeDebug(command)) {
+ this.printToMessageLog(MessageLog.INTERACTIVE, '');
return;
}
- executeMain(command);
+ this.executeMain(command);
}
// Executes the current command in the CommandInput object.
// Typically called when the user hits Enter.
-function executeCurrentCommand() {
- var command = commandInput.getCurrentCommand();
- printToMessageLog(messageLog.INTERACTIVE, '> ' + command);
- executeCommand(command);
+DebugConsole.prototype.executeCurrentCommand = function() {
+ let command = this.commandInput.getCurrentCommand();
+ this.printToMessageLog(MessageLog.INTERACTIVE, '> ' + command);
+ this.executeCommand(command);
}
-function onWheel(event) {
+DebugConsole.prototype.onWheel = function(event) {
if (event.deltaY > 0) {
- messageLog.scrollDown(event.deltaY);
+ this.messageLog.scrollDown(event.deltaY);
} else if (event.deltaY < 0) {
- messageLog.scrollUp(-event.deltaY);
+ this.messageLog.scrollUp(-event.deltaY);
}
}
-function onKeydown(event) {
- var key = event.key;
- if (key == 'Unidentified') {
- key = unidentifiedCobaltKeyMap[event.keyCode] || 'Unidentified';
- }
+DebugConsole.prototype.onKeydown = function(event) {
+ let key = event.key;
if (key == 'ArrowLeft') {
- commandInput.moveCursor(-1);
+ this.commandInput.moveCursor(-1);
} else if (key == 'ArrowRight') {
- commandInput.moveCursor(1);
+ this.commandInput.moveCursor(1);
} else if (key == 'ArrowUp') {
- commandInput.back();
+ this.commandInput.back();
} else if (key == 'ArrowDown') {
- commandInput.forward();
+ this.commandInput.forward();
} else if (key == 'Backspace') {
- commandInput.deleteCharBehindCursor();
+ this.commandInput.deleteCharBehindCursor();
} else if (key == 'Enter') {
- executeCurrentCommand();
+ this.executeCurrentCommand();
} else if (key == 'PageUp') {
- messageLog.pageUp();
+ this.messageLog.pageUp();
} else if (key == 'PageDown') {
- messageLog.pageDown();
+ this.messageLog.pageDown();
} else if (key == 'Delete') {
- commandInput.deleteCharAtCursor();
+ this.commandInput.deleteCharAtCursor();
} else if (key == 'Home') {
if (event.ctrlKey) {
- messageLog.toHead();
+ this.messageLog.toHead();
} else {
- commandInput.moveCursor(-1000);
+ this.commandInput.moveCursor(-1000);
}
} else if (key == 'End') {
if (event.ctrlKey) {
- messageLog.toTail();
+ this.messageLog.toTail();
} else {
- commandInput.moveCursor(1000);
+ this.commandInput.moveCursor(1000);
}
}
}
-function onKeyup(event) {}
+DebugConsole.prototype.onKeyup = function(event) {}
-function onKeypress(event) {
- var mode = window.debugHub.getDebugConsoleMode();
- if (mode >= window.debugHub.DEBUG_CONSOLE_ON) {
- event.preventDefault();
- event.stopPropagation();
- var c = event.charCode;
- // If we have a printable character, insert it; otherwise ignore.
- if (c >= 0x20 && c <= 0x7e) {
- commandInput.insertStringBehindCursor(String.fromCharCode(c));
- }
+DebugConsole.prototype.onKeypress = function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ let c = event.charCode;
+ // If we have a printable character, insert it; otherwise ignore.
+ if (c >= 0x20 && c <= 0x7e) {
+ this.commandInput.insertStringBehindCursor(String.fromCharCode(c));
}
}
-function onInput(event) {
+DebugConsole.prototype.onInput = function(event) {
console.log('In DebugConsole onInput, event.data ' + event.data);
- var mode = window.debugHub.getDebugConsoleMode();
- if (mode >= window.debugHub.DEBUG_CONSOLE_ON && event.data) {
+ if (event.data) {
event.preventDefault();
event.stopPropagation();
- commandInput.insertStringBehindCursor(event.data);
+ this.commandInput.insertStringBehindCursor(event.data);
}
}
-function start() {
- createCommandInput();
- createMessageLog();
- createDebuggerClient();
- showHud(false);
- showConsole(false);
- createConsoleValues();
- initDebugCommands();
- document.addEventListener('wheel', onWheel);
- document.addEventListener('keypress', onKeypress);
- document.addEventListener('keydown', onKeydown);
- document.addEventListener('keyup', onKeyup);
- if (typeof window.onScreenKeyboard != 'undefined'
- && window.onScreenKeyboard) {
- window.onScreenKeyboard.oninput = onInput;
+DebugConsole.prototype.printToLogCallback = function(result) {
+ if (result.wasThrown) {
+ this.printToMessageLog(MessageLog.ERROR,
+ 'Uncaught ' + result.result.description);
+ } else if (result.result.preview) {
+ this.printToMessageLog(MessageLog.INFO, result.result.preview.description);
+ if (result.result.preview.properties) {
+ for (let i = 0; i < result.result.preview.properties.length; ++i) {
+ let property = result.result.preview.properties[i];
+ this.printToMessageLog(MessageLog.INFO,
+ ' ' + property.name + ': ' + property.value);
+ }
+ }
+ if (result.result.preview.overflow) {
+ this.printToMessageLog(MessageLog.INFO, ' ...');
+ }
+ } else if (result.result.description) {
+ this.printToMessageLog(MessageLog.INFO, result.result.description);
+ } else if (result.result.value) {
+ this.printToMessageLog(MessageLog.INFO, result.result.value.toString());
}
- curr = window.performance.now();
- window.requestAnimationFrame(animate);
+ this.printToMessageLog(MessageLog.INFO, '');
}
-
-window.addEventListener('load', start);
-
diff --git a/src/cobalt/debug/console/content/debugger_client.js b/src/cobalt/debug/console/content/debugger_client.js
index cc2b168..2ceb3e0 100644
--- a/src/cobalt/debug/console/content/debugger_client.js
+++ b/src/cobalt/debug/console/content/debugger_client.js
@@ -28,7 +28,7 @@
DebuggerClient.prototype.attach = function() {
if (this.attachState == this.DEBUGGER_DETACHED) {
this.attachState = this.DEBUGGER_ATTACHING;
- printToMessageLog(messageLog.INTERACTIVE,
+ printToMessageLog(MessageLog.INTERACTIVE,
'Attempting to attach to debugger...');
this.scripts = [];
debugHub.onEvent.addListener(this.onEventCallback);
@@ -37,7 +37,7 @@
this.sendCommand('Log.enable');
this.sendCommand('Runtime.enable');
} else if (this.attachState == this.DEBUGGER_ATTACHING) {
- printToMessageLog(messageLog.INTERACTIVE,
+ printToMessageLog(MessageLog.INTERACTIVE,
'Still attempting to attach to debugger...');
}
}
@@ -49,7 +49,7 @@
for (var i in this.scripts) {
var index = this.pad(i, 3);
var scriptUrl = this.scripts[i].url;
- printToMessageLog(messageLog.INTERACTIVE, index + ': ' + scriptUrl);
+ printToMessageLog(MessageLog.INTERACTIVE, index + ': ' + scriptUrl);
}
}
@@ -74,12 +74,11 @@
var lines = scriptSource.split('\n');
for (var i = 0; i < lines.length; i++) {
var index = this.pad(i + 1, 4);
- printToMessageLog(messageLog.INFO, index + ': ' + lines[i]);
+ printToMessageLog(MessageLog.INFO, index + ': ' + lines[i]);
}
}
-DebuggerClient.prototype.evaluate = function(expression) {
- var callback = this.evaluateCallback.bind(this);
+DebuggerClient.prototype.evaluate = function(expression, callback) {
var method = 'Runtime.evaluate';
var params = {};
params.contextId = this.executionContext;
@@ -91,30 +90,6 @@
this.sendCommand(method, params, callback);
}
-DebuggerClient.prototype.evaluateCallback = function(result) {
- if (result.wasThrown) {
- printToMessageLog(messageLog.ERROR,
- 'Uncaught ' + result.result.description);
- } else if (result.result.preview) {
- printToMessageLog(messageLog.INFO, result.result.preview.description);
- if (result.result.preview.properties) {
- for (var i = 0; i < result.result.preview.properties.length; ++i) {
- var property = result.result.preview.properties[i];
- printToMessageLog(messageLog.INFO,
- ' ' + property.name + ': ' + property.value);
- }
- }
- if (result.result.preview.overflow) {
- printToMessageLog(messageLog.INFO, ' ...');
- }
- } else if (result.result.description) {
- printToMessageLog(messageLog.INFO, result.result.description);
- } else if (result.result.value) {
- printToMessageLog(messageLog.INFO, result.result.value.toString());
- }
- printToMessageLog(messageLog.INFO, '');
-}
-
// All debugger commands are routed through this method. Converts the command
// parameters into a JSON string to pass to the debug dispatcher.
DebuggerClient.prototype.sendCommand = function(method, commandParams,
@@ -133,7 +108,7 @@
if (response && response.error) {
printToMessageLog(
- messageLog.ERROR,
+ MessageLog.ERROR,
'[ERROR(' + response.error.code + '):' + method + '] ' +
response.error.message);
} else if (callback) {
@@ -149,10 +124,10 @@
DebuggerClient.prototype.onAttach = function() {
if (debugHub.lastError) {
- printToMessageLog(messageLog.WARNING, 'Could not attach to debugger.');
+ printToMessageLog(MessageLog.WARNING, 'Could not attach to debugger.');
this.attachState = this.DEBUGGER_DETACHED;
} else {
- printToMessageLog(messageLog.INTERACTIVE, 'Debugger attached.');
+ printToMessageLog(MessageLog.INTERACTIVE, 'Debugger attached.');
this.attachState = this.DEBUGGER_ATTACHED;
}
}
@@ -178,13 +153,13 @@
}
DebuggerClient.prototype.onDetached = function() {
- printToMessageLog(messageLog.INTERACTIVE, 'Debugger detached.');
+ printToMessageLog(MessageLog.INTERACTIVE, 'Debugger detached.');
this.attachState = this.DEBUGGER_DETACHED;
}
DebuggerClient.prototype.onExecutionContextCreated = function(params) {
this.executionContext = params.context.id;
- printToMessageLog(messageLog.INFO,
+ printToMessageLog(MessageLog.INFO,
'Execution context created: ' + this.executionContext);
}
@@ -205,9 +180,9 @@
DebuggerClient.prototype.onConsoleApiCalled = function(params) {
var severity = params.type;
if (severity === "assert") {
- severity = messageLog.ERROR;
+ severity = MessageLog.ERROR;
} else if (severity === "log") {
- severity = messageLog.INFO;
+ severity = MessageLog.INFO;
}
var message = '';
@@ -222,7 +197,7 @@
}
}
- printToMessageLog(messageLog.CONSOLE + severity, message);
+ printToMessageLog(MessageLog.CONSOLE + severity, message);
}
DebuggerClient.prototype.onScriptParsed = function(params) {
diff --git a/src/cobalt/debug/console/content/media_console.js b/src/cobalt/debug/console/content/media_console.js
new file mode 100644
index 0000000..66b33d1
--- /dev/null
+++ b/src/cobalt/debug/console/content/media_console.js
@@ -0,0 +1,221 @@
+// 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.
+
+const kNewline = '\r\n';
+
+const kMediaConsoleFrame = 'mediaConsoleFrame';
+
+const videoCodecFamilies = ['av01', 'avc1', 'avc3', 'hev1', 'hvc1', 'vp09',
+ 'vp8', 'vp9'];
+const audioCodecFamilies = ['ac-3', 'ec-3', 'mp4a', 'opus', 'vorbis'];
+
+const kAltModifier = 'A-';
+const kCtrlModifier = 'C-';
+
+function MediaConsole(debuggerClient) {
+ let mediaConsoleNode = document.getElementById('mediaConsole');
+
+ this.debuggerClient = debuggerClient;
+ this.getDisabledMediaCodecs = window.debugHub.cVal.getValue.bind(
+ window.debugHub.cVal, 'Media.DisabledMediaCodecs');
+ this.setDisabledMediaCodecs = window.debugHub.sendConsoleCommand.bind(
+ window.debugHub, 'disable_media_codecs');
+ this.lastUpdateTime = window.performance.now();
+ this.updatePeriod = 1000;
+
+ // Registry of all the hotkeys and their function handlers.
+ // NOTE: This is not the full list, since more will be added once the codec
+ // specific controls are registered.
+ this.hotkeyRegistry = new Map([
+ ['p', {handler: this.onPlayPause.bind(this), help: '(p)ause/(p)lay'}],
+ [']', {handler: this.onIncreasePlaybackRate.bind(this),
+ help: '(])Increase Playback Rate'}],
+ ['[', {handler: this.onDecreasePlaybackRate.bind(this),
+ help: '([)Decrease Playback Rate'}],
+ ]);
+
+ this.hotkeyHelpNode = document.createTextNode('');
+ mediaConsoleNode.appendChild(this.hotkeyHelpNode);
+
+ // Dynamically added div will be changed as we get state information.
+ let playerStateElem = document.createElement('div');
+ this.playerStateText = document.createTextNode('');
+ playerStateElem.appendChild(this.playerStateText);
+ mediaConsoleNode.appendChild(playerStateElem);
+
+ this.printToMediaConsoleCallback = this.printToMediaConsole.bind(this);
+ this.codecSupportMap = new Map();
+ this.isInitialized = false;
+}
+
+MediaConsole.prototype.setVisible = function(visible) {
+ if (visible) {
+ this.initialize();
+ }
+}
+
+MediaConsole.prototype.initialize = function() {
+ if (this.initialized) { return; }
+
+ this.debuggerClient.attach();
+ this.initializeMediaConsoleContext();
+ this.initializeSupportedCodecs();
+
+ // Since |initializeSupportedCodecs| is resolved asynchronously, we need to
+ // wait until |codecSupportMap| is fully populated before finishing the rest
+ // of the initialization.
+ if (this.codecSupportMap.size > 0) {
+ this.registerCodecHotkeys();
+ this.generateHotkeyHelpText();
+ this.isInitialized = true;
+ }
+}
+
+MediaConsole.prototype.initializeMediaConsoleContext = function() {
+ let js = debugHub.readDebugContentText('console/media_console_context.js');
+ this.debuggerClient.evaluate(js);
+}
+
+MediaConsole.prototype.initializeSupportedCodecs = function() {
+ if (this.codecSupportMap.size > 0) {
+ return;
+ }
+
+ function handleSupportQueryResponse(response) {
+ let results = JSON.parse(response.result.value);
+ results.forEach(record => {
+ let codecFamily = record[0];
+ let isSupported = record[1] || !!this.codecSupportMap.get(codecFamily);
+ this.codecSupportMap.set(codecFamily, isSupported);
+ });
+ };
+
+ this.debuggerClient.evaluate(`_mediaConsoleContext.getSupportedCodecs()`,
+ handleSupportQueryResponse.bind(this));
+}
+
+MediaConsole.prototype.registerCodecHotkeys = function() {
+ // Codec control hotkeys are of the form: Modifier + Number.
+ // Due to the large number of codecs, we split all the video codecs into Ctrl
+ // and all the audio codecs into Alt.
+ // "C-" prefix indicates a key with the ctrlKey modifier.
+ // "A-" prefix indicates a key with the altKey modifier.
+ // Additionally, we only register those hotkeys that we filter as supported.
+ function registerCodecHotkey(modifier, codec, index) {
+ if (!this.codecSupportMap.get(codec)) {
+ return;
+ }
+ let modAndNum = `${modifier}${index + 1}`;
+ let helpStr = `( ${modAndNum} ) ${codec}`;
+ this.hotkeyRegistry.set(modAndNum, {
+ handler: this.onToggleCodec.bind(this, codec),
+ help: helpStr
+ });
+ };
+ videoCodecFamilies.forEach(registerCodecHotkey.bind(this, kCtrlModifier));
+ audioCodecFamilies.forEach(registerCodecHotkey.bind(this, kAltModifier));
+}
+
+MediaConsole.prototype.generateHotkeyHelpText = function() {
+ // Generate the help text that will be displayed at the top of the console
+ // output.
+ let hotkeysHelp = `Hotkeys: ${kNewline}`;
+ hotkeysHelp += `Ctrl+Num toggles video codecs ${kNewline}`;
+ hotkeysHelp += `Alt+Num toggles audio codecs ${kNewline}`;
+ const generateHelpText = function(hotkeyInfo, hotkey, map) {
+ hotkeysHelp += hotkeyInfo.help + ', ';
+ };
+ this.hotkeyRegistry.forEach(generateHelpText);
+ hotkeysHelp = hotkeysHelp.substring(0, hotkeysHelp.length-2);
+ hotkeysHelp += kNewline;
+ hotkeysHelp += kNewline;
+ hotkeysHelp += kNewline;
+
+ this.hotkeyHelpNode.nodeValue = hotkeysHelp;
+}
+
+MediaConsole.prototype.parseStateFromResponse = function(response) {
+ // TODO: Handle the situation where the response contains exceptionDetails.
+ // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-evaluate
+ return JSON.parse(response.result.value);
+}
+
+MediaConsole.prototype.printToMediaConsole = function(response) {
+ const state = this.parseStateFromResponse(response);
+ let videoStatus = 'No primary video.';
+ if(state.hasPrimaryVideo) { videoStatus = ''; }
+ this.playerStateText.textContent =
+ `Primary Video: ${videoStatus} ${kNewline} \
+ Paused: ${state.paused} ${kNewline} \
+ Current Time: ${state.currentTime} ${kNewline} \
+ Duration: ${state.duration} ${kNewline} \
+ Playback Rate: ${state.playbackRate} \
+ (default: ${state.defaultPlaybackRate}) ${kNewline} \
+ Disabled Media Codecs: ${state.disabledMediaCodecs}`;
+}
+
+MediaConsole.prototype.update = function() {
+ const t = window.performance.now();
+ if (t > this.lastUpdateTime + this.updatePeriod) {
+ this.lastUpdateTime = t;
+ this.debuggerClient.evaluate('_mediaConsoleContext.getPlayerState()',
+ this.printToMediaConsoleCallback);
+ }
+}
+
+MediaConsole.prototype.onWheel = function(event) {}
+
+MediaConsole.prototype.onKeyup = function(event) {}
+
+MediaConsole.prototype.onKeypress = function(event) {}
+
+MediaConsole.prototype.onInput = function(event) {}
+
+MediaConsole.prototype.onKeydown = function(event) {
+ let matchedHotkeyEntry = null;
+ this.hotkeyRegistry.forEach(function(hotkeyInfo, hotkey, map) {
+ let eventKey = event.key;
+ if(event.altKey) { eventKey = kAltModifier + eventKey; }
+ if(event.ctrlKey) { eventKey = kCtrlModifier + eventKey; }
+ if(eventKey == hotkey) {
+ matchedHotkeyEntry = hotkeyInfo;
+ }
+ });
+ if (matchedHotkeyEntry) {
+ matchedHotkeyEntry.handler();
+ }
+}
+
+MediaConsole.prototype.onPlayPause = function() {
+ this.debuggerClient.evaluate('_mediaConsoleContext.togglePlayPause()',
+ this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onIncreasePlaybackRate = function() {
+ this.debuggerClient.evaluate('_mediaConsoleContext.increasePlaybackRate()',
+ this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onDecreasePlaybackRate = function() {
+ this.debuggerClient.evaluate('_mediaConsoleContext.decreasePlaybackRate()',
+ this.printToMediaConsoleCallback);
+}
+
+MediaConsole.prototype.onToggleCodec = function(codecToToggle) {
+ let codecs = this.getDisabledMediaCodecs().split(";");
+ codecs = codecs.filter(s => s.length > 0);
+ toggled = codecs.filter(c => c != codecToToggle);
+ if(codecs.length == toggled.length) { toggled.push(codecToToggle); }
+ this.setDisabledMediaCodecs(toggled.join(';'));
+}
diff --git a/src/cobalt/debug/console/content/media_console_context.js b/src/cobalt/debug/console/content/media_console_context.js
new file mode 100644
index 0000000..bd0df0f
--- /dev/null
+++ b/src/cobalt/debug/console/content/media_console_context.js
@@ -0,0 +1,162 @@
+// 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.
+
+(function() {
+
+let ctx = window._mediaConsoleContext = {};
+
+// NOTE: Place all "private" members and methods of |_mediaConsoleContext|
+// below. Private functions are not attached to the |_mediaConsoleContext|
+// directly. Rather they are referenced within the public functions, which
+// prevent it from being garbage collected.
+
+const kPlaybackRates = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0];
+
+function getPrimaryVideo() {
+ const elem = document.querySelectorAll('video');
+ if (elem && elem.length > 0) {
+ let primary = null;
+ for (let i = 0; i < elem.length; i++) {
+ const rect = elem[i].getBoundingClientRect();
+ if (rect.width == window.innerWidth &&
+ rect.height == window.innerHeight) {
+ if (primary != null) {
+ console.warn('Two video elements found with the same\
+ dimensions as the main window.');
+ }
+ primary = elem[i];
+ }
+ }
+ return primary;
+ }
+ return null;
+};
+
+function extractState(video, disabledMediaCodecs) {
+ let state = {};
+ state.disabledMediaCodecs = disabledMediaCodecs;
+ state.hasPrimaryVideo = false;
+ if (video) {
+ state.hasPrimaryVideo = true;
+ state.paused = video.paused;
+ state.currentTime = video.currentTime;
+ state.duration = video.duration;
+ state.defaultPlaybackRate = video.defaultPlaybackRate;
+ state.playbackRate = video.playbackRate;
+ }
+ return JSON.stringify(state);
+};
+
+function getDisabledMediaCodecs() {
+ return window.h5vcc.cVal.getValue("Media.DisabledMediaCodecs");
+};
+
+// NOTE: Place all public members and methods of |_mediaConsoleContext|
+// below. They form closures with the above "private" members and methods
+// and hence can use them directly, without referencing |this|.
+
+ctx.getPlayerState = function() {
+ const video = getPrimaryVideo();
+ const disabledMediaCodecs = getDisabledMediaCodecs();
+ return extractState(video, disabledMediaCodecs);
+};
+
+ctx.togglePlayPause = function() {
+ let video = getPrimaryVideo();
+ if (video) {
+ if (video.paused) {
+ video.play();
+ } else {
+ video.pause();
+ }
+ }
+ return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.increasePlaybackRate = function() {
+ let video = getPrimaryVideo();
+ if (video) {
+ let i = kPlaybackRates.indexOf(video.playbackRate);
+ i = Math.min(i + 1, kPlaybackRates.length - 1);
+ video.playbackRate = kPlaybackRates[i];
+ }
+ return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.decreasePlaybackRate = function() {
+ let video = getPrimaryVideo();
+ if (video) {
+ let i = kPlaybackRates.indexOf(video.playbackRate);
+ i = Math.max(i - 1, 0);
+ video.playbackRate = kPlaybackRates[i];
+ }
+ return extractState(video, getDisabledMediaCodecs());
+};
+
+ctx.getSupportedCodecs = function() {
+ // By querying all the possible mime and codec pairs, we can determine
+ // which codecs are valid to control and toggle. We use arbitrarily-
+ // chosen codec subformats to determine if the entire family is
+ // supported.
+ const kVideoCodecs = [
+ 'av01.0.04M.10.0.110.09.16.09.0',
+ 'avc1.640028',
+ 'hvc1.1.2.L93.B0',
+ 'vp09.02.10.10.01.09.16.09.01',
+ 'vp8',
+ 'vp9',
+ ];
+ const kVideoMIMEs = [
+ 'video/mpeg',
+ 'video/mp4',
+ 'video/ogg',
+ 'video/webm',
+ ];
+ const kAudioCodecs = [
+ 'ac-3',
+ 'ec-3',
+ 'mp4a.40.2',
+ 'opus',
+ 'vorbis',
+ ];
+ const kAudioMIMEs = [
+ 'audio/flac',
+ 'audio/mpeg',
+ 'audio/mp3',
+ 'audio/mp4',
+ 'audio/ogg',
+ 'audio/wav',
+ 'audio/webm',
+ 'audio/x-m4a',
+ ];
+
+ let results = [];
+ kVideoMIMEs.forEach(mime => {
+ kVideoCodecs.forEach(codec => {
+ let family = codec.split('.')[0];
+ let mimeCodec = mime + '; codecs ="' + codec + '"';
+ results.push([family, MediaSource.isTypeSupported(mimeCodec)]);
+ })
+ });
+ kAudioMIMEs.forEach(mime => {
+ kAudioCodecs.forEach(codec => {
+ let family = codec.split('.')[0];
+ let mimeCodec = mime + '; codecs ="' + codec + '"';
+ results.push([family, MediaSource.isTypeSupported(mimeCodec)]);
+ })
+ });
+ return JSON.stringify(results);
+}
+
+})()
diff --git a/src/cobalt/debug/console/content/message_log.js b/src/cobalt/debug/console/content/message_log.js
index 03433bf..c22b510 100644
--- a/src/cobalt/debug/console/content/message_log.js
+++ b/src/cobalt/debug/console/content/message_log.js
@@ -77,16 +77,6 @@
// Constructor for the message log object itself.
function MessageLog(messageContainer) {
- // Log levels defined by the 'level' property of LogEntry
- // https://chromedevtools.github.io/devtools-protocol/1-3/Log#type-LogEntry
- this.VERBOSE = "verbose";
- this.INFO = "info";
- this.WARNING = "warning";
- this.ERROR = "error";
- // Custom level used internally by the console.
- this.INTERACTIVE = "interactive";
- // Prefix on severity for messages from the JS console.
- this.CONSOLE = '*';
// Number of items to display on a single page.
this.PAGE_SIZE = 50;
// Number of items to scroll when the user pages up or down.
@@ -103,6 +93,17 @@
this.visible = false;
}
+// Log levels defined by the 'level' property of LogEntry
+// https://chromedevtools.github.io/devtools-protocol/1-3/Log#type-LogEntry
+MessageLog.VERBOSE = "verbose";
+MessageLog.INFO = "info";
+MessageLog.WARNING = "warning";
+MessageLog.ERROR = "error";
+// Custom level used internally by the console.
+MessageLog.INTERACTIVE = "interactive";
+// Prefix on severity for messages from the JS console.
+MessageLog.CONSOLE = '*';
+
MessageLog.prototype.setVisible = function(visible) {
var wasVisible = this.visible;
this.visible = visible;
@@ -131,8 +132,8 @@
elem.className = 'message';
var text = document.createTextNode(message);
- if (severity.startsWith(this.CONSOLE)) {
- severity = severity.substr(this.CONSOLE.length);
+ if (severity.startsWith(MessageLog.CONSOLE)) {
+ severity = severity.substr(MessageLog.CONSOLE.length);
elem.classList.add('console');
}
diff --git a/src/cobalt/debug/console/debug_console_mode.idl b/src/cobalt/debug/console/debug_console_mode.idl
new file mode 100644
index 0000000..04163e2
--- /dev/null
+++ b/src/cobalt/debug/console/debug_console_mode.idl
@@ -0,0 +1,25 @@
+// 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.
+
+// Custom interface to communicate with the debugger, modeled after
+// chrome.debugger extension API.
+// https://developer.chrome.com/extensions/debugger
+
+enum DebugConsoleMode {
+ "off",
+ "hud",
+ "debug",
+ "media",
+ "count"
+};
diff --git a/src/cobalt/debug/console/debug_hub.cc b/src/cobalt/debug/console/debug_hub.cc
index e8760d5..2d9ecdc 100644
--- a/src/cobalt/debug/console/debug_hub.cc
+++ b/src/cobalt/debug/console/debug_hub.cc
@@ -17,9 +17,13 @@
#include <memory>
#include <set>
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/json/json_writer.h"
+#include "base/path_service.h"
#include "base/values.h"
#include "cobalt/base/c_val.h"
+#include "cobalt/base/cobalt_paths.h"
#include "cobalt/debug/console/command_manager.h"
#include "cobalt/debug/json_object.h"
@@ -27,6 +31,10 @@
namespace debug {
namespace console {
+namespace {
+constexpr char kContentDir[] = "cobalt/debug";
+} // namespace
+
DebugHub::DebugHub(
const GetHudModeCallback& get_hud_mode_callback,
const CreateDebugClientCallback& create_debug_client_callback)
@@ -37,7 +45,7 @@
DebugHub::~DebugHub() {}
-int DebugHub::GetDebugConsoleMode() const {
+debug::console::DebugConsoleMode DebugHub::GetDebugConsoleMode() const {
return get_hud_mode_callback_.Run();
}
@@ -62,6 +70,21 @@
callback_reference.value().Run();
}
+std::string DebugHub::ReadDebugContentText(const std::string& filename) {
+ std::string result;
+
+ base::FilePath file_path;
+ base::PathService::Get(paths::DIR_COBALT_WEB_ROOT, &file_path);
+ file_path = file_path.AppendASCII(kContentDir);
+ file_path = file_path.AppendASCII(filename);
+
+ std::string text;
+ if (!base::ReadFileToString(file_path, &text)) {
+ DLOG(WARNING) << "Cannot read file: " << file_path.value();
+ }
+ return text;
+}
+
void DebugHub::SendCommand(const std::string& method,
const std::string& json_params,
const ResponseCallbackArg& callback) {
diff --git a/src/cobalt/debug/console/debug_hub.h b/src/cobalt/debug/console/debug_hub.h
index cb02444..319e862 100644
--- a/src/cobalt/debug/console/debug_hub.h
+++ b/src/cobalt/debug/console/debug_hub.h
@@ -22,6 +22,7 @@
#include "base/message_loop/message_loop.h"
#include "base/optional.h"
#include "cobalt/debug/console/console_command.h"
+#include "cobalt/debug/console/debug_console_mode.h"
#include "cobalt/debug/console/debugger_event_target.h"
#include "cobalt/debug/debug_client.h"
#include "cobalt/dom/c_val_view.h"
@@ -43,7 +44,7 @@
public:
// Function signature to call when we need to query for the Hud visibility
// mode.
- typedef base::Callback<int()> GetHudModeCallback;
+ typedef base::Callback<debug::console::DebugConsoleMode()> GetHudModeCallback;
// JavaScript callback to be run when debugger attaches/detaches.
typedef script::CallbackFunction<void()> AttachCallback;
@@ -54,12 +55,6 @@
ResponseCallback;
typedef script::ScriptValue<ResponseCallback> ResponseCallbackArg;
- // Debug console visibility modes.
- static const int kDebugConsoleOff = 0;
- static const int kDebugConsoleHud = 1;
- static const int kDebugConsoleOn = 2;
- static const int kDebugConsoleNumModes = kDebugConsoleOn + 1;
-
// Thread-safe ref-counted struct used to pass asynchronously executed
// response callbacks around. Stores the message loop the callback must be
// executed on as well as the callback itself.
@@ -80,11 +75,15 @@
const scoped_refptr<dom::CValView>& c_val() const { return c_val_; }
- int GetDebugConsoleMode() const;
+ debug::console::DebugConsoleMode GetDebugConsoleMode() const;
void Attach(const AttachCallbackArg& callback);
void Detach(const AttachCallbackArg& callback);
+ // Read a text file from the content/web/cobalt/debug/ directory and return
+ // its contents.
+ std::string ReadDebugContentText(const std::string& filename);
+
// Sends a devtools protocol command to be executed in the context of the main
// WebModule that is being debugged.
void SendCommand(const std::string& method, const std::string& json_params,
diff --git a/src/cobalt/debug/console/debug_hub.idl b/src/cobalt/debug/console/debug_hub.idl
index 21af7d6..a9afb03 100644
--- a/src/cobalt/debug/console/debug_hub.idl
+++ b/src/cobalt/debug/console/debug_hub.idl
@@ -19,17 +19,14 @@
[
Conditional=ENABLE_DEBUGGER
] interface DebugHub {
- const long DEBUG_CONSOLE_OFF = 0;
- const long DEBUG_CONSOLE_HUD = 1;
- const long DEBUG_CONSOLE_ON = 2;
-
readonly attribute CValView cVal;
- long getDebugConsoleMode();
+ DebugConsoleMode getDebugConsoleMode();
void attach(AttachCallback cb);
void detach(AttachCallback cb);
+ DOMString readDebugContentText(DOMString filename);
void sendCommand(DOMString method, DOMString jsonParams, ResponseCallback cb);
readonly attribute DOMString? lastError;
diff --git a/src/cobalt/debug/debug.gyp b/src/cobalt/debug/debug.gyp
index 059acf9..35da044 100644
--- a/src/cobalt/debug/debug.gyp
+++ b/src/cobalt/debug/debug.gyp
@@ -40,6 +40,8 @@
'backend/dom_agent.h',
'backend/log_agent.cc',
'backend/log_agent.h',
+ 'backend/overlay_agent.cc',
+ 'backend/overlay_agent.h',
'backend/page_agent.cc',
'backend/page_agent.h',
'backend/render_layer.cc',
diff --git a/src/cobalt/doc/web_debugging.md b/src/cobalt/doc/web_debugging.md
index b43a59b..c4711db 100644
--- a/src/cobalt/doc/web_debugging.md
+++ b/src/cobalt/doc/web_debugging.md
@@ -158,9 +158,6 @@
## Tips
-* Don't open Cobalt's console overlay before attaching the remote DevTools as
- that can cause the latter not to work.
-
* You can make Cobalt reload the current page by pressing F5 in the Cobalt
window, or ctrl-R in the remote DevTools. This may be useful for debugging
startup code in the web app. It may also help in case some source file is
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp
index 7dd6d14..29a0a2f 100644
--- a/src/cobalt/dom/dom_test.gyp
+++ b/src/cobalt/dom/dom_test.gyp
@@ -77,10 +77,10 @@
'<(DEPTH)/cobalt/loader/loader.gyp:loader',
'<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
'<(DEPTH)/cobalt/speech/speech.gyp:speech',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/dom/eme/media_key_session.cc b/src/cobalt/dom/eme/media_key_session.cc
index 3570c14..5a9c775 100644
--- a/src/cobalt/dom/eme/media_key_session.cc
+++ b/src/cobalt/dom/eme/media_key_session.cc
@@ -215,12 +215,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();
@@ -416,6 +414,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 1d3a522..7b7483c 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -535,7 +535,7 @@
// has no associated scrolling box, or the element has no overflow,
// terminate these steps.
if (!layout_boxes_ ||
- scroll_width() <= layout_boxes_->GetPaddingEdgeWidth() ) {
+ scroll_width() <= layout_boxes_->GetPaddingEdgeWidth()) {
// Make sure the UI navigation container is set to the expected 0.
x = 0.0f;
}
@@ -597,7 +597,7 @@
}
// Algorithm for offsetParent:
-// https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-htmlelement-offsetparent
+// https://drafts.csswg.org/date/2019-10-11/cssom-view/#extensions-to-the-htmlelement-interface
Element* HTMLElement::offset_parent() {
DCHECK(node_document());
node_document()->DoSynchronousLayout();
@@ -617,7 +617,9 @@
// 2. Return the nearest ancestor element of the element for which at least
// one of the following is true and terminate this algorithm if such an
// ancestor is found:
- // . The computed value of the 'position' property is not 'static'.
+ // . The element is a containing block of absolutely-positioned descendants
+ // (regardless of whether there are any absolutely-positioned
+ // descendants).
// . It is the HTML body element.
for (Node* ancestor_node = parent_node(); ancestor_node;
ancestor_node = ancestor_node->parent_node()) {
@@ -631,8 +633,8 @@
}
DCHECK(ancestor_html_element->computed_style());
if (ancestor_html_element->AsHTMLBodyElement() ||
- ancestor_html_element->computed_style()->position() !=
- cssom::KeywordValue::GetStatic()) {
+ ancestor_html_element->computed_style()
+ ->IsContainingBlockForPositionAbsoluteElements()) {
return ancestor_element;
}
}
@@ -1144,24 +1146,23 @@
directionality_ = base::nullopt;
}
-void HTMLElement::OnUiNavBlur() {
- Blur();
-}
+void HTMLElement::OnUiNavBlur() { Blur(); }
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() {
Document* document = node_document();
scoped_refptr<Window> window(document ? document->window() : nullptr);
- DispatchEvent(new UIEvent(base::Tokens::scroll(),
- Event::kBubbles, Event::kNotCancelable, window));
+ DispatchEvent(new UIEvent(base::Tokens::scroll(), Event::kBubbles,
+ Event::kNotCancelable, window));
}
HTMLElement::HTMLElement(Document* document, base::Token local_name)
@@ -1342,7 +1343,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 5166bf8..d1f548d 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -493,6 +493,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/dom/source_buffer.cc b/src/cobalt/dom/source_buffer.cc
index 08ea6e4..b99fde2 100644
--- a/src/cobalt/dom/source_buffer.cc
+++ b/src/cobalt/dom/source_buffer.cc
@@ -45,9 +45,6 @@
#include "cobalt/dom/source_buffer.h"
#include <algorithm>
-#include <limits>
-#include <memory>
-#include <vector>
#include "base/compiler_specific.h"
#include "base/logging.h"
@@ -94,21 +91,11 @@
id_(id),
chunk_demuxer_(chunk_demuxer),
media_source_(media_source),
- track_defaults_(new TrackDefaultList(NULL)),
event_queue_(event_queue),
- mode_(kSourceBufferAppendModeSegments),
- updating_(false),
- timestamp_offset_(0),
audio_tracks_(
new AudioTrackList(settings, media_source->GetMediaElement())),
video_tracks_(
- new VideoTrackList(settings, media_source->GetMediaElement())),
- append_window_start_(0),
- append_window_end_(std::numeric_limits<double>::infinity()),
- first_initialization_segment_received_(false),
- pending_append_data_offset_(0),
- pending_remove_start_(-1),
- pending_remove_end_(-1) {
+ new VideoTrackList(settings, media_source->GetMediaElement())) {
DCHECK(!id_.empty());
DCHECK(media_source_);
DCHECK(chunk_demuxer);
@@ -340,6 +327,9 @@
chunk_demuxer_ = NULL;
media_source_ = NULL;
event_queue_ = NULL;
+
+ pending_append_data_.reset();
+ pending_append_data_capacity_ = 0;
}
double SourceBuffer::GetHighestPresentationTimestamp() const {
@@ -419,8 +409,15 @@
DCHECK(data || size == 0);
if (data) {
- pending_append_data_.insert(pending_append_data_.end(), data, data + size);
+ DCHECK_EQ(pending_append_data_offset_, 0u);
+ if (pending_append_data_capacity_ < size) {
+ pending_append_data_.reset();
+ pending_append_data_.reset(new uint8_t[size]);
+ pending_append_data_capacity_ = size;
+ }
+ SbMemoryCopy(pending_append_data_.get(), data, size);
}
+ pending_append_data_size_ = size;
pending_append_data_offset_ = 0;
updating_ = true;
@@ -436,15 +433,14 @@
DCHECK(updating_);
- DCHECK_GE(pending_append_data_.size(), pending_append_data_offset_);
- size_t append_size =
- pending_append_data_.size() - pending_append_data_offset_;
+ DCHECK_GE(pending_append_data_size_, pending_append_data_offset_);
+ size_t append_size = pending_append_data_size_ - pending_append_data_offset_;
append_size = std::min(append_size, kMaxAppendSize);
- uint8 dummy[1];
+ uint8_t dummy;
const uint8* data_to_append =
- append_size > 0 ? &pending_append_data_[0] + pending_append_data_offset_
- : dummy;
+ append_size > 0 ? pending_append_data_.get() + pending_append_data_offset_
+ : &dummy;
base::TimeDelta timestamp_offset = DoubleToTimeDelta(timestamp_offset_);
bool success = chunk_demuxer_->AppendData(
@@ -456,20 +452,20 @@
}
if (!success) {
- pending_append_data_.clear();
+ pending_append_data_size_ = 0;
pending_append_data_offset_ = 0;
AppendError();
} else {
pending_append_data_offset_ += append_size;
- if (pending_append_data_offset_ < pending_append_data_.size()) {
+ if (pending_append_data_offset_ < pending_append_data_size_) {
append_timer_.Start(FROM_HERE, base::TimeDelta(), this,
&SourceBuffer::OnAppendTimer);
return;
}
updating_ = false;
- pending_append_data_.clear();
+ pending_append_data_size_ = 0;
pending_append_data_offset_ = 0;
ScheduleEvent(base::Tokens::update());
@@ -524,7 +520,7 @@
DCHECK_EQ(pending_remove_start_, -1);
append_timer_.Stop();
- pending_append_data_.clear();
+ pending_append_data_size_ = 0;
pending_append_data_offset_ = 0;
updating_ = false;
diff --git a/src/cobalt/dom/source_buffer.h b/src/cobalt/dom/source_buffer.h
index efdd34b..2aa4a3f 100644
--- a/src/cobalt/dom/source_buffer.h
+++ b/src/cobalt/dom/source_buffer.h
@@ -45,6 +45,7 @@
#ifndef COBALT_DOM_SOURCE_BUFFER_H_
#define COBALT_DOM_SOURCE_BUFFER_H_
+#include <limits>
#include <memory>
#include <string>
#include <vector>
@@ -170,25 +171,27 @@
const std::string id_;
media::ChunkDemuxer* chunk_demuxer_;
MediaSource* media_source_;
- scoped_refptr<TrackDefaultList> track_defaults_;
+ scoped_refptr<TrackDefaultList> track_defaults_ = new TrackDefaultList(NULL);
EventQueue* event_queue_;
- SourceBufferAppendMode mode_;
- bool updating_;
- double timestamp_offset_;
+ SourceBufferAppendMode mode_ = kSourceBufferAppendModeSegments;
+ bool updating_ = false;
+ double timestamp_offset_ = 0;
scoped_refptr<AudioTrackList> audio_tracks_;
scoped_refptr<VideoTrackList> video_tracks_;
- double append_window_start_;
- double append_window_end_;
+ double append_window_start_ = 0;
+ double append_window_end_ = std::numeric_limits<double>::infinity();
base::OneShotTimer append_timer_;
- bool first_initialization_segment_received_;
- std::vector<uint8_t> pending_append_data_;
- size_t pending_append_data_offset_;
+ bool first_initialization_segment_received_ = false;
+ std::unique_ptr<uint8_t[]> pending_append_data_;
+ size_t pending_append_data_capacity_ = 0;
+ size_t pending_append_data_size_ = 0;
+ size_t pending_append_data_offset_ = 0;
base::OneShotTimer remove_timer_;
- double pending_remove_start_;
- double pending_remove_end_;
+ double pending_remove_start_ = -1;
+ double pending_remove_end_ = -1;
};
} // namespace dom
diff --git a/src/cobalt/dom_parser/dom_parser_test.gyp b/src/cobalt/dom_parser/dom_parser_test.gyp
index 6730d25..aedb612 100644
--- a/src/cobalt/dom_parser/dom_parser_test.gyp
+++ b/src/cobalt/dom_parser/dom_parser_test.gyp
@@ -28,10 +28,10 @@
'<(DEPTH)/cobalt/dom/dom.gyp:dom',
'<(DEPTH)/cobalt/dom/testing/dom_testing.gyp:dom_testing',
'<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/dom_parser/html_decoder.cc b/src/cobalt/dom_parser/html_decoder.cc
index 0adbef0..b662528 100644
--- a/src/cobalt/dom_parser/html_decoder.cc
+++ b/src/cobalt/dom_parser/html_decoder.cc
@@ -64,7 +64,10 @@
require_csp_ == csp::kCSPOptional) {
return loader::kLoadResponseContinue;
} else {
- DLOG(ERROR) << "Failure receiving Content Security Policy headers";
+ LOG(ERROR) << "Failure receiving Content Security Policy headers "
+ "for URL: " << url_fetcher->GetURL() << ".";
+ LOG(ERROR) << "The server *must* send CSP headers or Cobalt will not "
+ "load the page.";
return loader::kLoadResponseAbort;
}
}
diff --git a/src/cobalt/extension/extension.gyp b/src/cobalt/extension/extension.gyp
index 340fbc9..bf63c0d 100644
--- a/src/cobalt/extension/extension.gyp
+++ b/src/cobalt/extension/extension.gyp
@@ -26,11 +26,11 @@
'dependencies': [
'<@(cobalt_platform_dependencies)',
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/starboard/starboard.gyp:starboard',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'extension_test_deploy',
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 044bdde..2615ba2 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -15,6 +15,7 @@
#include <cmath>
#include "cobalt/extension/graphics.h"
+#include "cobalt/extension/installation_manager.h"
#include "cobalt/extension/platform_service.h"
#include "starboard/system.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -34,7 +35,8 @@
}
EXPECT_STREQ(extension_api->name, kExtensionName);
- EXPECT_EQ(extension_api->version, 1) << "Invalid version";
+ EXPECT_TRUE(extension_api->version == 1 ||
+ extension_api->version == 2) << "Invalid version";
EXPECT_TRUE(extension_api->Has != NULL);
EXPECT_TRUE(extension_api->Open != NULL);
EXPECT_TRUE(extension_api->Close != NULL);
@@ -57,19 +59,52 @@
}
EXPECT_STREQ(extension_api->name, kExtensionName);
- EXPECT_EQ(extension_api->version, 1) << "Invalid version";
+ EXPECT_TRUE(extension_api->version == 1 ||
+ extension_api->version == 2) << "Invalid version";
EXPECT_TRUE(extension_api->GetMaximumFrameIntervalInMilliseconds != NULL);
+ if (extension_api->version >= 2) {
+ EXPECT_TRUE(extension_api->GetMinimumFrameIntervalInMilliseconds != NULL);
+ }
float maximum_frame_interval =
extension_api->GetMaximumFrameIntervalInMilliseconds();
EXPECT_FALSE(std::isnan(maximum_frame_interval));
+ if (extension_api->version >= 2) {
+ float minimum_frame_interval =
+ extension_api->GetMinimumFrameIntervalInMilliseconds();
+ EXPECT_GT(minimum_frame_interval, 0);
+ }
const ExtensionApi* second_extension_api = static_cast<const ExtensionApi*>(
SbSystemGetExtension(kExtensionName));
EXPECT_EQ(second_extension_api, extension_api)
<< "Extension struct should be a singleton";
}
+TEST(ExtensionTest, InstallationManager) {
+ typedef CobaltExtensionInstallationManagerApi ExtensionApi;
+ const char* kExtensionName = kCobaltExtensionInstallationManagerName;
+
+ const ExtensionApi* extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ if (!extension_api) {
+ return;
+ }
+
+ EXPECT_STREQ(extension_api->name, kExtensionName);
+ EXPECT_TRUE(extension_api->version == 1 ||
+ extension_api->version == 2) << "Invalid version";
+ EXPECT_TRUE(extension_api->GetCurrentInstallationIndex != NULL);
+ EXPECT_TRUE(extension_api->MarkInstallationSuccessful != NULL);
+ EXPECT_TRUE(extension_api->RequestRollForwardToInstallation != NULL);
+ EXPECT_TRUE(extension_api->GetInstallationPath != NULL);
+ EXPECT_TRUE(extension_api->SelectNewInstallationIndex != NULL);
+
+ const ExtensionApi* second_extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ EXPECT_EQ(second_extension_api, extension_api)
+ << "Extension struct should be a singleton";
+}
} // namespace extension
} // namespace cobalt
#endif // SB_API_VERSION >= 11
diff --git a/src/cobalt/extension/graphics.h b/src/cobalt/extension/graphics.h
index b68ac9f..b0e2bb8 100644
--- a/src/cobalt/extension/graphics.h
+++ b/src/cobalt/extension/graphics.h
@@ -48,6 +48,15 @@
// precedence over this. For example, if the minimum frame time is 8ms and
// the maximum frame interval is 0ms, then the renderer will target 125 fps.
float (*GetMaximumFrameIntervalInMilliseconds)();
+
+ // The fields below this point were added in version 2 or later.
+
+ // Allow throttling of the frame rate. This is expressed in terms of
+ // milliseconds and can be a floating point number. Keep in mind that
+ // swapping frames may take some additional processing time, so it may be
+ // better to specify a lower delay. For example, '33' instead of '33.33'
+ // for 30 Hz refresh.
+ float (*GetMinimumFrameIntervalInMilliseconds)();
} CobaltExtensionGraphicsApi;
#ifdef __cplusplus
diff --git a/src/cobalt/extension/installation_manager.h b/src/cobalt/extension/installation_manager.h
new file mode 100644
index 0000000..4a99cda
--- /dev/null
+++ b/src/cobalt/extension/installation_manager.h
@@ -0,0 +1,55 @@
+// 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 COBALT_EXTENSION_INSTALLATION_MANAGER_H_
+#define COBALT_EXTENSION_INSTALLATION_MANAGER_H_
+
+#include <stdint.h>
+
+#include "starboard/configuration.h"
+
+#define IM_EXT_ERROR -1
+#define IM_EXT_SUCCESS 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kCobaltExtensionInstallationManagerName \
+ "dev.cobalt.extension.InstallationManager"
+
+typedef struct CobaltExtensionInstallationManagerApi {
+ // Name should be the string kCobaltExtensionInstallationManagerName.
+ // This helps to validate that the extension API is correct.
+ const char* name;
+
+ // This specifies the version of the API that is implemented.
+ uint32_t version;
+
+ // Installation Manager API wrapper.
+ // For more details, check:
+ // starboard/loader_app/installation_manager.h
+ int (*GetCurrentInstallationIndex)();
+ int (*MarkInstallationSuccessful)(int installation_index);
+ int (*RequestRollForwardToInstallation)(int installation_index);
+ int (*GetInstallationPath)(int installation_index, char* path,
+ int path_length);
+ int (*SelectNewInstallationIndex)();
+} CobaltExtensionInstallationManagerApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // COBALT_EXTENSION_INSTALLATION_MANAGER_H_
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 85ccbb1..221b04f 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -241,8 +241,7 @@
keypress_generator_filter_.HandleKeyboardEvent(type, keyboard_event);
int32_t key_code_in_int32 = static_cast<int32_t>(key_code);
- overlay_info::OverlayInfoRegistry::Register(
- "input_manager:keydown", &key_code_in_int32, sizeof(key_code_in_int32));
+ overlay_info::OverlayInfoRegistry::Register("keydown", key_code_in_int32);
}
void InputDeviceManagerDesktop::HandlePointerEvent(
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 4eff827..ac4ebed 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -132,11 +132,11 @@
Box::~Box() { layout_stat_tracker_->OnBoxDestroyed(); }
bool Box::IsPositioned() const {
- return computed_style()->position() != cssom::KeywordValue::GetStatic();
+ return computed_style()->IsPositioned();
}
bool Box::IsTransformed() const {
- return computed_style()->transform() != cssom::KeywordValue::GetNone();
+ return computed_style()->IsTransformed();
}
bool Box::IsAbsolutelyPositioned() const {
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index 3c9ef9e..acdddff 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -170,7 +170,8 @@
}
bool ContainerBox::IsContainingBlockForPositionAbsoluteElements() const {
- return parent() == NULL || IsPositioned() || IsTransformed();
+ return parent() == NULL ||
+ computed_style()->IsContainingBlockForPositionAbsoluteElements();
}
bool ContainerBox::IsContainingBlockForPositionFixedElements() const {
diff --git a/src/cobalt/layout/layout.gyp b/src/cobalt/layout/layout.gyp
index bef1686..e608390 100644
--- a/src/cobalt/layout/layout.gyp
+++ b/src/cobalt/layout/layout.gyp
@@ -138,11 +138,11 @@
'dependencies': [
'<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
'<(DEPTH)/cobalt/layout/layout.gyp:layout_testing',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'layout',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/layout/line_box.cc b/src/cobalt/layout/line_box.cc
index a73a401..75b83c1 100644
--- a/src/cobalt/layout/line_box.cc
+++ b/src/cobalt/layout/line_box.cc
@@ -871,9 +871,8 @@
// The start edge offset at which the ellipsis was eventually placed. This
// will be set by TryPlaceEllipsisOrProcessPlacedEllipsis() within one of the
// child boxes.
- // NOTE: While this is is guaranteed to be set later, initializing it here
- // keeps compilers from complaining about it being an uninitialized variable
- // below.
+ // NOTE: While this is guaranteed to be set later, initializing it here keeps
+ // compilers from complaining about it being an uninitialized variable below.
LayoutUnit placed_start_edge_offset;
// Walk each box within the line in base direction order attempting to place
diff --git a/src/cobalt/layout_tests/layout_tests.gyp b/src/cobalt/layout_tests/layout_tests.gyp
index 35683b2..05b34a6 100644
--- a/src/cobalt/layout_tests/layout_tests.gyp
+++ b/src/cobalt/layout_tests/layout_tests.gyp
@@ -65,12 +65,12 @@
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/browser/browser.gyp:browser',
'<(DEPTH)/cobalt/renderer/renderer.gyp:render_tree_pixel_tester',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/url/url.gyp:url',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'layout_test_utils',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
@@ -94,12 +94,12 @@
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/browser/browser.gyp:browser',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/url/url.gyp:url',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'layout_test_utils',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/layout_tests/testdata/csp/img-src.html b/src/cobalt/layout_tests/testdata/csp/img-src.html
index ad628af..20a59ea 100644
--- a/src/cobalt/layout_tests/testdata/csp/img-src.html
+++ b/src/cobalt/layout_tests/testdata/csp/img-src.html
@@ -24,6 +24,7 @@
var div_ids = ["insecure", "secure"];
var urls = ["http://" + image_base, "https://" + image_base];
+ var num_errors = 0;
for (var i = 0; i < 2; i++) {
var divname = div_ids[i];
var url = urls[i];
@@ -36,8 +37,12 @@
}
}
images[i].onerror = function() {
- // NOTE: This won't be called due to an outstanding bug.
+ ++num_errors;
console.log('Error loading: ' + this.src);
+ if (num_errors >= 2) {
+ console.log('More errors than expected!');
+ window.testRunner.notifyDone();
+ }
}
images[i].src = urls[i];
}
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 87cdade..d05d443 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -159,7 +159,6 @@
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/math/math.gyp:math',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/third_party/ots/ots.gyp:ots',
@@ -167,6 +166,7 @@
'loader_copy_test_data',
'<@(cobalt_platform_dependencies)',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc
index 3302418..bf5a097 100644
--- a/src/cobalt/loader/net_fetcher.cc
+++ b/src/cobalt/loader/net_fetcher.cc
@@ -172,11 +172,13 @@
auto* download_data_writer =
base::polymorphic_downcast<URLFetcherStringWriter*>(
source->GetResponseWriter());
- std::unique_ptr<std::string> data = download_data_writer->data();
- if (!data->empty()) {
+ std::string data;
+ download_data_writer->GetAndResetData(&data);
+ if (!data.empty()) {
DLOG(INFO) << "in OnURLFetchComplete data still has bytes: "
- << data->size();
- handler()->OnReceivedPassed(this, std::move(data));
+ << data.size();
+ handler()->OnReceivedPassed(
+ this, std::unique_ptr<std::string>(new std::string(std::move(data))));
}
handler()->OnDone(this);
} else {
@@ -209,15 +211,17 @@
auto* download_data_writer =
base::polymorphic_downcast<URLFetcherStringWriter*>(
source->GetResponseWriter());
- std::unique_ptr<std::string> data = download_data_writer->data();
- if (data->empty()) {
+ std::string data;
+ download_data_writer->GetAndResetData(&data);
+ if (data.empty()) {
return;
}
#if defined(HANDLE_CORE_DUMP)
net_fetcher_log.Get().IncrementFetchedBytes(
- static_cast<int>(data->length()));
+ static_cast<int>(data.length()));
#endif
- handler()->OnReceivedPassed(this, std::move(data));
+ handler()->OnReceivedPassed(
+ this, std::unique_ptr<std::string>(new std::string(std::move(data))));
}
}
diff --git a/src/cobalt/loader/url_fetcher_string_writer.cc b/src/cobalt/loader/url_fetcher_string_writer.cc
index b583799..54ae81a 100644
--- a/src/cobalt/loader/url_fetcher_string_writer.cc
+++ b/src/cobalt/loader/url_fetcher_string_writer.cc
@@ -13,6 +13,8 @@
// limitations under the License.
#include "cobalt/loader/url_fetcher_string_writer.h"
+
+#include "base/logging.h"
#include "net/base/net_errors.h"
namespace cobalt {
@@ -24,6 +26,10 @@
// DCHECK(consumer_task_runner);
// }
+namespace {
+const int64_t kPreAllocateThreshold = 64 * 1024;
+} // namespace
+
URLFetcherStringWriter::URLFetcherStringWriter() = default;
URLFetcherStringWriter::~URLFetcherStringWriter() = default;
@@ -33,22 +39,56 @@
return net::OK;
}
-std::unique_ptr<std::string> URLFetcherStringWriter::data() {
+void URLFetcherStringWriter::OnResponseStarted(int64_t content_length) {
base::AutoLock auto_lock(lock_);
- if (!data_) {
- return std::make_unique<std::string>();
+
+ if (content_length >= 0) {
+ content_length_ = content_length;
}
- return std::move(data_);
+}
+
+bool URLFetcherStringWriter::HasData() const {
+ base::AutoLock auto_lock(lock_);
+ return !data_.empty();
+}
+
+void URLFetcherStringWriter::GetAndResetData(std::string* data) {
+ DCHECK(data);
+
+ std::string empty;
+ data->swap(empty);
+
+ base::AutoLock auto_lock(lock_);
+ data_.swap(*data);
}
int URLFetcherStringWriter::Write(net::IOBuffer* buffer, int num_bytes,
net::CompletionOnceCallback /*callback*/) {
base::AutoLock auto_lock(lock_);
- if (!data_) {
- data_ = std::make_unique<std::string>();
+
+ if (content_offset_ == 0 && num_bytes <= content_length_) {
+ // Pre-allocate the whole buffer for small downloads, hope that all data can
+ // be downloaded before GetAndResetData() is called.
+ if (content_length_ <= kPreAllocateThreshold) {
+ data_.reserve(content_length_);
+ } else {
+ data_.reserve(kPreAllocateThreshold);
+ }
}
- data_->append(buffer->data(), num_bytes);
+ if (content_length_ > 0 && content_length_ > content_offset_ &&
+ data_.size() + num_bytes > data_.capacity()) {
+ // There is not enough memory allocated, and std::string is going to double
+ // the allocation. So a content in "1M + 1" bytes may end up allocating 2M
+ // bytes. Try to reserve the proper size to avoid this.
+ auto content_remaining = content_length_ - content_offset_;
+ if (data_.size() + content_remaining < data_.capacity() * 2) {
+ data_.reserve(data_.size() + content_remaining);
+ }
+ }
+
+ data_.append(buffer->data(), num_bytes);
+ content_offset_ += num_bytes;
// consumer_task_runner_->PostTask(FROM_HERE,
// base::Bind((on_write_callback_.Run), std::move(data)));
return num_bytes;
diff --git a/src/cobalt/loader/url_fetcher_string_writer.h b/src/cobalt/loader/url_fetcher_string_writer.h
index 3bab4a6..72d87e8 100644
--- a/src/cobalt/loader/url_fetcher_string_writer.h
+++ b/src/cobalt/loader/url_fetcher_string_writer.h
@@ -35,19 +35,22 @@
URLFetcherStringWriter();
~URLFetcherStringWriter() override;
- std::unique_ptr<std::string> data();
+ bool HasData() const;
+ void GetAndResetData(std::string* data);
// URLFetcherResponseWriter overrides:
int Initialize(net::CompletionOnceCallback callback) override;
+ void OnResponseStarted(int64_t content_length) override;
int Write(net::IOBuffer* buffer, int num_bytes,
net::CompletionOnceCallback callback) override;
int Finish(int net_error, net::CompletionOnceCallback callback) override;
private:
- // This class can be accessed by both network thread and MainWebModule
- // thread.
- base::Lock lock_;
- std::unique_ptr<std::string> data_;
+ // This class can be accessed by both network thread and MainWebModule thread.
+ mutable base::Lock lock_;
+ int64_t content_length_ = -1;
+ int64_t content_offset_ = 0;
+ std::string data_;
// OnWriteCallback on_write_callback_;
// base::TaskRunner* consumer_task_runner_;
diff --git a/src/cobalt/math/math.gyp b/src/cobalt/math/math.gyp
index 5fc0326..344ec57 100644
--- a/src/cobalt/math/math.gyp
+++ b/src/cobalt/math/math.gyp
@@ -90,10 +90,10 @@
'dependencies': [
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gtest.gyp:gtest',
'math',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'math_test_deploy',
diff --git a/src/cobalt/media/base/drm_system.cc b/src/cobalt/media/base/drm_system.cc
index 8ad4118..b94c858 100644
--- a/src/cobalt/media/base/drm_system.cc
+++ b/src/cobalt/media/base/drm_system.cc
@@ -28,8 +28,7 @@
DECLARE_INSTANCE_COUNTER(DrmSystem);
DrmSystem::Session::Session(
- DrmSystem* drm_system
- ,
+ DrmSystem* drm_system,
SessionUpdateKeyStatusesCallback update_key_statuses_callback
#if SB_HAS(DRM_SESSION_CLOSED)
,
@@ -41,7 +40,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());
@@ -69,7 +69,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);
}
@@ -169,8 +169,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&
@@ -221,6 +221,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)) {
@@ -237,21 +239,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.
@@ -274,15 +280,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);
@@ -306,6 +316,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);
@@ -313,13 +325,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);
@@ -327,9 +344,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)
@@ -337,6 +356,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/fetcher_buffered_data_source.cc b/src/cobalt/media/fetcher_buffered_data_source.cc
index cbf6f41..254f386 100644
--- a/src/cobalt/media/fetcher_buffered_data_source.cc
+++ b/src/cobalt/media/fetcher_buffered_data_source.cc
@@ -211,12 +211,13 @@
auto* download_data_writer =
base::polymorphic_downcast<loader::URLFetcherStringWriter*>(
source->GetResponseWriter());
- std::unique_ptr<std::string> download_data = download_data_writer->data();
- size_t size = download_data->size();
+ std::string downloaded_data;
+ download_data_writer->GetAndResetData(&downloaded_data);
+ size_t size = downloaded_data.size();
if (size == 0) {
return;
}
- const uint8* data = reinterpret_cast<const uint8*>(download_data->data());
+ const uint8* data = reinterpret_cast<const uint8*>(downloaded_data.data());
base::AutoLock auto_lock(lock_);
if (fetcher_.get() != source || error_occured_) {
diff --git a/src/cobalt/media/fetcher_buffered_data_source.h b/src/cobalt/media/fetcher_buffered_data_source.h
index 6eca602..66fef53 100644
--- a/src/cobalt/media/fetcher_buffered_data_source.h
+++ b/src/cobalt/media/fetcher_buffered_data_source.h
@@ -28,6 +28,7 @@
#include "cobalt/csp/content_security_policy.h"
#include "cobalt/loader/fetcher.h"
#include "cobalt/loader/origin.h"
+#include "cobalt/loader/url_fetcher_string_writer.h"
#include "cobalt/media/player/buffered_data_source.h"
#include "cobalt/network/network_module.h"
#include "net/url_request/url_fetcher.h"
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index 7cd8793..38b157f 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -232,7 +232,6 @@
'dependencies': [
'media',
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
@@ -241,6 +240,7 @@
'filters/shell_mp4_map_unittest.cc',
'filters/shell_rbsp_stream_unittest.cc',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'media_test_deploy',
diff --git a/src/cobalt/media/sandbox/fuzzer_app.cc b/src/cobalt/media/sandbox/fuzzer_app.cc
index 9a9d0c2..18f58ae 100644
--- a/src/cobalt/media/sandbox/fuzzer_app.cc
+++ b/src/cobalt/media/sandbox/fuzzer_app.cc
@@ -113,11 +113,21 @@
return;
}
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME);
+
+ while (SbDirectoryGetNext(directory, entry.data(), entry.size())) {
+ std::string file_name = path_name + SB_FILE_SEP_STRING + entry.data();
+ AddFile(file_name, min_ratio, max_ratio, initial_seed);
+ }
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry;
+
while (SbDirectoryGetNext(directory, &entry)) {
std::string file_name = path_name + SB_FILE_SEP_STRING + entry.name;
AddFile(file_name, min_ratio, max_ratio, initial_seed);
}
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryClose(directory);
}
diff --git a/src/cobalt/media_capture/media_capture_test.gyp b/src/cobalt/media_capture/media_capture_test.gyp
index a28069c..2d330ab 100644
--- a/src/cobalt/media_capture/media_capture_test.gyp
+++ b/src/cobalt/media_capture/media_capture_test.gyp
@@ -35,10 +35,10 @@
# For Fake Microphone.
'<(DEPTH)/cobalt/speech/speech.gyp:speech',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/media_session/media_session_test.gyp b/src/cobalt/media_session/media_session_test.gyp
index aa135c1..9c2c8d7 100644
--- a/src/cobalt/media_session/media_session_test.gyp
+++ b/src/cobalt/media_session/media_session_test.gyp
@@ -24,10 +24,10 @@
'<(DEPTH)/cobalt/media_session/media_session.gyp:media_session',
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/browser/browser.gyp:browser',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'media_session_test_deploy',
diff --git a/src/cobalt/media_stream/media_stream_test.gyp b/src/cobalt/media_stream/media_stream_test.gyp
index ac57801..a7cc5b1 100644
--- a/src/cobalt/media_stream/media_stream_test.gyp
+++ b/src/cobalt/media_stream/media_stream_test.gyp
@@ -32,10 +32,10 @@
'dependencies': [
'<(DEPTH)/cobalt/dom/dom.gyp:dom',
'<(DEPTH)/cobalt/media_stream/media_stream.gyp:media_stream',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/network/local_network.cc b/src/cobalt/network/local_network.cc
index 862eb19..07e5765 100644
--- a/src/cobalt/network/local_network.cc
+++ b/src/cobalt/network/local_network.cc
@@ -83,7 +83,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/network/network.gyp b/src/cobalt/network/network.gyp
index 08e7036..c06c9e9 100644
--- a/src/cobalt/network/network.gyp
+++ b/src/cobalt/network/network.gyp
@@ -116,11 +116,11 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'network',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'network_test_deploy',
diff --git a/src/cobalt/overlay_info/overlay_info.gyp b/src/cobalt/overlay_info/overlay_info.gyp
index 78c4b0f..9aab7f5 100644
--- a/src/cobalt/overlay_info/overlay_info.gyp
+++ b/src/cobalt/overlay_info/overlay_info.gyp
@@ -42,9 +42,9 @@
],
'dependencies': [
'<(DEPTH)/cobalt/overlay_info/overlay_info.gyp:overlay_info',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/overlay_info/overlay_info_registry.cc b/src/cobalt/overlay_info/overlay_info_registry.cc
index 6b01448..1341374 100644
--- a/src/cobalt/overlay_info/overlay_info_registry.cc
+++ b/src/cobalt/overlay_info/overlay_info_registry.cc
@@ -32,8 +32,7 @@
void Disable();
void Register(const char* category, const char* str);
- void Register(const char* category, const void* data, size_t data_size);
- void RetrieveAndClear(std::vector<uint8_t>* infos);
+ void RetrieveAndClear(std::string* infos);
private:
// Reserve enough data for |infos_| to avoid extra allcations.
@@ -48,7 +47,7 @@
bool enabled_ = true;
starboard::Mutex mutex_;
- std::vector<uint8_t> infos_;
+ std::string infos_;
};
// static
@@ -64,40 +63,25 @@
infos_.clear();
}
-void OverlayInfoRegistryImpl::Register(const char* category, const char* str) {
- auto length = SbStringGetLength(str);
- Register(category, reinterpret_cast<const uint8_t*>(str), length);
-}
-
-void OverlayInfoRegistryImpl::Register(const char* category, const void* data,
- size_t data_size) {
+void OverlayInfoRegistryImpl::Register(const char* category, const char* data) {
DCHECK(SbStringFindCharacter(
category, static_cast<char>(OverlayInfoRegistry::kDelimiter)) ==
NULL)
<< "Category " << category
<< " cannot contain the delimiter:" << OverlayInfoRegistry::kDelimiter;
- auto category_size = SbStringGetLength(category);
- auto total_size = category_size + 1 + data_size;
-
- DCHECK_GT(category_size, 0u);
- // Use |kMaxSizeOfData + 0| to avoid link error caused by DCHECK_LE.
- DCHECK_LE(total_size, OverlayInfoRegistry::kMaxSizeOfData + 0);
-
- starboard::ScopedLock scoped_lock(mutex_);
- // Use |kMaxNumberOfPendingOverlayInfo + 0| to avoid link error caused by
- // DCHECK_LE.
- DCHECK_LE(infos_.size() + total_size,
- OverlayInfoRegistry::kMaxNumberOfPendingOverlayInfo + 0);
- if (enabled_) {
- infos_.push_back(static_cast<uint8_t>(total_size));
- infos_.insert(infos_.end(), category, category + category_size);
- infos_.push_back(kDelimiter);
- infos_.insert(infos_.end(), static_cast<const uint8_t*>(data),
- static_cast<const uint8_t*>(data) + data_size);
+ DCHECK(SbStringFindCharacter(
+ data, static_cast<char>(OverlayInfoRegistry::kDelimiter)) == NULL)
+ << "Data " << data
+ << " cannot contain the delimiter:" << OverlayInfoRegistry::kDelimiter;
+ if (!infos_.empty()) {
+ infos_ += kDelimiter;
}
+ infos_ += category;
+ infos_ += kDelimiter;
+ infos_ += data;
}
-void OverlayInfoRegistryImpl::RetrieveAndClear(std::vector<uint8_t>* infos) {
+void OverlayInfoRegistryImpl::RetrieveAndClear(std::string* infos) {
DCHECK(infos);
starboard::ScopedLock scoped_lock(mutex_);
@@ -114,16 +98,30 @@
OverlayInfoRegistryImpl::GetInstance()->Disable();
}
-void OverlayInfoRegistry::Register(const char* category, const char* str) {
- OverlayInfoRegistryImpl::GetInstance()->Register(category, str);
+void OverlayInfoRegistry::Register(const char* category, const char* data) {
+ OverlayInfoRegistryImpl::GetInstance()->Register(category, data);
}
void OverlayInfoRegistry::Register(const char* category, const void* data,
size_t data_size) {
- OverlayInfoRegistryImpl::GetInstance()->Register(category, data, data_size);
+ const char kHex[] = "0123456789abcdef";
+
+ const uint8_t* data_as_bytes = static_cast<const uint8_t*>(data);
+ std::string data_in_hex;
+
+ data_in_hex.reserve(data_size * 2);
+
+ while (data_size > 0) {
+ data_in_hex += kHex[*data_as_bytes / 16];
+ data_in_hex += kHex[*data_as_bytes % 16];
+ ++data_as_bytes;
+ --data_size;
+ }
+ OverlayInfoRegistryImpl::GetInstance()->Register(category,
+ data_in_hex.c_str());
}
-void OverlayInfoRegistry::RetrieveAndClear(std::vector<uint8_t>* infos) {
+void OverlayInfoRegistry::RetrieveAndClear(std::string* infos) {
OverlayInfoRegistryImpl::GetInstance()->RetrieveAndClear(infos);
}
diff --git a/src/cobalt/overlay_info/overlay_info_registry.h b/src/cobalt/overlay_info/overlay_info_registry.h
index ac45190..7de2a28 100644
--- a/src/cobalt/overlay_info/overlay_info_registry.h
+++ b/src/cobalt/overlay_info/overlay_info_registry.h
@@ -15,34 +15,27 @@
#ifndef COBALT_OVERLAY_INFO_OVERLAY_INFO_REGISTRY_H_
#define COBALT_OVERLAY_INFO_OVERLAY_INFO_REGISTRY_H_
-#include <vector>
+#include <sstream>
+#include <string>
#include "starboard/types.h"
namespace cobalt {
namespace overlay_info {
-// This class allows register of arbitrary overlay information in the form of
-// string or binary data from anywhere inside Cobalt. It also allows a consumer
-// to retrieve and clear all registered info, such info will be displayed as
-// overlay.
+// This class allows register of arbitrary overlay information from anywhere
+// inside Cobalt. It also allows a consumer to retrieve and clear all
+// registered info, such info will be displayed as overlay. The data is stored
+// as std::string internally.
// The class is thread safe and all its methods can be accessed from any thread.
// On average it expects to have less than 10 such info registered per frame.
//
-// The binary data or string are stored in the following format:
-// [<one byte size> <size bytes data>]*
-// Each data entry contains a string category and some binary data, separated by
-// the delimiter '<'.
-// For example, the overlay infos ("media", "pts"), ("renderer", "fps\x60"), and
-// ("dom", "keydown") will be stored as
-// '\x09', 'm', 'e', 'd', 'i', 'a', '<', 'p', 't', 's',
-// '\x0d', 'r', 'e', 'n', 'd', 'e', 'r', 'e', 'r', '<', 'f', 'p', 's', '\x60',
-// '\x0b', 'd', 'o', 'm', '<', 'k', 'e', 'y', 'd', 'o', 'w', 'n',
-// and the size of the vector will be 36. Note that C strings won't be NULL
-// terminated and their sizes are calculated by the size of the data.
+// The info is stored in the following format:
+// name0,value0,name1,value1,...
+// Binary data will be converted into hex string before storing.
class OverlayInfoRegistry {
public:
- static const uint8_t kDelimiter = '<'; // ':' doesn't work with some scanners
+ static const char kDelimiter = ','; // ':' doesn't work with some scanners
// The size of category and data combined should be less than or equal to
// kMaxSizeOfData - 1. The extra room of one byte is used by the delimiter.
static const size_t kMaxSizeOfData = 255;
@@ -52,14 +45,20 @@
static void Disable();
- // |category| cannot contain ':'. The sum of size of |category| and |string|
- // cannot exceed 254. It leaves room for the delimiter.
- static void Register(const char* category, const char* str);
- // |category| cannot contain ':'. The sum of size of |category| and |data|
- // cannot exceed 254. It leaves room for the delimiter.
+ // Both |category| and |data| cannot contain the delimiter.
+ static void Register(const char* category, const char* data);
+ // Both |category| and |data| cannot contain the delimiter.
static void Register(const char* category, const void* data,
size_t data_size);
- static void RetrieveAndClear(std::vector<uint8_t>* infos);
+ // Both |category| and |data| cannot contain the delimiter.
+ template <typename T>
+ static void Register(const char* category, T data) {
+ std::stringstream ss;
+ ss << data;
+ Register(category, ss.str().c_str());
+ }
+
+ static void RetrieveAndClear(std::string* infos);
};
} // namespace overlay_info
diff --git a/src/cobalt/overlay_info/overlay_info_registry_test.cc b/src/cobalt/overlay_info/overlay_info_registry_test.cc
index ce031ee..0b5d97e 100644
--- a/src/cobalt/overlay_info/overlay_info_registry_test.cc
+++ b/src/cobalt/overlay_info/overlay_info_registry_test.cc
@@ -20,6 +20,7 @@
#include <vector>
#include "base/logging.h"
+#include "base/strings/string_split.h"
#include "starboard/memory.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -29,56 +30,36 @@
namespace {
-typedef std::pair<std::string, std::vector<uint8_t>> ValuePair;
+typedef std::pair<std::string, std::string> ValuePair;
// See comment of OverlayInfoRegistry for format of |info|.
-std::vector<ValuePair> ParseOverlayInfo(std::vector<uint8_t> info) {
- std::vector<ValuePair> parsed_infos;
+bool ParseOverlayInfo(std::string infos, std::vector<ValuePair>* values) {
+ CHECK(values);
- while (!info.empty()) {
- // Parse the size
- size_t size = info[0];
- info.erase(info.begin());
+ const char delimiter[] = {OverlayInfoRegistry::kDelimiter, 0};
- CHECK_LE(size, info.size());
-
- // Parse the category name
- const auto kDelimiter = OverlayInfoRegistry::kDelimiter;
- auto iter = std::find(info.begin(), info.end(), kDelimiter);
- CHECK(iter != info.end());
-
- const auto category_length = iter - info.begin();
- CHECK_LE(static_cast<size_t>(category_length), size);
-
- std::string category(
- reinterpret_cast<char*>(info.data()),
- reinterpret_cast<char*>(info.data()) + category_length);
-
- // Parse the data
- std::vector<uint8_t> data(info.begin() + category_length + 1,
- info.begin() + size);
- info.erase(info.begin(), info.begin() + size);
-
- parsed_infos.push_back(std::make_pair(category, data));
+ auto tokens = base::SplitString(infos, delimiter, base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ if (tokens.size() % 2 != 0) {
+ return false;
}
- CHECK(info.empty());
- return parsed_infos;
-}
+ while (!tokens.empty()) {
+ values->push_back(std::make_pair(tokens[0], tokens[1]));
+ tokens.erase(tokens.begin(), tokens.begin() + 2);
+ }
-bool IsSame(const std::vector<uint8_t>& data, const std::string& str) {
- return data.size() == str.size() &&
- SbMemoryCompare(data.data(), str.c_str(), data.size()) == 0;
+ return true;
}
} // namespace
TEST(OverlayInfoRegistryTest, RetrieveOnEmptyData) {
- std::vector<uint8_t> infos('a');
+ std::string infos("a");
OverlayInfoRegistry::RetrieveAndClear(&infos);
EXPECT_TRUE(infos.empty());
- std::vector<uint8_t> infos1('a');
+ std::string infos1("b");
OverlayInfoRegistry::RetrieveAndClear(&infos1);
EXPECT_TRUE(infos1.empty());
}
@@ -92,33 +73,13 @@
OverlayInfoRegistry::Register(kCategory, value.c_str());
- std::vector<uint8_t> infos('a');
+ std::string infos("a");
OverlayInfoRegistry::RetrieveAndClear(&infos);
- auto parsed_infos = ParseOverlayInfo(infos);
+ std::vector<ValuePair> parsed_infos;
+ ASSERT_TRUE(ParseOverlayInfo(infos, &parsed_infos));
EXPECT_EQ(parsed_infos.size(), 1);
EXPECT_EQ(parsed_infos[0].first, kCategory);
- EXPECT_TRUE(IsSame(parsed_infos[0].second, value));
-
- OverlayInfoRegistry::RetrieveAndClear(&infos);
- EXPECT_TRUE(infos.empty());
- }
-}
-
-TEST(OverlayInfoRegistryTest, RegisterSingleBinaryStringAndRetrieve) {
- const char kCategory[] = "category";
- const size_t kMaxDataSize = 20;
-
- for (size_t i = 0; i < kMaxDataSize; ++i) {
- std::vector<uint8_t> value(i, static_cast<uint8_t>(i % 2));
-
- OverlayInfoRegistry::Register(kCategory, value.data(), value.size());
-
- std::vector<uint8_t> infos('a');
- OverlayInfoRegistry::RetrieveAndClear(&infos);
- auto parsed_infos = ParseOverlayInfo(infos);
- EXPECT_EQ(parsed_infos.size(), 1);
- EXPECT_EQ(parsed_infos[0].first, kCategory);
- EXPECT_TRUE(parsed_infos[0].second == value);
+ EXPECT_EQ(parsed_infos[0].second, value);
OverlayInfoRegistry::RetrieveAndClear(&infos);
EXPECT_TRUE(infos.empty());
@@ -137,9 +98,10 @@
OverlayInfoRegistry::Register(kCategory, values.back().c_str());
}
- std::vector<uint8_t> infos('a');
+ std::string infos("a");
OverlayInfoRegistry::RetrieveAndClear(&infos);
- auto parsed_infos = ParseOverlayInfo(infos);
+ std::vector<ValuePair> parsed_infos;
+ ASSERT_TRUE(ParseOverlayInfo(infos, &parsed_infos));
OverlayInfoRegistry::RetrieveAndClear(&infos);
EXPECT_TRUE(infos.empty());
@@ -147,35 +109,19 @@
for (size_t i = 0; i < kMaxStringLength; ++i) {
EXPECT_EQ(parsed_infos[i].first, kCategory);
- EXPECT_TRUE(IsSame(parsed_infos[i].second, values[i]));
+ EXPECT_EQ(parsed_infos[i].second, values[i]);
}
}
-TEST(OverlayInfoRegistryTest, RegisterMultipleBinaryDataAndRetrieve) {
- const char kCategory[] = "c";
- const size_t kMaxDataSize = 20;
+TEST(OverlayInfoRegistryTest, RegisterMultipleTypes) {
+ OverlayInfoRegistry::Register("string", "string_value");
+ OverlayInfoRegistry::Register("int", -12345);
+ OverlayInfoRegistry::Register("uint64", 123456789012u);
- std::vector<std::vector<uint8_t>> values;
-
- for (size_t i = 0; i < kMaxDataSize; ++i) {
- values.push_back(std::vector<uint8_t>(i, static_cast<uint8_t>(i % 2)));
-
- OverlayInfoRegistry::Register(kCategory, values.back().data(),
- values.back().size());
- }
-
- std::vector<uint8_t> infos('a');
+ std::string infos("a");
OverlayInfoRegistry::RetrieveAndClear(&infos);
- auto parsed_infos = ParseOverlayInfo(infos);
- OverlayInfoRegistry::RetrieveAndClear(&infos);
- EXPECT_TRUE(infos.empty());
- ASSERT_EQ(parsed_infos.size(), kMaxDataSize);
-
- for (size_t i = 0; i < kMaxDataSize; ++i) {
- EXPECT_EQ(parsed_infos[i].first, kCategory);
- EXPECT_TRUE(parsed_infos[i].second == values[i]);
- }
+ EXPECT_EQ(infos, "string,string_value,int,-12345,uint64,123456789012");
}
} // namespace overlay_info
diff --git a/src/cobalt/overlay_info/qr_code_overlay.cc b/src/cobalt/overlay_info/qr_code_overlay.cc
index 6fee031..f4691d3 100644
--- a/src/cobalt/overlay_info/qr_code_overlay.cc
+++ b/src/cobalt/overlay_info/qr_code_overlay.cc
@@ -15,13 +15,14 @@
#include "cobalt/overlay_info/qr_code_overlay.h"
#include <algorithm>
-#include <vector>
+#include <string>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/overlay_info/overlay_info_registry.h"
#include "cobalt/render_tree/animations/animate_node.h"
+#include "starboard/memory.h"
#include "third_party/QR-Code-generator/cpp/QrCode.hpp"
namespace cobalt {
@@ -32,16 +33,20 @@
using qrcodegen::QrCode;
using render_tree::Image;
using render_tree::ImageNode;
+using render_tree::ResourceProvider;
-const int kModuleDimensionInPixels = 4;
+const int kMinimumQrCodeVersion = 3;
const int kPixelSizeInBytes = 4;
-const uint32_t kBlack = 0x00000000;
+#if SB_IS_BIG_ENDIAN
+const uint32_t kBlack = 0x000000FF;
+#else // SB_IS_BIG_ENDIAN
+const uint32_t kBlack = 0xFF000000;
+#endif // SB_IS_BIG_ENDIAN
const uint32_t kWhite = 0xFFFFFFFF;
const uint32_t kBorderColor = kWhite;
-const int kCodeBorderInPixels = 16;
-const int kScreenMarginInPixels = 128;
+const int kScreenMarginInPixels = 64;
-int64_t s_frame_count_ = 0;
+uint32_t s_frame_index_ = 0;
void DrawRect(int width, int height, int pitch_in_bytes, uint32_t color,
uint8_t* target_buffer) {
@@ -55,43 +60,45 @@
}
}
-void DrawQrCode(const QrCode& qr_code, int pitch_in_bytes,
- uint8_t* target_buffer) {
+void DrawQrCode(const QrCode& qr_code, int module_dimension_in_pixels,
+ int pitch_in_bytes, uint8_t* target_buffer) {
uint8_t* row_data = target_buffer;
for (int row = 0; row < qr_code.getSize(); ++row) {
uint8_t* column_data = row_data;
for (int column = 0; column < qr_code.getSize(); ++column) {
- DrawRect(kModuleDimensionInPixels, kModuleDimensionInPixels,
+ DrawRect(module_dimension_in_pixels, module_dimension_in_pixels,
pitch_in_bytes, qr_code.getModule(row, column) ? kBlack : kWhite,
column_data);
- column_data += kPixelSizeInBytes * kModuleDimensionInPixels;
+ column_data += kPixelSizeInBytes * module_dimension_in_pixels;
}
- row_data += pitch_in_bytes * kModuleDimensionInPixels;
+ row_data += pitch_in_bytes * module_dimension_in_pixels;
}
}
-scoped_refptr<Image> CreateImageForQrCodes(
- const std::vector<QrCode>& qr_codes, const math::Size& screen_size,
- render_tree::ResourceProvider* resource_provider) {
- TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCodes()");
+scoped_refptr<Image> CreateImageForQrCode(const QrCode& qr_code,
+ const math::Size& screen_size,
+ int slots,
+ ResourceProvider* resource_provider) {
+ TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCode()");
- int max_code_size = 0;
+ const int module_dimension_in_pixels = screen_size.height() > 1080 ? 16 : 8;
+ const int code_border_in_pixels = module_dimension_in_pixels * 2;
- for (auto& qr_code : qr_codes) {
- max_code_size = std::max(max_code_size, qr_code.getSize());
- }
+ int qr_code_size_in_blocks = qr_code.getSize();
- int column =
- (screen_size.width() - kScreenMarginInPixels * 2 - kCodeBorderInPixels) /
- (max_code_size * kModuleDimensionInPixels + kCodeBorderInPixels);
- column = std::min(column, static_cast<int>(qr_codes.size()));
- int row = (static_cast<int>(qr_codes.size()) + column - 1) / column;
+ int column = (screen_size.width() - kScreenMarginInPixels * 2 -
+ code_border_in_pixels) /
+ (qr_code_size_in_blocks * module_dimension_in_pixels +
+ code_border_in_pixels);
+ column = std::min(column, slots);
+ int row = (slots + column - 1) / column;
- int image_width = column * max_code_size * kModuleDimensionInPixels +
- kCodeBorderInPixels * (column + 1);
- int image_height = row * max_code_size * kModuleDimensionInPixels +
- kCodeBorderInPixels * (row + 1);
+ int image_width =
+ column * qr_code_size_in_blocks * module_dimension_in_pixels +
+ code_border_in_pixels * (column + 1);
+ int image_height = row * qr_code_size_in_blocks * module_dimension_in_pixels +
+ code_border_in_pixels * (row + 1);
auto image_data = resource_provider->AllocateImageData(
math::Size(image_width, image_height), render_tree::kPixelFormatRGBA8,
@@ -99,59 +106,63 @@
DCHECK(image_data);
auto image_desc = image_data->GetDescriptor();
- size_t qr_code_index = 0;
+ uint32_t slot_index = 0;
auto row_data = image_data->GetMemory();
for (int i = 0; i < row; ++i) {
- // Draw the top border of all qr codes in the row.
- DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes,
+ // Draw the top border of all qr code blocks in the row.
+ DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes,
kBorderColor, row_data);
- row_data += kCodeBorderInPixels * image_desc.pitch_in_bytes;
+ row_data += code_border_in_pixels * image_desc.pitch_in_bytes;
auto column_data = row_data;
for (int j = 0; j < column; ++j) {
// Draw the left border.
- DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels,
+ DrawRect(code_border_in_pixels,
+ qr_code_size_in_blocks * module_dimension_in_pixels,
image_desc.pitch_in_bytes, kBorderColor, column_data);
- column_data += kCodeBorderInPixels * kPixelSizeInBytes;
- if (qr_code_index < qr_codes.size()) {
+ column_data += code_border_in_pixels * kPixelSizeInBytes;
+ if (slot_index == s_frame_index_ % slots) {
// Draw qr code.
- DrawQrCode(qr_codes[qr_code_index], image_desc.pitch_in_bytes,
- column_data);
- ++qr_code_index;
+ DrawQrCode(qr_code, module_dimension_in_pixels,
+ image_desc.pitch_in_bytes, column_data);
+ } else {
+ DrawRect(qr_code_size_in_blocks * module_dimension_in_pixels,
+ qr_code_size_in_blocks * module_dimension_in_pixels,
+ image_desc.pitch_in_bytes, kBlack, column_data);
}
- column_data +=
- max_code_size * kModuleDimensionInPixels * kPixelSizeInBytes;
+ ++slot_index;
+ column_data += qr_code_size_in_blocks * module_dimension_in_pixels *
+ kPixelSizeInBytes;
}
// Draw the right border of the row.
- DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels,
+ DrawRect(code_border_in_pixels,
+ qr_code_size_in_blocks * module_dimension_in_pixels,
image_desc.pitch_in_bytes, kBorderColor, column_data);
- row_data +=
- max_code_size * kModuleDimensionInPixels * image_desc.pitch_in_bytes;
+ row_data += qr_code_size_in_blocks * module_dimension_in_pixels *
+ image_desc.pitch_in_bytes;
}
// Draw the bottom border of all qr code.
- DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes,
+ DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes,
kBorderColor, row_data);
return resource_provider->CreateImage(std::move(image_data));
}
-void AnimateCB(math::Size screen_size,
- render_tree::ResourceProvider* resource_provider,
- render_tree::ImageNode::Builder* image_node,
- base::TimeDelta time) {
+void AnimateCB(math::Size screen_size, int slots,
+ ResourceProvider* resource_provider,
+ ImageNode::Builder* image_node, base::TimeDelta time) {
SB_UNREFERENCED_PARAMETER(time);
DCHECK(image_node);
TRACE_EVENT0("cobalt::overlay_info", "AnimateCB()");
- OverlayInfoRegistry::Register("overlay_info:frame_count", &s_frame_count_,
- sizeof(s_frame_count_));
- ++s_frame_count_;
+ OverlayInfoRegistry::Register("frame", s_frame_index_);
+ ++s_frame_index_;
- std::vector<uint8_t> infos;
+ std::string infos;
OverlayInfoRegistry::RetrieveAndClear(&infos);
if (infos.empty()) {
@@ -159,26 +170,25 @@
return;
}
- // Use a vector in case we decide to switch back to multiple qr codes.
- std::vector<QrCode> qr_codes;
- qr_codes.emplace_back(QrCode::encodeBinary(infos, QrCode::Ecc::LOW));
-
+ auto qrcode = QrCode::encodeText(infos.c_str(), QrCode::Ecc::LOW,
+ kMinimumQrCodeVersion);
image_node->source =
- CreateImageForQrCodes(qr_codes, screen_size, resource_provider);
+ CreateImageForQrCode(qrcode, screen_size, slots, resource_provider);
auto image_size = image_node->source->GetSize();
- // TODO: Move the QR code between draws to avoid tearing.
- image_node->destination_rect =
- math::RectF(kScreenMarginInPixels, kScreenMarginInPixels,
- image_size.width(), image_size.height());
+ image_node->destination_rect = math::RectF(
+ screen_size.width() - image_size.width() - kScreenMarginInPixels,
+ screen_size.height() - image_size.height() - kScreenMarginInPixels,
+ image_size.width(), image_size.height());
}
} // namespace
QrCodeOverlay::QrCodeOverlay(
- const math::Size& screen_size,
- render_tree::ResourceProvider* resource_provider,
+ const math::Size& screen_size, int slots,
+ ResourceProvider* resource_provider,
const RenderTreeProducedCB& render_tree_produced_cb)
: render_tree_produced_cb_(render_tree_produced_cb),
+ slots_(slots),
screen_size_(screen_size),
resource_provider_(resource_provider) {
DCHECK_GT(screen_size.width(), 0);
@@ -196,8 +206,7 @@
UpdateRenderTree();
}
-void QrCodeOverlay::SetResourceProvider(
- render_tree::ResourceProvider* resource_provider) {
+void QrCodeOverlay::SetResourceProvider(ResourceProvider* resource_provider) {
resource_provider_ = resource_provider;
UpdateRenderTree();
}
@@ -212,8 +221,8 @@
scoped_refptr<ImageNode> image_node = new ImageNode(nullptr);
render_tree::animations::AnimateNode::Builder animate_node_builder;
- animate_node_builder.Add(
- image_node, base::Bind(AnimateCB, screen_size_, resource_provider_));
+ animate_node_builder.Add(image_node, base::Bind(AnimateCB, screen_size_,
+ slots_, resource_provider_));
render_tree_produced_cb_.Run(new render_tree::animations::AnimateNode(
animate_node_builder, image_node));
diff --git a/src/cobalt/overlay_info/qr_code_overlay.h b/src/cobalt/overlay_info/qr_code_overlay.h
index 5d5aa15..02d630b 100644
--- a/src/cobalt/overlay_info/qr_code_overlay.h
+++ b/src/cobalt/overlay_info/qr_code_overlay.h
@@ -31,7 +31,7 @@
typedef base::Callback<void(const scoped_refptr<render_tree::Node>&)>
RenderTreeProducedCB;
- QrCodeOverlay(const math::Size& screen_size,
+ QrCodeOverlay(const math::Size& screen_size, int slots,
render_tree::ResourceProvider* resource_provider,
const RenderTreeProducedCB& render_tree_produced_cb);
@@ -41,7 +41,11 @@
private:
void UpdateRenderTree();
- RenderTreeProducedCB render_tree_produced_cb_;
+ const RenderTreeProducedCB render_tree_produced_cb_;
+ // Qr codes are displayed in rotating positions so it won't be blurred during
+ // capture. The number of rotating positions are specified by |slots_|.
+ const int slots_;
+
math::Size screen_size_;
render_tree::ResourceProvider* resource_provider_;
};
diff --git a/src/cobalt/page_visibility/page_visibility.gyp b/src/cobalt/page_visibility/page_visibility.gyp
index bef5f08..4d85443 100644
--- a/src/cobalt/page_visibility/page_visibility.gyp
+++ b/src/cobalt/page_visibility/page_visibility.gyp
@@ -47,11 +47,11 @@
'page_visibility_state_test.cc',
],
'dependencies': [
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'page_visibility',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'page_visibility_test_deploy',
diff --git a/src/cobalt/render_tree/render_tree.gyp b/src/cobalt/render_tree/render_tree.gyp
index c7c0ec9..8f74058 100644
--- a/src/cobalt/render_tree/render_tree.gyp
+++ b/src/cobalt/render_tree/render_tree.gyp
@@ -104,12 +104,12 @@
'node_visitor_test.cc',
],
'dependencies': [
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'animations',
'render_tree',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
# Deploys the render tree library test on a console.
diff --git a/src/cobalt/renderer/backend/backend.gyp b/src/cobalt/renderer/backend/backend.gyp
index 3ff2de4..a3b5e85 100644
--- a/src/cobalt/renderer/backend/backend.gyp
+++ b/src/cobalt/renderer/backend/backend.gyp
@@ -23,7 +23,6 @@
'render_target.cc',
'render_target.h',
],
-
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/math/math.gyp:math',
diff --git a/src/cobalt/renderer/backend/graphics_context.cc b/src/cobalt/renderer/backend/graphics_context.cc
index 12f0e3c..4008b32 100644
--- a/src/cobalt/renderer/backend/graphics_context.cc
+++ b/src/cobalt/renderer/backend/graphics_context.cc
@@ -52,6 +52,16 @@
return -1.0f;
}
+float GraphicsContext::GetMinimumFrameIntervalInMilliseconds() {
+ if (graphics_extension_ && graphics_extension_->version >= 2) {
+ return graphics_extension_->GetMinimumFrameIntervalInMilliseconds();
+ }
+
+ // Return negative value, if the GraphicsExtension is not implemented
+ // or the GraphicsExtension version is below 2.
+ return -1.0f;
+}
+
} // namespace backend
} // namespace renderer
} // namespace cobalt
diff --git a/src/cobalt/renderer/backend/graphics_context.h b/src/cobalt/renderer/backend/graphics_context.h
index 8621f5e..ead28c3 100644
--- a/src/cobalt/renderer/backend/graphics_context.h
+++ b/src/cobalt/renderer/backend/graphics_context.h
@@ -85,6 +85,15 @@
// only be presented when something changes.
virtual float GetMaximumFrameIntervalInMilliseconds();
+ // Allow throttling of the frame rate. This is expressed in terms of
+ // milliseconds and can be a floating point number. Keep in mind that
+ // swapping frames may take some additional processing time, so it may be
+ // better to specify a lower delay. For example, '33' instead of '33.33'
+ // for 30 Hz refresh. If implemented, this takes precedence over the gyp
+ // variable 'cobalt_minimum_frame_time_in_milliseconds'.
+ // Note: Return a negative number if no value is specified by the platform.
+ virtual float GetMinimumFrameIntervalInMilliseconds();
+
private:
GraphicsSystem* system_;
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index ef54726..e2dad23 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -298,12 +298,20 @@
// swaps. It is possible that a submission is not rendered (this can
// happen if the render tree has not changed between submissions), so no
// frame swap occurs, and the minimum frame time is the only throttle.
+ float minimum_frame_interval_milliseconds =
+ graphics_context_
+ ? graphics_context_->GetMinimumFrameIntervalInMilliseconds()
+ : -1.0f;
COMPILE_ASSERT(COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0,
frame_time_must_be_positive);
+ if (minimum_frame_interval_milliseconds < 0.0f) {
+ minimum_frame_interval_milliseconds =
+ COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS;
+ }
+ DCHECK(minimum_frame_interval_milliseconds > 0.0f);
rasterize_timer_.emplace(
FROM_HERE,
- base::TimeDelta::FromMillisecondsD(
- COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS),
+ base::TimeDelta::FromMillisecondsD(minimum_frame_interval_milliseconds),
base::BindRepeating(&Pipeline::RasterizeCurrentTree,
base::Unretained(this)));
rasterize_timer_->Reset();
diff --git a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
index f989a6d..af03b7b 100644
--- a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
+++ b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
@@ -103,10 +103,10 @@
'dependencies': [
'hardware_rasterizer',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest'
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index b3e56b2..0fd2ab2 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -85,10 +85,17 @@
1.164f, 2.112f, 0.0f, -1.12875f, 0.0f, 0.0f, 0.0f, 1.0f};
// Used for 10bit unnormalized YUV images.
+// Y is between 64 and 940 inclusive. U and V are between 64 and 960 inclusive.
+// it is 1023/(940-64) = 1.1678 for Y and 1023/(960-64) = 1.1417 for U and V.
+// 64 is the scale factor for 10 bit.
+// Input YUV must be subtracted by (0.0625, 0.5, 0.5), so
+// -1.1678 * 0.0625 - 0 * 0.5 - 1.6835 * 0.5 = -0.9147
+// -1.1678 * 0.0625 -(-0.1878 * 0.5) - (-0.6522 * 0.5) = 0.347
+// -1.1678 * 0.0625 - (2.1479f * 0.5) - 0 * 0.5 = -1.1469
const float k10BitBT2020ColorMatrix[16] = {
- 64 * 1.1678f, 0.0f, 64 * 1.6835f, -0.96925f,
- 64 * 1.1678f, 64 * -0.1878f, 64 * -0.6522f, 0.30025f,
- 64 * 1.1678f, 64 * 2.1479f, 0.0f, -1.12875f,
+ 64 * 1.1678f, 0.0f, 64 * 1.6835f, -0.9147f,
+ 64 * 1.1678f, 64 * -0.1878f, 64 * -0.6522f, 0.347f,
+ 64 * 1.1678f, 64 * 2.1479f, 0.0f, -1.1469f,
0.0f, 0.0f, 0.0f, 1.0f};
const float* GetColorMatrixForImageType(
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp
index 89d6804..b9b59e9 100644
--- a/src/cobalt/renderer/renderer.gyp
+++ b/src/cobalt/renderer/renderer.gyp
@@ -101,7 +101,6 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'render_tree_pixel_tester',
@@ -112,6 +111,7 @@
'defines' : ['ENABLE_MAP_TO_MESH'],
}],
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'renderer_copy_test_data',
diff --git a/src/cobalt/renderer/test/png_utils/png_utils.gyp b/src/cobalt/renderer/test/png_utils/png_utils.gyp
index dbea190..f56ddc6 100644
--- a/src/cobalt/renderer/test/png_utils/png_utils.gyp
+++ b/src/cobalt/renderer/test/png_utils/png_utils.gyp
@@ -44,12 +44,12 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'png_utils',
'png_utils_copy_test_data',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'png_utils_benchmark',
diff --git a/src/cobalt/samples/simple_example/simple_example.gyp b/src/cobalt/samples/simple_example/simple_example.gyp
index 5548fc2..7a6226c 100644
--- a/src/cobalt/samples/simple_example/simple_example.gyp
+++ b/src/cobalt/samples/simple_example/simple_example.gyp
@@ -91,11 +91,11 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gtest.gyp:gtest',
'simple_example_lib',
'simple_example_copy_test_data',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
# This target is optional and is only needed if tests are using
diff --git a/src/cobalt/script/array_buffer.h b/src/cobalt/script/array_buffer.h
index 850fc52..eb5acce 100644
--- a/src/cobalt/script/array_buffer.h
+++ b/src/cobalt/script/array_buffer.h
@@ -15,6 +15,7 @@
#ifndef COBALT_SCRIPT_ARRAY_BUFFER_H_
#define COBALT_SCRIPT_ARRAY_BUFFER_H_
+#include <algorithm>
#include <memory>
#include "base/logging.h"
@@ -81,6 +82,7 @@
// DCHECK_EQ(data.data(), nullptr);
class PreallocatedArrayBufferData {
public:
+ PreallocatedArrayBufferData() = default;
explicit PreallocatedArrayBufferData(size_t byte_length);
~PreallocatedArrayBufferData();
@@ -89,8 +91,17 @@
default;
void* data() { return data_; }
+ const void* data() const { return data_; }
size_t byte_length() const { return byte_length_; }
+ void Swap(PreallocatedArrayBufferData* that) {
+ DCHECK(that);
+
+ std::swap(data_, that->data_);
+ std::swap(byte_length_, that->byte_length_);
+ }
+ void Resize(size_t new_byte_length);
+
private:
PreallocatedArrayBufferData(const PreallocatedArrayBufferData&) = delete;
void operator=(const PreallocatedArrayBufferData&) = delete;
@@ -106,8 +117,8 @@
byte_length_ = 0u;
}
- void* data_;
- size_t byte_length_;
+ void* data_ = nullptr;
+ size_t byte_length_ = 0u;
friend ArrayBuffer;
};
diff --git a/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc b/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
index 1fa7a43..714a582 100644
--- a/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
+++ b/src/cobalt/script/mozjs-45/mozjs_array_buffer.cc
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <algorithm>
#include <memory>
#include "cobalt/script/mozjs-45/mozjs_array_buffer.h"
#include "cobalt/base/polymorphic_downcast.h"
+#include "starboard/memory.h"
namespace cobalt {
namespace script {
@@ -36,6 +38,22 @@
}
}
+void PreallocatedArrayBufferData::Resize(size_t new_byte_length) {
+ if (byte_length_ == new_byte_length) {
+ return;
+ }
+ auto new_data = js_malloc(new_byte_length);
+ DCHECK(new_data);
+ if (data_) {
+ if (new_data) {
+ SbMemoryCopy(new_data, data_, std::min(byte_length_, new_byte_length));
+ }
+ js_free(data_);
+ }
+ data_ = new_data;
+ byte_length_ = new_byte_length;
+}
+
// static
Handle<ArrayBuffer> ArrayBuffer::New(GlobalEnvironment* global_environment,
size_t byte_length) {
diff --git a/src/cobalt/script/v8c/v8c_array_buffer.cc b/src/cobalt/script/v8c/v8c_array_buffer.cc
index 13087af..7fe8b3a 100644
--- a/src/cobalt/script/v8c/v8c_array_buffer.cc
+++ b/src/cobalt/script/v8c/v8c_array_buffer.cc
@@ -17,6 +17,7 @@
#include "cobalt/script/v8c/v8c_array_buffer.h"
#include "cobalt/base/polymorphic_downcast.h"
+#include "starboard/memory.h"
namespace cobalt {
namespace script {
@@ -34,6 +35,11 @@
}
}
+void PreallocatedArrayBufferData::Resize(size_t new_byte_length) {
+ data_ = SbMemoryReallocate(data_, new_byte_length);
+ byte_length_ = new_byte_length;
+}
+
// static
Handle<ArrayBuffer> ArrayBuffer::New(GlobalEnvironment* global_environment,
size_t byte_length) {
diff --git a/src/cobalt/script/v8c/v8c_callback_function.h b/src/cobalt/script/v8c/v8c_callback_function.h
index b958457..c069726 100644
--- a/src/cobalt/script/v8c/v8c_callback_function.h
+++ b/src/cobalt/script/v8c/v8c_callback_function.h
@@ -22,6 +22,8 @@
#ifndef COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
#define COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
+#include <string>
+
#include "base/logging.h"
#include "cobalt/script/callback_function.h"
#include "cobalt/script/v8c/conversion_helpers.h"
@@ -90,6 +92,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -110,7 +113,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -133,8 +145,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1)
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1)
const override {
CallbackResult<R> callback_result;
DCHECK(!this->IsEmpty());
@@ -146,6 +157,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -168,7 +180,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -191,8 +212,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2)
const override {
CallbackResult<R> callback_result;
@@ -205,6 +225,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -228,7 +249,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -251,8 +281,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2,
typename CallbackParamTraits<A3>::ForwardType a3)
const override {
@@ -266,6 +295,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -290,7 +320,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -313,8 +352,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2,
typename CallbackParamTraits<A3>::ForwardType a3,
typename CallbackParamTraits<A4>::ForwardType a4)
@@ -329,6 +367,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -354,7 +393,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -378,8 +426,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2,
typename CallbackParamTraits<A3>::ForwardType a3,
typename CallbackParamTraits<A4>::ForwardType a4,
@@ -395,6 +442,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -421,7 +469,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -445,8 +502,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2,
typename CallbackParamTraits<A3>::ForwardType a3,
typename CallbackParamTraits<A4>::ForwardType a4,
@@ -463,6 +519,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -490,7 +547,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
@@ -514,8 +580,7 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run( typename
- CallbackParamTraits<A1>::ForwardType a1,
+ CallbackResult<R> Run(typename CallbackParamTraits<A1>::ForwardType a1,
typename CallbackParamTraits<A2>::ForwardType a2,
typename CallbackParamTraits<A3>::ForwardType a3,
typename CallbackParamTraits<A4>::ForwardType a4,
@@ -533,6 +598,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -561,7 +627,16 @@
function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
diff --git a/src/cobalt/script/v8c/v8c_callback_function.h.pump b/src/cobalt/script/v8c/v8c_callback_function.h.pump
index 42c9ffb..8bf8cd2 100644
--- a/src/cobalt/script/v8c/v8c_callback_function.h.pump
+++ b/src/cobalt/script/v8c/v8c_callback_function.h.pump
@@ -27,6 +27,8 @@
#ifndef COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
#define COBALT_SCRIPT_V8C_V8C_CALLBACK_FUNCTION_H_
+#include <string>
+
#include "base/logging.h"
#include "cobalt/script/callback_function.h"
#include "cobalt/script/v8c/conversion_helpers.h"
@@ -95,8 +97,8 @@
V8cCallbackFunction(v8::Isolate* isolate, v8::Local<v8::Value> handle)
: ScopedPersistent(isolate, handle), isolate_(isolate) {}
- CallbackResult<R> Run($for ARG , [[
- typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
+ CallbackResult<R> Run($for ARG ,
+ [[typename CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]])
const override {
CallbackResult<R> callback_result;
DCHECK(!this->IsEmpty());
@@ -108,6 +110,7 @@
EntryScope entry_scope(isolate_);
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+ v8::TryCatch try_catch(isolate_);
// https://www.w3.org/TR/WebIDL/#es-invoking-callback-functions
// Callback 'this' is set to null, unless overridden by other specifications
@@ -134,7 +137,16 @@
v8::MaybeLocal<v8::Value> maybe_return_value = function_as_object->CallAsFunction(context, this_value, argc, argv);
v8::Local<v8::Value> return_value;
if (!maybe_return_value.ToLocal(&return_value)) {
- NOTIMPLEMENTED();
+ std::string description;
+ v8::Local<v8::Value> stack;
+ if (try_catch.StackTrace(context).ToLocal(&stack)) {
+ description = *v8::String::Utf8Value(isolate_, stack);
+ } else {
+ description = *v8::String::Utf8Value(isolate_, try_catch.Exception());
+ }
+ if (description.empty()) description = "Unknown exception";
+ // TODO: Send the description to the console instead of logging it.
+ LOG(ERROR) << description;
callback_result.exception = true;
} else {
callback_result = ConvertCallbackReturnValue<R>(isolate_, return_value);
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 231bf06..30d7463 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -229,18 +229,22 @@
is_last_chunk));
}
+// TODO: Refactor OnURLFetchDownloadProgress() into a private function that is
+// called by OnURLFetchDownloadProgress() and OnURLFetchComplete(), to
+// explicitly remove the unreferenced parameters.
void GoogleSpeechService::OnURLFetchDownloadProgress(
const net::URLFetcher* source, int64_t /*current*/, int64_t /*total*/,
int64_t /*current_network_bytes*/) {
DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
- std::unique_ptr<std::string> data = download_data_writer_->data();
+ std::string data;
+ download_data_writer_->GetAndResetData(&data);
const net::URLRequestStatus& status = source->GetStatus();
const int response_code = source->GetResponseCode();
if (source == downstream_fetcher_.get()) {
if (status.is_success() && IsResponseCodeSuccess(response_code)) {
- chunked_byte_buffer_.Append(*data);
+ chunked_byte_buffer_.Append(data);
while (chunked_byte_buffer_.HasChunks()) {
std::unique_ptr<std::vector<uint8_t> > chunk =
chunked_byte_buffer_.PopChunk();
@@ -272,12 +276,11 @@
void GoogleSpeechService::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
- std::unique_ptr<std::string> remaining_data = download_data_writer_->data();
- int64_t length = remaining_data->length();
- if (remaining_data && length > 0) {
- OnURLFetchDownloadProgress(source, length, length, length);
+ if (download_data_writer_->HasData()) {
+ // Explicitly pass '-1' for all sizes, as it is not used by
+ // OnURLFetchDownloadProgress();
+ OnURLFetchDownloadProgress(source, -1, -1, -1);
}
- // no-op.
}
// static
diff --git a/src/cobalt/speech/google_speech_service.h b/src/cobalt/speech/google_speech_service.h
index a69ff9d..3768636 100644
--- a/src/cobalt/speech/google_speech_service.h
+++ b/src/cobalt/speech/google_speech_service.h
@@ -68,7 +68,7 @@
// net::URLFetcherDelegate interface
void OnURLFetchDownloadProgress(const net::URLFetcher* source,
- int64_t current, int64_t total,
+ int64_t /*current*/, int64_t /*total*/,
int64_t /*current_network_bytes*/) override;
void OnURLFetchComplete(const net::URLFetcher* source) override;
void OnURLFetchUploadProgress(const net::URLFetcher* /*source*/,
diff --git a/src/cobalt/storage/storage.gyp b/src/cobalt/storage/storage.gyp
index 9b77578..96ea7bc 100644
--- a/src/cobalt/storage/storage.gyp
+++ b/src/cobalt/storage/storage.gyp
@@ -55,12 +55,12 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'storage',
'storage_upgrade_copy_test_data',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'storage_test_deploy',
diff --git a/src/cobalt/storage/store/store.gyp b/src/cobalt/storage/store/store.gyp
index d80a876..1cf9fc4 100644
--- a/src/cobalt/storage/store/store.gyp
+++ b/src/cobalt/storage/store/store.gyp
@@ -39,11 +39,11 @@
],
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'memory_store',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'memory_store_test_deploy',
diff --git a/src/cobalt/storage/store_upgrade/upgrade.gyp b/src/cobalt/storage/store_upgrade/upgrade.gyp
index 1066f1a..23a196e 100644
--- a/src/cobalt/storage/store_upgrade/upgrade.gyp
+++ b/src/cobalt/storage/store_upgrade/upgrade.gyp
@@ -48,11 +48,11 @@
'storage_upgrade',
'storage_upgrade_copy_test_files',
'<(DEPTH)/cobalt/base/base.gyp:base',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'storage_upgrade_test_deploy',
diff --git a/src/cobalt/test/run_all_unittests.cc b/src/cobalt/test/run_all_unittests.cc
index 90025ec..0d69f0c 100644
--- a/src/cobalt/test/run_all_unittests.cc
+++ b/src/cobalt/test/run_all_unittests.cc
@@ -22,7 +22,6 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace {
-
int InitAndRunAllTests(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
base::AtExitManager exit_manager;
diff --git a/src/cobalt/test/test.gyp b/src/cobalt/test/test.gyp
deleted file mode 100644
index 30af2ea..0000000
--- a/src/cobalt/test/test.gyp
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-
-{
- 'targets': [
- {
- 'target_name': 'run_all_unittests',
- 'type': 'static_library',
- 'dependencies': [
- '<(DEPTH)/base/base.gyp:test_support_base',
- '<(DEPTH)/testing/gtest.gyp:gtest',
- ],
- 'sources': [
- 'run_all_unittests.cc',
- ],
- },
- ]
-}
diff --git a/src/cobalt/test/test.gypi b/src/cobalt/test/test.gypi
new file mode 100644
index 0000000..9df4e38
--- /dev/null
+++ b/src/cobalt/test/test.gypi
@@ -0,0 +1,23 @@
+# 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.
+
+{
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:test_support_base',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ '<(DEPTH)/cobalt/test/run_all_unittests.cc',
+ ],
+}
diff --git a/src/cobalt/tools/automated_testing/cobalt_runner.py b/src/cobalt/tools/automated_testing/cobalt_runner.py
index 3f392c9..56a1b24 100644
--- a/src/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/src/cobalt/tools/automated_testing/cobalt_runner.py
@@ -11,6 +11,7 @@
import thread
import threading
import time
+import traceback
import _env # pylint: disable=unused-import
from cobalt.tools.automated_testing import c_val_names
@@ -141,6 +142,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
@@ -254,10 +259,18 @@
def _KillLauncher(self):
"""Kills the launcher and its attached Cobalt instance."""
+ wait_for_runner_thread = True
if self.CanExecuteJavaScript():
- self.ExecuteJavaScript('window.close();')
+ try:
+ self.ExecuteJavaScript('window.close();')
+ except Exception:
+ wait_for_runner_thread = False
+ sys.stderr.write(
+ '***An exception was raised while trying to close the app:')
+ traceback.print_exc(file=sys.stderr)
- self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
+ if wait_for_runner_thread:
+ self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
if self.runner_thread.isAlive():
sys.stderr.write(
'***Runner thread still alive after sending graceful shutdown command, try again by killing app***\n'
@@ -503,7 +516,7 @@
device_params.config = args.config
device_params.device_id = args.device_id
device_params.out_directory = args.out_directory
- if args.target_params == None:
+ if args.target_params is None:
device_params.target_params = []
else:
device_params.target_params = [args.target_params]
diff --git a/src/cobalt/web_animations/web_animations.gyp b/src/cobalt/web_animations/web_animations.gyp
index ff2eaf8..c350740 100644
--- a/src/cobalt/web_animations/web_animations.gyp
+++ b/src/cobalt/web_animations/web_animations.gyp
@@ -57,10 +57,10 @@
'web_animations',
'<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
'<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/webdriver/screencast/screencast_module.cc b/src/cobalt/webdriver/screencast/screencast_module.cc
index ddbdbbd..9df5269 100644
--- a/src/cobalt/webdriver/screencast/screencast_module.cc
+++ b/src/cobalt/webdriver/screencast/screencast_module.cc
@@ -31,6 +31,8 @@
namespace {
const char kJpegContentType[] = "image/jpeg";
+// Add screencast frame rate as 30 fps.
+const int kScreencastFramesPerSecond = 30;
}
ScreencastModule::ScreencastModule(
@@ -88,7 +90,7 @@
base::Bind(&ScreencastModule::TakeScreenshot, base::Unretained(this));
screenshot_timer_->Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(
- COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS),
+ 1000.0f / kScreencastFramesPerSecond),
screenshot_event);
}
diff --git a/src/cobalt/webdriver/webdriver.gyp b/src/cobalt/webdriver/webdriver.gyp
index f344d75..491531d 100644
--- a/src/cobalt/webdriver/webdriver.gyp
+++ b/src/cobalt/webdriver/webdriver.gyp
@@ -94,7 +94,6 @@
'dependencies': [ 'copy_webdriver_data', ],
'defines': [
'ENABLE_WEBDRIVER',
- 'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)',
],
'all_dependent_settings': {
'defines': [ 'ENABLE_WEBDRIVER', ],
diff --git a/src/cobalt/webdriver/webdriver_test.gyp b/src/cobalt/webdriver/webdriver_test.gyp
index 9342fdc..33c8ab8 100644
--- a/src/cobalt/webdriver/webdriver_test.gyp
+++ b/src/cobalt/webdriver/webdriver_test.gyp
@@ -15,11 +15,11 @@
],
'dependencies': [
'<(DEPTH)/cobalt/browser/browser.gyp:browser',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'webdriver_copy_test_data',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
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 39ef2ec..1334aeb 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..1c2d0ac
--- /dev/null
+++ b/src/cobalt/websocket/web_socket_impl_test.cc
@@ -0,0 +1,230 @@
+// 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_debugger_hooks_, 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.
+ base::NullDebuggerHooks null_debugger_hooks_;
+ 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 575b46c..f85d4bb 100644
--- a/src/cobalt/websocket/web_socket_test.cc
+++ b/src/cobalt/websocket/web_socket_test.cc
@@ -36,6 +36,7 @@
namespace cobalt {
namespace websocket {
+namespace {
class FakeSettings : public dom::testing::StubEnvironmentSettings {
public:
FakeSettings() : base_("https://example.com") {
@@ -48,6 +49,7 @@
GURL base_;
std::unique_ptr<network::NetworkModule> network_module_;
};
+} // namespace
class WebSocketTest : public ::testing::Test {
public:
diff --git a/src/cobalt/websocket/websocket.gyp b/src/cobalt/websocket/websocket.gyp
index 88e2bad..c3bfb3a 100644
--- a/src/cobalt/websocket/websocket.gyp
+++ b/src/cobalt/websocket/websocket.gyp
@@ -47,11 +47,12 @@
'type': '<(gtest_target_type)',
'sources': [
'web_socket_test.cc',
+ 'mock_websocket_channel.cc',
+ 'web_socket_impl_test.cc',
],
'dependencies': [
'websocket',
'<(DEPTH)/cobalt/dom/dom.gyp:dom',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/url/url.gyp:url',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
@@ -68,6 +69,7 @@
],
}],
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.cc b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
new file mode 100644
index 0000000..f8c4378
--- /dev/null
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -0,0 +1,303 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/xhr/url_fetcher_buffer_writer.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "starboard/memory.h"
+
+namespace cobalt {
+namespace xhr {
+
+namespace {
+
+// Allocate 64KB if the total size is unknown to avoid allocating small buffer
+// too many times.
+const int64_t kDefaultPreAllocateSizeInBytes = 64 * 1024;
+
+void ReleaseMemory(std::string* str) {
+ DCHECK(str);
+ std::string empty;
+ str->swap(empty);
+}
+
+void ReleaseMemory(script::PreallocatedArrayBufferData* data) {
+ DCHECK(data);
+ script::PreallocatedArrayBufferData empty;
+ data->Swap(&empty);
+}
+
+} // namespace
+
+URLFetcherResponseWriter::Buffer::Buffer(Type type) : type_(type) {}
+
+void URLFetcherResponseWriter::Buffer::DisablePreallocate() {
+ base::AutoLock auto_lock(lock_);
+
+ DCHECK_EQ(GetSize_Locked(), 0u);
+ allow_preallocate_ = false;
+}
+
+void URLFetcherResponseWriter::Buffer::Clear() {
+ base::AutoLock auto_lock(lock_);
+
+ ReleaseMemory(&data_as_string_);
+ ReleaseMemory(©_of_data_as_string_);
+ ReleaseMemory(&data_as_array_buffer_);
+
+ download_progress_ = 0;
+ data_as_array_buffer_size_ = 0;
+}
+
+int64_t URLFetcherResponseWriter::Buffer::GetAndResetDownloadProgress() {
+ base::AutoLock auto_lock(lock_);
+ download_progress_ = GetSize_Locked();
+ return static_cast<int64_t>(download_progress_);
+}
+
+bool URLFetcherResponseWriter::Buffer::HasProgressSinceLastGetAndReset() const {
+ base::AutoLock auto_lock(lock_);
+ return GetSize_Locked() > download_progress_;
+}
+
+const std::string&
+URLFetcherResponseWriter::Buffer::GetReferenceOfStringAndSeal() {
+ base::AutoLock auto_lock(lock_);
+
+ UpdateType_Locked(kString);
+ allow_write_ = false;
+
+ return data_as_string_;
+}
+
+const std::string&
+URLFetcherResponseWriter::Buffer::GetTemporaryReferenceOfString() {
+ base::AutoLock auto_lock(lock_);
+
+ // This function can be further optimized by always return reference of
+ // |data_as_string_|, and only make a copy when |data_as_string_| is extended.
+ // It is not done as GetTemporaryReferenceOfString() is currently not
+ // triggered. It will only be called when JS app is retrieving responseText
+ // while the request is still in progress.
+
+ if (type_ == kString) {
+ copy_of_data_as_string_ = data_as_string_;
+ } else {
+ DCHECK_EQ(type_, kArrayBuffer);
+ const char* begin = static_cast<const char*>(data_as_array_buffer_.data());
+ copy_of_data_as_string_.assign(begin, begin + data_as_array_buffer_size_);
+ }
+
+ return copy_of_data_as_string_;
+}
+
+void URLFetcherResponseWriter::Buffer::GetAndReset(std::string* str) {
+ DCHECK(str);
+
+ ReleaseMemory(str);
+
+ base::AutoLock auto_lock(lock_);
+
+ UpdateType_Locked(kString);
+
+ if (capacity_known_ && data_as_string_.size() != data_as_string_.capacity()) {
+ DLOG(WARNING) << "String size " << data_as_string_.size()
+ << " is different than its preset capacity "
+ << data_as_string_.capacity();
+ }
+
+ data_as_string_.swap(*str);
+}
+
+void URLFetcherResponseWriter::Buffer::GetAndReset(
+ PreallocatedArrayBufferData* data) {
+ DCHECK(data);
+
+ ReleaseMemory(data);
+
+ base::AutoLock auto_lock(lock_);
+
+ UpdateType_Locked(kArrayBuffer);
+
+ if (data_as_array_buffer_.byte_length() != data_as_array_buffer_size_) {
+ DCHECK_LT(data_as_array_buffer_size_, data_as_array_buffer_.byte_length());
+ DLOG_IF(WARNING, capacity_known_)
+ << "ArrayBuffer size " << data_as_array_buffer_size_
+ << " is different than its preset capacity "
+ << data_as_array_buffer_.byte_length();
+ data_as_array_buffer_.Resize(data_as_array_buffer_size_);
+ }
+ data_as_array_buffer_.Swap(data);
+}
+
+void URLFetcherResponseWriter::Buffer::MaybePreallocate(int64_t capacity) {
+ base::AutoLock auto_lock(lock_);
+
+ if (!allow_preallocate_) {
+ return;
+ }
+
+ if (capacity < 0) {
+ capacity = kDefaultPreAllocateSizeInBytes;
+ } else {
+ capacity_known_ = true;
+ }
+
+ if (capacity == 0) {
+ return;
+ }
+
+ switch (type_) {
+ case kString:
+ DCHECK_EQ(data_as_string_.size(), 0u);
+ data_as_string_.reserve(capacity);
+ return;
+ case kArrayBuffer:
+ DCHECK_EQ(data_as_array_buffer_size_, 0u);
+ data_as_array_buffer_.Resize(capacity);
+ return;
+ }
+ NOTREACHED();
+}
+
+void URLFetcherResponseWriter::Buffer::Write(const void* buffer,
+ int num_bytes) {
+ DCHECK_GE(num_bytes, 0);
+
+ if (num_bytes <= 0) {
+ return;
+ }
+
+ base::AutoLock auto_lock(lock_);
+
+ DCHECK(allow_write_);
+
+ if (!allow_write_) {
+ return;
+ }
+
+ if (type_ == kString) {
+ if (capacity_known_ &&
+ num_bytes + data_as_string_.size() >= data_as_string_.capacity()) {
+ SB_LOG(WARNING) << "Data written is larger than the preset capacity "
+ << data_as_string_.capacity();
+ }
+ data_as_string_.append(static_cast<const char*>(buffer), num_bytes);
+ return;
+ }
+
+ DCHECK_EQ(type_, kArrayBuffer);
+ if (data_as_array_buffer_size_ + num_bytes >
+ data_as_array_buffer_.byte_length()) {
+ if (capacity_known_) {
+ SB_LOG(WARNING) << "Data written is larger than the preset capacity "
+ << data_as_array_buffer_.byte_length();
+ }
+ data_as_array_buffer_.Resize(data_as_array_buffer_size_ + num_bytes);
+ }
+
+ auto destination = static_cast<uint8_t*>(data_as_array_buffer_.data()) +
+ data_as_array_buffer_size_;
+ SbMemoryCopy(destination, buffer, num_bytes);
+ data_as_array_buffer_size_ += num_bytes;
+}
+
+size_t URLFetcherResponseWriter::Buffer::GetSize_Locked() const {
+ lock_.AssertAcquired();
+
+ switch (type_) {
+ case kString:
+ return data_as_string_.size();
+ case kArrayBuffer:
+ return data_as_array_buffer_size_;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+void URLFetcherResponseWriter::Buffer::UpdateType_Locked(Type type) {
+ lock_.AssertAcquired();
+
+ if (type_ == type) {
+ return;
+ }
+
+ DCHECK(allow_write_);
+
+ DLOG_IF(WARNING, GetSize_Locked() > 0)
+ << "Change response type from " << type_ << " to " << type
+ << " after response is started, which is less efficient.";
+
+ if (type_ == kString) {
+ DCHECK_EQ(type, kArrayBuffer);
+ DCHECK_EQ(data_as_array_buffer_size_, 0u);
+ DCHECK_EQ(data_as_array_buffer_.byte_length(), 0u);
+ } else {
+ DCHECK_EQ(type_, kArrayBuffer);
+ DCHECK_EQ(type, kString);
+ DCHECK_EQ(data_as_string_.size(), 0u);
+ }
+
+ type_ = type;
+
+ if (type == kArrayBuffer) {
+ data_as_array_buffer_.Resize(data_as_string_.capacity());
+ data_as_array_buffer_size_ = data_as_string_.size();
+ SbMemoryCopy(data_as_array_buffer_.data(), data_as_string_.data(),
+ data_as_array_buffer_size_);
+
+ ReleaseMemory(&data_as_string_);
+ ReleaseMemory(©_of_data_as_string_);
+ return;
+ }
+
+ data_as_string_.reserve(data_as_array_buffer_.byte_length());
+ data_as_string_.append(static_cast<const char*>(data_as_array_buffer_.data()),
+ data_as_array_buffer_size_);
+
+ ReleaseMemory(&data_as_array_buffer_);
+ data_as_array_buffer_size_ = 0;
+}
+
+URLFetcherResponseWriter::URLFetcherResponseWriter(
+ const scoped_refptr<Buffer>& buffer)
+ : buffer_(buffer) {
+ DCHECK(buffer_);
+}
+
+URLFetcherResponseWriter::~URLFetcherResponseWriter() = default;
+
+int URLFetcherResponseWriter::Initialize(
+ net::CompletionOnceCallback /*callback*/) {
+ return net::OK;
+}
+
+void URLFetcherResponseWriter::OnResponseStarted(int64_t content_length) {
+ buffer_->MaybePreallocate(content_length);
+}
+
+int URLFetcherResponseWriter::Write(net::IOBuffer* buffer, int num_bytes,
+ net::CompletionOnceCallback /*callback*/) {
+ buffer_->Write(buffer->data(), num_bytes);
+ return num_bytes;
+}
+
+int URLFetcherResponseWriter::Finish(int /*net_error*/,
+ net::CompletionOnceCallback /*callback*/) {
+ return net::OK;
+}
+
+} // namespace xhr
+} // namespace cobalt
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.h b/src/cobalt/xhr/url_fetcher_buffer_writer.h
new file mode 100644
index 0000000..c82ce32
--- /dev/null
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.h
@@ -0,0 +1,112 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_XHR_URL_FETCHER_BUFFER_WRITER_H_
+#define COBALT_XHR_URL_FETCHER_BUFFER_WRITER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "cobalt/script/array_buffer.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_fetcher_response_writer.h"
+
+namespace cobalt {
+namespace xhr {
+
+class URLFetcherResponseWriter : public net::URLFetcherResponseWriter {
+ public:
+ class Buffer : public base::RefCountedThreadSafe<Buffer> {
+ public:
+ typedef script::PreallocatedArrayBufferData PreallocatedArrayBufferData;
+
+ enum Type {
+ kString,
+ kArrayBuffer,
+ };
+
+ explicit Buffer(Type type);
+
+ void DisablePreallocate();
+ void Clear();
+
+ int64_t GetAndResetDownloadProgress();
+ bool HasProgressSinceLastGetAndReset() const;
+
+ // When the following function is called, Write() can no longer be called to
+ // append more data. It is the responsibility of the user of this class to
+ // ensure such behavior.
+ const std::string& GetReferenceOfStringAndSeal();
+ // Returns a reference to a std::string containing a copy of the data
+ // downloaded so far. The reference is guaranteed to be valid until another
+ // public member function is called on this object.
+ const std::string& GetTemporaryReferenceOfString();
+
+ void GetAndReset(std::string* str);
+ void GetAndReset(PreallocatedArrayBufferData* data);
+
+ void MaybePreallocate(int64_t capacity);
+ void Write(const void* buffer, int num_bytes);
+
+ private:
+ size_t GetSize_Locked() const;
+
+ // It is possible (but extremely rare) that JS app changes response type
+ // after some data has been written on the network thread, in such case we
+ // allow to change the buffer type dynamically.
+ void UpdateType_Locked(Type type);
+
+ Type type_;
+ bool allow_preallocate_ = true;
+ bool capacity_known_ = false;
+
+ // This class can be accessed by both network and MainWebModule threads.
+ mutable base::Lock lock_;
+
+ bool allow_write_ = true;
+ size_t download_progress_ = 0;
+
+ // Data is stored in one of the following buffers, depends on the value of
+ // |type_|.
+ std::string data_as_string_;
+ // For use in GetReferenceOfString() so it can return a reference.
+ std::string copy_of_data_as_string_;
+ PreallocatedArrayBufferData data_as_array_buffer_;
+ size_t data_as_array_buffer_size_ = 0;
+ };
+
+ explicit URLFetcherResponseWriter(const scoped_refptr<Buffer>& buffer);
+ ~URLFetcherResponseWriter() override;
+
+ // URLFetcherResponseWriter overrides:
+ int Initialize(net::CompletionOnceCallback callback) override;
+ void OnResponseStarted(int64_t content_length) override;
+ int Write(net::IOBuffer* buffer, int num_bytes,
+ net::CompletionOnceCallback callback) override;
+ int Finish(int net_error, net::CompletionOnceCallback callback) override;
+
+ private:
+ scoped_refptr<Buffer> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherResponseWriter);
+};
+
+} // namespace xhr
+} // namespace cobalt
+
+#endif // COBALT_XHR_URL_FETCHER_BUFFER_WRITER_H_
diff --git a/src/cobalt/xhr/xhr.gyp b/src/cobalt/xhr/xhr.gyp
index eff72e7..86c029d 100644
--- a/src/cobalt/xhr/xhr.gyp
+++ b/src/cobalt/xhr/xhr.gyp
@@ -21,6 +21,8 @@
'target_name': 'xhr',
'type': 'static_library',
'sources': [
+ 'url_fetcher_buffer_writer.cc',
+ 'url_fetcher_buffer_writer.h',
'xhr_response_data.cc',
'xhr_response_data.h',
'xml_http_request.cc',
@@ -62,7 +64,6 @@
'dependencies': [
'<(DEPTH)/cobalt/base/base.gyp:base',
'<(DEPTH)/cobalt/dom/dom.gyp:dom',
- '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
'xhr',
@@ -71,6 +72,7 @@
# ScriptValueFactory has non-virtual method CreatePromise().
'<(DEPTH)/cobalt/script/engine.gyp:engine',
],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
},
{
'target_name': 'xhr_test_deploy',
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index de95cc4..0ae707f 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -51,10 +51,6 @@
// How many milliseconds must elapse between each progress event notification.
const int kProgressPeriodMs = 50;
-// Allocate 64KB on receiving the first chunk to avoid allocating small buffer
-// too many times.
-const size_t kInitialReceivingBufferSize = 64 * 1024;
-
const char* kResponseTypes[] = {
"", // kDefault
"text", // kText
@@ -168,6 +164,8 @@
XMLHttpRequest::XMLHttpRequest(script::EnvironmentSettings* settings)
: XMLHttpRequestEventTarget(settings),
+ response_body_(new URLFetcherResponseWriter::Buffer(
+ URLFetcherResponseWriter::Buffer::kString)),
settings_(base::polymorphic_downcast<dom::DOMSettings*>(settings)),
state_(kUnsent),
response_type_(kDefault),
@@ -203,7 +201,7 @@
}
ChangeState(kUnsent);
- response_body_.Clear();
+ response_body_->Clear();
response_array_buffer_reference_.reset();
}
@@ -485,7 +483,14 @@
return base::EmptyString();
}
- return response_body_.string();
+ // Note that the conversion from |response_body_| to std::string when |state_|
+ // isn't kDone isn't efficient for large responses. Fortunately this feature
+ // is rarely used.
+ if (state_ == kLoading) {
+ LOG(WARNING) << "Retrieving responseText while loading can be inefficient.";
+ return response_body_->GetTemporaryReferenceOfString();
+ }
+ return response_body_->GetReferenceOfStringAndSeal();
}
// https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-responsexml-attribute
@@ -659,19 +664,6 @@
fetch_mode_callback_->value().Run(is_cross_origin_);
}
- // Reserve space for the content in the case of a regular XHR request.
- DCHECK_EQ(response_body_.size(), 0u);
- if (!fetch_callback_) {
- const int64 content_length = http_response_headers_->GetContentLength();
-
- // If we know the eventual content length, allocate the total response body.
- // Otherwise just reserve a reasonably large initial chunk.
- size_t bytes_to_reserve = content_length > 0
- ? static_cast<size_t>(content_length)
- : kInitialReceivingBufferSize;
- response_body_.Reserve(bytes_to_reserve);
- }
-
// Further filter response headers as XHR's mode is cors
if (is_cross_origin_) {
size_t iter = 0;
@@ -707,7 +699,7 @@
ChangeState(kHeadersReceived);
- UpdateProgress();
+ UpdateProgress(0);
}
void XMLHttpRequest::OnURLFetchDownloadProgress(
@@ -717,27 +709,19 @@
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_NE(state_, kDone);
- auto* download_data_writer =
- base::polymorphic_downcast<loader::URLFetcherStringWriter*>(
- source->GetResponseWriter());
- std::unique_ptr<std::string> download_data = download_data_writer->data();
- if (!download_data.get() || download_data->empty()) {
+ if (response_body_->HasProgressSinceLastGetAndReset() == 0) {
return;
}
- // Preserve the response body only for regular XHR requests. Fetch requests
- // process the response in pieces, so do not need to keep the whole response.
- if (!fetch_callback_) {
- response_body_.Append(reinterpret_cast<const uint8*>(download_data->data()),
- download_data->size());
- }
// Signal to JavaScript that new data is now available.
ChangeState(kLoading);
if (fetch_callback_) {
+ std::string downloaded_data;
+ response_body_->GetAndReset(&downloaded_data);
script::Handle<script::Uint8Array> data =
script::Uint8Array::New(settings_->global_environment(),
- download_data->data(), download_data->size());
+ downloaded_data.data(), downloaded_data.size());
fetch_callback_->value().Run(data);
}
@@ -746,7 +730,9 @@
const base::TimeDelta elapsed(now - last_progress_time_);
if (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs)) {
last_progress_time_ = now;
- UpdateProgress();
+ // TODO: Investigate if we have to fire progress event with 0 loaded bytes
+ // when used as Fetch API.
+ UpdateProgress(response_body_->GetAndResetDownloadProgress());
}
}
@@ -787,7 +773,7 @@
FireProgressEvent(upload_, base::Tokens::loadend());
}
ChangeState(kDone);
- UpdateProgress();
+ UpdateProgress(response_body_->GetAndResetDownloadProgress());
// Undo the ref we added in Send()
DecrementActiveRequests();
} else {
@@ -1038,13 +1024,14 @@
// The request is done so it is safe to only keep the ArrayBuffer and clear
// |response_body_|. As |response_body_| will not be used unless the
// request is re-opened.
- auto array_buffer =
- script::ArrayBuffer::New(settings_->global_environment(),
- response_body_.data(), response_body_.size());
+ std::unique_ptr<script::PreallocatedArrayBufferData> downloaded_data(
+ new script::PreallocatedArrayBufferData());
+ response_body_->GetAndReset(downloaded_data.get());
+ auto array_buffer = script::ArrayBuffer::New(
+ settings_->global_environment(), std::move(downloaded_data));
response_array_buffer_reference_.reset(
new script::ScriptValue<script::ArrayBuffer>::Reference(this,
array_buffer));
- response_body_.Clear();
return array_buffer;
} else {
return script::Handle<script::ArrayBuffer>(
@@ -1052,10 +1039,9 @@
}
}
-void XMLHttpRequest::UpdateProgress() {
+void XMLHttpRequest::UpdateProgress(int64_t received_length) {
DCHECK(http_response_headers_);
const int64 content_length = http_response_headers_->GetContentLength();
- const int64 received_length = static_cast<int64>(response_body_.size());
const bool length_computable =
content_length > 0 && received_length <= content_length;
const uint64 total =
@@ -1111,15 +1097,24 @@
void XMLHttpRequest::StartRequest(const std::string& request_body) {
TRACK_MEMORY_SCOPE("XHR");
- response_body_.Clear();
response_array_buffer_reference_.reset();
network::NetworkModule* network_module =
settings_->fetcher_factory()->network_module();
url_fetcher_ = net::URLFetcher::Create(request_url_, method_, this);
url_fetcher_->SetRequestContext(network_module->url_request_context_getter());
+ if (fetch_callback_) {
+ response_body_ = new URLFetcherResponseWriter::Buffer(
+ URLFetcherResponseWriter::Buffer::kString);
+ response_body_->DisablePreallocate();
+ } else {
+ response_body_ = new URLFetcherResponseWriter::Buffer(
+ response_type_ == kArrayBuffer
+ ? URLFetcherResponseWriter::Buffer::kArrayBuffer
+ : URLFetcherResponseWriter::Buffer::kString);
+ }
std::unique_ptr<net::URLFetcherResponseWriter> download_data_writer(
- new loader::URLFetcherStringWriter());
+ new URLFetcherResponseWriter(response_body_));
url_fetcher_->SaveResponseWithWriter(std::move(download_data_writer));
// Don't retry, let the caller deal with it.
url_fetcher_->SetAutomaticallyRetryOn5xx(false);
@@ -1197,9 +1192,11 @@
(xhr.response_type_ == XMLHttpRequest::kDefault ||
xhr.response_type_ == XMLHttpRequest::kText)) {
size_t kMaxSize = 4096;
- response_text = base::StringPiece(
- reinterpret_cast<const char*>(xhr.response_body_.data()),
- std::min(kMaxSize, xhr.response_body_.size()));
+ const auto& response_body =
+ xhr.response_body_->GetTemporaryReferenceOfString();
+ response_text =
+ base::StringPiece(reinterpret_cast<const char*>(response_body.data()),
+ std::min(kMaxSize, response_body.size()));
}
std::string xhr_out = base::StringPrintf(
@@ -1231,6 +1228,8 @@
// https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#document-response-entity-body
scoped_refptr<dom::Document> XMLHttpRequest::GetDocumentResponseEntityBody() {
+ DCHECK_EQ(state_, kDone);
+
// Step 1..5
const std::string final_mime_type =
mime_type_override_.empty() ? response_mime_type_ : mime_type_override_;
@@ -1250,8 +1249,8 @@
base::Bind(&XMLHttpRequest::XMLDecoderLoadCompleteCallback,
base::Unretained(this)));
has_xml_decoder_error_ = false;
- xml_decoder.DecodeChunk(response_body_.string().c_str(),
- response_body_.string().size());
+ xml_decoder.DecodeChunk(response_body_->GetReferenceOfStringAndSeal().c_str(),
+ response_body_->GetReferenceOfStringAndSeal().size());
xml_decoder.Finish();
if (has_xml_decoder_error_) {
return NULL;
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h
index 6ff0f76..4dd5375 100644
--- a/src/cobalt/xhr/xml_http_request.h
+++ b/src/cobalt/xhr/xml_http_request.h
@@ -34,7 +34,7 @@
#include "cobalt/script/global_environment.h"
#include "cobalt/script/typed_arrays.h"
#include "cobalt/script/union_type.h"
-#include "cobalt/xhr/xhr_response_data.h"
+#include "cobalt/xhr/url_fetcher_buffer_writer.h"
#include "cobalt/xhr/xml_http_request_event_target.h"
#include "cobalt/xhr/xml_http_request_upload.h"
#include "net/http/http_request_headers.h"
@@ -216,7 +216,7 @@
// Return array buffer response body as an ArrayBuffer.
script::Handle<script::ArrayBuffer> response_array_buffer();
- void UpdateProgress();
+ void UpdateProgress(int64_t received_length);
void StartRequest(const std::string& request_body);
@@ -253,7 +253,7 @@
std::unique_ptr<net::URLFetcher> url_fetcher_;
scoped_refptr<net::HttpResponseHeaders> http_response_headers_;
- XhrResponseData response_body_;
+ scoped_refptr<URLFetcherResponseWriter::Buffer> response_body_;
std::unique_ptr<script::ScriptValue<script::ArrayBuffer>::Reference>
response_array_buffer_reference_;
scoped_refptr<XMLHttpRequestUpload> upload_;
diff --git a/src/content/browser/speech/speech.gyp b/src/content/browser/speech/speech.gyp
index 397b372..e562177 100644
--- a/src/content/browser/speech/speech.gyp
+++ b/src/content/browser/speech/speech.gyp
@@ -39,12 +39,12 @@
],
'dependencies': [
'speech',
- '<(DEPTH)/base/base.gyp:run_all_unittests',
'<(DEPTH)/base/base.gyp:test_support_base',
'<(DEPTH)/cobalt/media/media.gyp:media',
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/starboard/starboard.gyp:starboard',
],
+ 'includes': ['<(DEPTH)/base/test/test.gypi'],
},
{
diff --git a/src/crypto/crypto.gyp b/src/crypto/crypto.gyp
index 354d689..48610c6 100644
--- a/src/crypto/crypto.gyp
+++ b/src/crypto/crypto.gyp
@@ -88,10 +88,10 @@
'crypto',
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/base/base.gyp:test_support_base',
- '<(DEPTH)/base/base.gyp:run_all_unittests',
'<(DEPTH)/testing/gmock.gyp:gmock',
'<(DEPTH)/testing/gtest.gyp:gtest',
],
+ 'includes': ['<(DEPTH)/base/test/test.gypi'],
},
],
'conditions': [
diff --git a/src/nb/analytics/memory_tracker_impl.cc b/src/nb/analytics/memory_tracker_impl.cc
index 58eab36..6b08eea 100644
--- a/src/nb/analytics/memory_tracker_impl.cc
+++ b/src/nb/analytics/memory_tracker_impl.cc
@@ -231,7 +231,10 @@
const void* memory,
size_t size) {
// We might do something more interesting with MapMemory calls later.
+ MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context);
+ t->PushAllocationGroupByName("Mapped Memory");
OnMalloc(context, memory, size);
+ t->PopAllocationGroup();
}
void MemoryTrackerImpl::OnUnMapMem(void* context,
diff --git a/src/nb/fixed_no_free_allocator.cc b/src/nb/fixed_no_free_allocator.cc
index 9c60617..f4f8e1c 100644
--- a/src/nb/fixed_no_free_allocator.cc
+++ b/src/nb/fixed_no_free_allocator.cc
@@ -16,8 +16,6 @@
#include "nb/fixed_no_free_allocator.h"
-#include <algorithm>
-
#include "nb/pointer_arithmetic.h"
#include "starboard/common/log.h"
@@ -54,8 +52,6 @@
void* FixedNoFreeAllocator::Allocate(std::size_t* size,
std::size_t alignment,
bool align_pointer) {
- *size = std::max<std::size_t>(*size, 1);
-
// Find the next aligned memory available.
uint8_t* aligned_next_memory =
AsPointer(AlignUp(AsInteger(next_memory_), alignment));
diff --git a/src/nb/reuse_allocator_base.cc b/src/nb/reuse_allocator_base.cc
index 03e9903..95189ac 100644
--- a/src/nb/reuse_allocator_base.cc
+++ b/src/nb/reuse_allocator_base.cc
@@ -358,18 +358,45 @@
// 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
// large enough to fulfill |size|.
- size_t size_difference = size - free_blocks_.rbegin()->size();
- if (max_capacity_ && capacity_ + size_difference > max_capacity_) {
+ size_t free_address = AsInteger(free_blocks_.rbegin()->address());
+ size_t free_size = free_blocks_.rbegin()->size();
+ size_t aligned_address = AlignUp(free_address, alignment);
+ // In order to calculate |size_to_allocate|, we need to account for two
+ // possible scenarios: when |aligned_address| is within the free block region,
+ // or when it is after the free block region.
+ //
+ // Scenario 1:
+ //
+ // |free_address| |free_address + free_size|
+ // | |
+ // | <- free_size -> | <- size_to_allocate -> |
+ // --------------------------------------------
+ // |<- size -> |
+ // |
+ // |aligned_address|
+ //
+ // Scenario 2:
+ //
+ // |free_address|
+ // |
+ // | <- free_size -> | <- size_to_allocate -> |
+ // --------------------------------------------
+ // | | <- size -> |
+ // | |
+ // |free_address + free_size| |aligned_address|
+ size_t size_to_allocate = aligned_address + size - free_address - free_size;
+ if (max_capacity_ && capacity_ + size_to_allocate > max_capacity_) {
return free_blocks_.end();
}
- ptr = fallback_allocator_->AllocateForAlignment(&size_difference, alignment);
+ SB_DCHECK(size_to_allocate > 0);
+ ptr = fallback_allocator_->AllocateForAlignment(&size_to_allocate, 1);
if (ptr == NULL) {
return free_blocks_.end();
}
fallback_allocations_.push_back(ptr);
- capacity_ += size_difference;
- AddFreeBlock(MemoryBlock(ptr, size_difference));
+ capacity_ += size_to_allocate;
+ AddFreeBlock(MemoryBlock(ptr, size_to_allocate));
FreeBlockSet::iterator iter = free_blocks_.end();
--iter;
return iter->CanFullfill(size, alignment) ? iter : free_blocks_.end();
diff --git a/src/net/base/ip_endpoint.cc b/src/net/base/ip_endpoint.cc
index 1ed221f..076d49c 100644
--- a/src/net/base/ip_endpoint.cc
+++ b/src/net/base/ip_endpoint.cc
@@ -54,7 +54,7 @@
default:
NOTREACHED();
- break;
+ return false;
}
return true;
diff --git a/src/net/cert/internal/trust_store_in_memory_starboard.cc b/src/net/cert/internal/trust_store_in_memory_starboard.cc
index 3d53b20..e3f0856 100644
--- a/src/net/cert/internal/trust_store_in_memory_starboard.cc
+++ b/src/net/cert/internal/trust_store_in_memory_starboard.cc
@@ -75,8 +75,20 @@
#endif
return std::unordered_set<std::string>();
}
- SbDirectoryEntry dir_entry;
+
std::unordered_set<std::string> trusted_certs_on_disk;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> dir_entry(SB_FILE_MAX_NAME);
+
+ while (SbDirectoryGetNext(sb_certs_directory, dir_entry.data(),
+ dir_entry.size())) {
+ if (SbStringGetLength(dir_entry.data()) != kCertFileNameLength) {
+ continue;
+ }
+ trusted_certs_on_disk.emplace(dir_entry.data());
+ }
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ SbDirectoryEntry dir_entry;
while (SbDirectoryGetNext(sb_certs_directory, &dir_entry)) {
if (SbStringGetLength(dir_entry.name) != kCertFileNameLength) {
@@ -84,6 +96,8 @@
}
trusted_certs_on_disk.emplace(dir_entry.name);
}
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
SbDirectoryClose(sb_certs_directory);
return std::move(trusted_certs_on_disk);
}
diff --git a/src/net/dial/dial_udp_server.cc b/src/net/dial/dial_udp_server.cc
index affd934..bca1005 100644
--- a/src/net/dial/dial_udp_server.cc
+++ b/src/net/dial/dial_udp_server.cc
@@ -140,20 +140,23 @@
// If M-Search request was valid, send response. Else, keep quiet.
if (ParseSearchRequest(std::string(read_buf_->data()))) {
auto response = std::make_unique<std::string>();
- *response = ConstructSearchResponse();
+ *response = std::move(ConstructSearchResponse());
// Using the fake IOBuffer to avoid another copy.
scoped_refptr<WrappedIOBuffer> fake_buffer =
new WrappedIOBuffer(response->data());
// After optimization, some compiler will dereference and get response size
// later than passing response.
auto response_size = response->size();
- auto result = socket_->SendTo(
+ int result = socket_->SendTo(
fake_buffer.get(), response_size, client_address_,
- base::Bind([](scoped_refptr<WrappedIOBuffer>,
- std::unique_ptr<std::string>, int /*rv*/) {},
+ base::Bind(&DialUdpServer::WriteComplete, base::Unretained(this),
fake_buffer, base::Passed(&response)));
- if (result < 0) {
- DLOG(WARNING) << "Socket SentTo error code: " << result;
+ if (result == ERR_IO_PENDING) {
+ // WriteComplete is responsible for posting the next callback to accept
+ // connection.
+ return;
+ } else if (result < 0) {
+ LOG(ERROR) << "UDPSocket SendTo error: " << result;
}
}
@@ -166,6 +169,17 @@
base::Unretained(this)));
}
+void DialUdpServer::WriteComplete(scoped_refptr<WrappedIOBuffer>,
+ std::unique_ptr<std::string>,
+ int rv) {
+ if (rv < 0) {
+ LOG(ERROR) << "UDPSocket completion callback error: " << rv;
+ }
+ thread_.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&DialUdpServer::AcceptAndProcessConnection,
+ base::Unretained(this)));
+}
+
// Parse a request to make sure it is a M-Search.
bool DialUdpServer::ParseSearchRequest(const std::string& request) {
HttpServerRequestInfo info;
@@ -215,7 +229,7 @@
// Since we are constructing a response from user-generated string,
// ensure all user-generated strings pass through StringPrintf.
-const std::string DialUdpServer::ConstructSearchResponse() const {
+std::string DialUdpServer::ConstructSearchResponse() const {
DCHECK(!location_url_.empty());
std::string ret("HTTP/1.1 200 OK\r\n");
@@ -237,7 +251,7 @@
DialSystemConfig::GetInstance()->model_uuid(),
kDialStRequest));
ret.append("\r\n");
- return ret;
+ return std::move(ret);
}
} // namespace net
diff --git a/src/net/dial/dial_udp_server.h b/src/net/dial/dial_udp_server.h
index c3115c9..7af175a 100644
--- a/src/net/dial/dial_udp_server.h
+++ b/src/net/dial/dial_udp_server.h
@@ -30,6 +30,10 @@
virtual void DidClose(UDPSocket* sock);
+ void WriteComplete(scoped_refptr<WrappedIOBuffer>,
+ std::unique_ptr<std::string>,
+ int rv);
+
private:
FRIEND_TEST_ALL_PREFIXES(DialUdpServerTest, ParseSearchRequest);
@@ -42,7 +46,7 @@
void AcceptAndProcessConnection();
// Construct the appropriate search response.
- const std::string ConstructSearchResponse() const;
+ std::string ConstructSearchResponse() const;
// Parse a request to make sure it is a M-Search.
static bool ParseSearchRequest(const std::string& request);
diff --git a/src/net/disk_cache/simple/simple_index_file_starboard.cc b/src/net/disk_cache/simple/simple_index_file_starboard.cc
index 3f9634b..29d913b 100644
--- a/src/net/disk_cache/simple/simple_index_file_starboard.cc
+++ b/src/net/disk_cache/simple/simple_index_file_starboard.cc
@@ -33,7 +33,19 @@
PLOG(ERROR) << "opendir " << cache_path.value() << ", erron: " << error;
return false;
}
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME);
+
+ while (true) {
+ if (!SbDirectoryGetNext(dir, entry.data(), entry.size())) {
+ PLOG(ERROR) << "readdir " << cache_path.value();
+ return false;
+ }
+
+ const std::string file_name(entry.data());
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry;
+
while (true) {
if (!SbDirectoryGetNext(dir, &entry)) {
PLOG(ERROR) << "readdir " << cache_path.value();
@@ -41,6 +53,7 @@
}
const std::string file_name(entry.name);
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
if (file_name == "." || file_name == "..")
continue;
const base::FilePath file_path =
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/url_request/url_fetcher_core.cc b/src/net/url_request/url_fetcher_core.cc
index 6d7b7f4..48c99c0 100644
--- a/src/net/url_request/url_fetcher_core.cc
+++ b/src/net/url_request/url_fetcher_core.cc
@@ -27,15 +27,45 @@
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_throttler_manager.h"
+#include "starboard/time.h"
#include "starboard/types.h"
#include "url/origin.h"
namespace {
+#if defined(STARBOARD)
+const SbTime kInformDownloadProgressInterval = 50 * kSbTimeMillisecond;
+#else // defined(STARBOARD)
const int kBufferSize = 4096;
+#endif // defined(STARBOARD)
+
const int kUploadProgressTimerInterval = 100;
+
bool g_ignore_certificate_requests = false;
+#if defined(STARBOARD)
+int GetIOBufferSizeByContentSize(int content_size) {
+ // If |content_size| is unknown, use 64k as buffer size.
+ if (content_size < 0) {
+ return 64 * 1024;
+ }
+ // If the content is really small, use 4k anyway.
+ if (content_size <= 4 * 1024) {
+ return 4 * 1024;
+ }
+ // If the content is medium sized, use the size as buffer size.
+ if (content_size < 64 * 1024) {
+ return content_size;
+ }
+ // If the content is fairly large, use a much larger buffer size.
+ if (content_size >= 512 * 1024) {
+ return 256 * 1024;
+ }
+ // Otherwise use 64k as buffer size.
+ return 64 * 1024;
+}
+#endif // defined(STARBOARD)
+
} // namespace
namespace net {
@@ -80,6 +110,9 @@
load_flags_(LOAD_NORMAL),
allow_credentials_(base::nullopt),
response_code_(URLFetcher::RESPONSE_CODE_INVALID),
+#if defined(STARBOARD)
+ io_buffer_size_(GetIOBufferSizeByContentSize(-1)),
+#endif // defined(STARBOARD)
url_request_data_key_(NULL),
was_fetched_via_proxy_(false),
was_cached_(false),
@@ -442,15 +475,22 @@
}
DCHECK(!buffer_);
- if (request_type_ != URLFetcher::HEAD)
- buffer_ = base::MakeRefCounted<IOBuffer>(kBufferSize);
#if defined(STARBOARD)
+ if (request_type_ != URLFetcher::HEAD) {
+ response_writer_->OnResponseStarted(total_response_bytes_);
+ io_buffer_size_ = GetIOBufferSizeByContentSize(total_response_bytes_);
+ buffer_ = base::MakeRefCounted<IOBuffer>(io_buffer_size_);
+ }
+
// We update this earlier than OnReadCompleted(), so that the delegate
// can know about it if they call GetURL() in any callback.
if (!stopped_on_redirect_) {
url_ = request_->url();
}
InformDelegateResponseStarted();
+#else // defined(STARBOARD)
+ if (request_type_ != URLFetcher::HEAD)
+ buffer_ = base::MakeRefCounted<IOBuffer>(kBufferSize);
#endif // defined(STARBOARD)
ReadResponse();
}
@@ -480,6 +520,35 @@
if (throttler_manager)
url_throttler_entry_ = throttler_manager->RegisterRequestUrl(url_);
+#if defined(STARBOARD)
+ // Prime it to the current time so it is only called after the loop, or every
+ // time when the loop takes |kInformDownloadProgressInterval|.
+ SbTime download_progress_informed_at = SbTimeGetMonotonicNow();
+ bool did_read_after_inform_download_progress = false;
+
+ while (bytes_read > 0) {
+ current_response_bytes_ += bytes_read;
+ did_read_after_inform_download_progress = true;
+ auto now = SbTimeGetMonotonicNow();
+ if (now - download_progress_informed_at > kInformDownloadProgressInterval) {
+ InformDelegateDownloadProgress();
+ download_progress_informed_at = now;
+ did_read_after_inform_download_progress = false;
+ }
+
+ const int result = WriteBuffer(
+ base::MakeRefCounted<DrainableIOBuffer>(buffer_, bytes_read));
+ if (result < 0) {
+ // Write failed or waiting for write completion.
+ return;
+ }
+ bytes_read = request_->Read(buffer_.get(), io_buffer_size_);
+ }
+
+ if (did_read_after_inform_download_progress) {
+ InformDelegateDownloadProgress();
+ }
+#else // defined(STARBOARD)
while (bytes_read > 0) {
current_response_bytes_ += bytes_read;
InformDelegateDownloadProgress();
@@ -492,6 +561,7 @@
}
bytes_read = request_->Read(buffer_.get(), kBufferSize);
}
+#endif // defined(STARBOARD)
// See comments re: HEAD requests in ReadResponse().
if (bytes_read != ERR_IO_PENDING || request_type_ == URLFetcher::HEAD) {
@@ -933,8 +1003,13 @@
// completed immediately, without trying to read any data back (all we care
// about is the response code and headers, which we already have).
int bytes_read = 0;
+#if defined(STARBOARD)
+ if (request_type_ != URLFetcher::HEAD)
+ bytes_read = request_->Read(buffer_.get(), io_buffer_size_);
+#else // defined(STARBOARD)
if (request_type_ != URLFetcher::HEAD)
bytes_read = request_->Read(buffer_.get(), kBufferSize);
+#endif // defined(STARBOARD)
OnReadCompleted(request_.get(), bytes_read);
}
diff --git a/src/net/url_request/url_fetcher_core.h b/src/net/url_request/url_fetcher_core.h
index 05ebd67..3a18e18 100644
--- a/src/net/url_request/url_fetcher_core.h
+++ b/src/net/url_request/url_fetcher_core.h
@@ -258,6 +258,10 @@
// Whether credentials are sent along with the request.
base::Optional<bool> allow_credentials_;
int response_code_; // HTTP status code for the request
+
+#if defined(STARBOARD)
+ int io_buffer_size_;
+#endif // defined(STARBOARD)
scoped_refptr<IOBuffer> buffer_;
// Read buffer
scoped_refptr<URLRequestContextGetter> request_context_getter_;
diff --git a/src/net/url_request/url_fetcher_response_writer.h b/src/net/url_request/url_fetcher_response_writer.h
index 1076a3e..bd4ca14 100644
--- a/src/net/url_request/url_fetcher_response_writer.h
+++ b/src/net/url_request/url_fetcher_response_writer.h
@@ -37,6 +37,13 @@
// Initialize() success results in discarding already written data.
virtual int Initialize(CompletionOnceCallback callback) = 0;
+#if defined(STARBOARD)
+ // The user of this class *may* call this function before any calls to Write()
+ // to prime the instance with response size, so it has a chance to do some
+ // preparation work, like pre-allocate the buffer.
+ virtual void OnResponseStarted(int64_t content_length) = 0;
+#endif // defined(STARBOARD)
+
// Writes |num_bytes| bytes in |buffer|, and returns the number of bytes
// written or an error code. If ERR_IO_PENDING is returned, |callback| will be
// run later with the result.
@@ -70,6 +77,9 @@
// URLFetcherResponseWriter overrides:
int Initialize(CompletionOnceCallback callback) override;
+#if defined(STARBOARD)
+ void OnResponseStarted(int64_t /*content_length*/) override {}
+#endif // defined(STARBOARD)
int Write(IOBuffer* buffer,
int num_bytes,
CompletionOnceCallback callback) override;
@@ -96,6 +106,9 @@
// URLFetcherResponseWriter overrides:
int Initialize(CompletionOnceCallback callback) override;
+#if defined(STARBOARD)
+ void OnResponseStarted(int64_t /*content_length*/) override {}
+#endif // defined(STARBOARD)
int Write(IOBuffer* buffer,
int num_bytes,
CompletionOnceCallback callback) override;
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/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 2105e5b..4a091f8 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -19,7 +19,9 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioTrack;
import android.os.Build;
import dev.cobalt.util.Log;
import dev.cobalt.util.UsedByNative;
@@ -105,4 +107,25 @@
}
return maxChannels;
}
+
+ /** Returns the minimum buffer size of AudioTrack. */
+ @SuppressWarnings("unused")
+ @UsedByNative
+ int getMinBufferSize(int sampleType, int sampleRate, int channelCount) {
+ int channelConfig;
+ switch (channelCount) {
+ case 1:
+ channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ break;
+ case 2:
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ break;
+ case 6:
+ channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
+ break;
+ default:
+ throw new RuntimeException("Unsupported channel count: " + channelCount);
+ }
+ return AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType);
+ }
}
diff --git a/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc b/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
index 2bd7c3f..c6af790 100644
--- a/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
+++ b/src/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
@@ -20,10 +20,6 @@
int SbAudioSinkGetMinBufferSizeInFrames(int channels,
SbMediaAudioSampleType sample_type,
int sampling_frequency_hz) {
- // Currently, we only use |min_required_frames_| for web audio, which
- // only supports 48k mono or stereo sound.
- SB_DCHECK(sampling_frequency_hz == 48000);
-
if (channels <= 0 || channels > SbAudioSinkGetMaxChannels()) {
SB_LOG(ERROR) << "Not support channels count " << channels;
return -1;
@@ -33,7 +29,7 @@
SB_LOG(ERROR) << "Not support sample type " << sample_type;
return -1;
}
- if (sampling_frequency_hz <= 0) {
+ if (sampling_frequency_hz <= 0 || sampling_frequency_hz >= 50000) {
SB_LOG(ERROR) << "Not support sample frequency " << sampling_frequency_hz;
return -1;
}
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index e9a87a3..6276641 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -39,22 +39,18 @@
}
} // namespace
-MinRequiredFramesTester::MinRequiredFramesTester(int audio_sink_buffer_size,
- int max_required_frames,
- int default_required_frames,
+MinRequiredFramesTester::MinRequiredFramesTester(int max_required_frames,
int required_frames_increment,
int min_stable_played_frames)
- : audio_sink_buffer_size_(audio_sink_buffer_size),
- max_required_frames_(max_required_frames),
- default_required_frames_(default_required_frames),
+ : max_required_frames_(max_required_frames),
required_frames_increment_(required_frames_increment),
min_stable_played_frames_(min_stable_played_frames),
condition_variable_(mutex_),
- destroyed_(false) {}
+ destroying_(false) {}
MinRequiredFramesTester::~MinRequiredFramesTester() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
- destroyed_.store(true);
+ destroying_.store(true);
if (SbThreadIsValid(tester_thread_)) {
{
ScopedLock scoped_lock(mutex_);
@@ -65,19 +61,24 @@
}
}
-void MinRequiredFramesTester::StartTest(
+void MinRequiredFramesTester::AddTest(
int number_of_channels,
SbMediaAudioSampleType sample_type,
int sample_rate,
- OnMinRequiredFramesReceivedCallback received_cb) {
+ const OnMinRequiredFramesReceivedCallback& received_cb,
+ int default_required_frames) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
- // MinRequiredFramesTester only supports to do test once now.
+ // MinRequiredFramesTester doesn't support to add test after starts.
SB_DCHECK(!SbThreadIsValid(tester_thread_));
- number_of_channels_ = number_of_channels;
- sample_type_ = sample_type;
- sample_rate_ = sample_rate;
- received_cb_ = received_cb;
+ test_tasks_.emplace_back(number_of_channels, sample_type, sample_rate,
+ received_cb, default_required_frames);
+}
+
+void MinRequiredFramesTester::Start() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ // MinRequiredFramesTester only supports to start once.
+ SB_DCHECK(!SbThreadIsValid(tester_thread_));
tester_thread_ =
SbThreadCreate(0, kSbThreadPriorityLowest, kSbThreadNoAffinity, true,
@@ -97,45 +98,51 @@
void MinRequiredFramesTester::TesterThreadFunc() {
bool wait_timeout = false;
- // Currently, we only support test once. But we can put following codes in
- // a for loop easily to support test multiple times.
- std::vector<uint8_t> silence_buffer(
- max_required_frames_ * number_of_channels_ * GetSampleSize(sample_type_),
- 0);
- void* frame_buffers[1];
- frame_buffers[0] = silence_buffer.data();
- // Set default values.
- min_required_frames_ = default_required_frames_;
- total_consumed_frames_ = 0;
- last_underrun_count_ = -1;
- last_total_consumed_frames_ = 0;
- {
- ScopedLock scoped_lock(mutex_);
- // Need to check |destroyed_| before start, as MinRequiredFramesTester may
+ for (const TestTask& task : test_tasks_) {
+ // Need to check |destroying_| before start, as MinRequiredFramesTester may
// be destroyed immediately after tester thread started.
- if (!destroyed_.load()) {
- audio_sink_ = new AudioTrackAudioSink(
- NULL, number_of_channels_, sample_rate_, sample_type_, frame_buffers,
- max_required_frames_,
- audio_sink_buffer_size_ * number_of_channels_ *
- GetSampleSize(sample_type_),
- &MinRequiredFramesTester::UpdateSourceStatusFunc,
- &MinRequiredFramesTester::ConsumeFramesFunc, this);
- wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
- if (wait_timeout) {
- SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
- SB_NOTREACHED();
- }
+ if (destroying_.load()) {
+ break;
}
- }
- delete audio_sink_;
- audio_sink_ = nullptr;
- // Call |received_cb_| after audio sink thread is ended.
- // |number_of_channels_|, |sample_type_|, |sample_rate_| and
- // |min_required_frames_| are shared between two threads.
- if (!destroyed_.load() && !wait_timeout) {
- received_cb_(number_of_channels_, sample_type_, sample_rate_,
- min_required_frames_);
+ std::vector<uint8_t> silence_buffer(max_required_frames_ *
+ task.number_of_channels *
+ GetSampleSize(task.sample_type),
+ 0);
+ void* frame_buffers[1];
+ frame_buffers[0] = silence_buffer.data();
+
+ // Set default values.
+ min_required_frames_ = task.default_required_frames;
+ total_consumed_frames_ = 0;
+ last_underrun_count_ = -1;
+ last_total_consumed_frames_ = 0;
+
+ audio_sink_ = new AudioTrackAudioSink(
+ NULL, task.number_of_channels, task.sample_rate, task.sample_type,
+ frame_buffers, max_required_frames_,
+ min_required_frames_ * task.number_of_channels *
+ GetSampleSize(task.sample_type),
+ &MinRequiredFramesTester::UpdateSourceStatusFunc,
+ &MinRequiredFramesTester::ConsumeFramesFunc, this);
+ {
+ ScopedLock scoped_lock(mutex_);
+ wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
+ }
+
+ if (wait_timeout) {
+ SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
+ SB_NOTREACHED();
+ }
+
+ delete audio_sink_;
+ audio_sink_ = nullptr;
+
+ // Call |received_cb_| after audio sink thread is ended.
+ // |min_required_frames_| is shared between two threads.
+ if (!destroying_.load() && !wait_timeout) {
+ task.received_cb(task.number_of_channels, task.sample_type,
+ task.sample_rate, min_required_frames_);
+ }
}
}
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
index 9e53134..290011d 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -17,6 +17,7 @@
#include <atomic>
#include <functional>
+#include <vector>
#include "starboard/common/condition_variable.h"
#include "starboard/common/mutex.h"
@@ -40,19 +41,39 @@
int min_required_frames)>
OnMinRequiredFramesReceivedCallback;
- MinRequiredFramesTester(int audio_sink_buffer_size,
- int max_required_frames,
- int default_required_frames,
+ MinRequiredFramesTester(int max_required_frames,
int required_frames_increment,
int min_stable_played_frames);
~MinRequiredFramesTester();
- void StartTest(int number_of_channels,
- SbMediaAudioSampleType sample_type,
- int sample_rate,
- OnMinRequiredFramesReceivedCallback received_cb);
+ void AddTest(int number_of_channels,
+ SbMediaAudioSampleType sample_type,
+ int sample_rate,
+ const OnMinRequiredFramesReceivedCallback& received_cb,
+ int default_required_frames);
+
+ void Start();
private:
+ struct TestTask {
+ TestTask(int number_of_channels,
+ SbMediaAudioSampleType sample_type,
+ int sample_rate,
+ OnMinRequiredFramesReceivedCallback received_cb,
+ int default_required_frames)
+ : number_of_channels(number_of_channels),
+ sample_type(sample_type),
+ sample_rate(sample_rate),
+ received_cb(received_cb),
+ default_required_frames(default_required_frames) {}
+
+ const int number_of_channels;
+ const SbMediaAudioSampleType sample_type;
+ const int sample_rate;
+ const OnMinRequiredFramesReceivedCallback received_cb;
+ const int default_required_frames;
+ };
+
static void* TesterThreadEntryPoint(void* context);
void TesterThreadFunc();
@@ -73,24 +94,16 @@
MinRequiredFramesTester(const MinRequiredFramesTester&) = delete;
MinRequiredFramesTester& operator=(const MinRequiredFramesTester&) = delete;
- const int audio_sink_buffer_size_;
const int max_required_frames_;
- const int default_required_frames_;
const int required_frames_increment_;
const int min_stable_played_frames_;
::starboard::shared::starboard::ThreadChecker thread_checker_;
- // Shared variables between tester thread and audio sink thread.
+ std::vector<const TestTask> test_tasks_;
AudioTrackAudioSink* audio_sink_ = nullptr;
- int number_of_channels_;
- SbMediaAudioSampleType sample_type_;
- int sample_rate_;
int min_required_frames_;
- // Used only by tester thread.
- OnMinRequiredFramesReceivedCallback received_cb_;
-
// Used only by audio sink thread.
int total_consumed_frames_;
int last_underrun_count_;
@@ -99,7 +112,7 @@
Mutex mutex_;
ConditionVariable condition_variable_;
SbThread tester_thread_ = kSbThreadInvalid;
- std::atomic_bool destroyed_;
+ std::atomic_bool destroying_;
};
} // namespace shared
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 994d904..363d4f9 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -35,12 +35,13 @@
const jint kNoOffset = 0;
const size_t kSilenceFramesPerAppend = 1024;
-const int kAudioSinkBufferSize = 4 * 1024;
const int kMaxRequiredFrames = 16 * 1024;
-const int kDefaultRequiredFrames = 8 * 1024;
const int kRequiredFramesIncrement = 2 * 1024;
const int kMinStablePlayedFrames = 12 * 1024;
+const int kSampleFrequency22k = 22050;
+const int kSampleFrequency48k = 48000;
+
// Helper function to compute the size of the two valid starboard audio sample
// types.
size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
@@ -80,7 +81,7 @@
SbMediaAudioSampleType sample_type,
SbAudioSinkFrameBuffers frame_buffers,
int frames_per_channel,
- int preferred_buffer_size,
+ int preferred_buffer_size_in_bytes,
SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
SbAudioSinkConsumeFramesFunc consume_frame_func,
void* context)
@@ -113,7 +114,7 @@
j_audio_output_manager.Get(), "createAudioTrackBridge",
"(IIII)Ldev/cobalt/media/AudioTrackBridge;",
GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_,
- preferred_buffer_size);
+ preferred_buffer_size_in_bytes);
if (!j_audio_track_bridge) {
return;
}
@@ -358,16 +359,25 @@
int sampling_frequency_hz) {
SB_DCHECK(audio_track_audio_sink_type_);
- return audio_track_audio_sink_type_->min_required_frames_.load();
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_audio_output_manager(
+ env->CallStarboardObjectMethodOrAbort(
+ "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+ int audio_track_min_buffer_size = static_cast<int>(env->CallIntMethodOrAbort(
+ j_audio_output_manager.Get(), "getMinBufferSize", "(III)I",
+ GetAudioFormatSampleType(sample_type), sampling_frequency_hz, channels));
+ int audio_track_min_buffer_size_in_frames =
+ audio_track_min_buffer_size / channels / GetSampleSize(sample_type);
+ return std::max(
+ audio_track_min_buffer_size_in_frames,
+ audio_track_audio_sink_type_->GetMinBufferSizeInFramesInternal(
+ channels, sample_type, sampling_frequency_hz));
}
AudioTrackAudioSinkType::AudioTrackAudioSinkType()
- : min_required_frames_tester_(kAudioSinkBufferSize,
- kMaxRequiredFrames,
- kDefaultRequiredFrames,
+ : min_required_frames_tester_(kMaxRequiredFrames,
kRequiredFramesIncrement,
- kMinStablePlayedFrames),
- min_required_frames_(kMaxRequiredFrames) {}
+ kMinStablePlayedFrames) {}
SbAudioSink AudioTrackAudioSinkType::Create(
int channels,
@@ -384,13 +394,13 @@
// large buffer may cause AudioTrack not able to start. And Cobalt now write
// no more than 1s of audio data and no more than 0.5 ahead to starboard
// player, limit the buffer size to store at most 0.5s of audio data.
- int preferred_buffer_size =
+ int preferred_buffer_size_in_bytes =
std::min(frames_per_channel, sampling_frequency_hz / 2) * channels *
GetSampleSize(audio_sample_type);
AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink(
this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
- frames_per_channel, preferred_buffer_size, update_source_status_func,
- consume_frames_func, context);
+ frames_per_channel, preferred_buffer_size_in_bytes,
+ update_source_status_func, consume_frames_func, context);
if (!audio_sink->IsAudioTrackValid()) {
SB_DLOG(ERROR)
<< "AudioTrackAudioSinkType::Create failed to create audio track";
@@ -407,7 +417,8 @@
SB_LOG(INFO) << "Received min required frames " << min_required_frames
<< " for " << number_of_channels << " channels, "
<< sample_rate << "hz.";
- min_required_frames_.store(min_required_frames);
+ ScopedLock lock(min_required_frames_map_mutex_);
+ min_required_frames_map_[sample_rate] = min_required_frames;
};
SbMediaAudioSampleType sample_type = kSbMediaAudioSampleTypeFloat32;
@@ -415,12 +426,33 @@
sample_type = kSbMediaAudioSampleTypeInt16Deprecated;
SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type));
}
+ min_required_frames_tester_.AddTest(2, sample_type, kSampleFrequency48k,
+ onMinRequiredFramesForWebAudioReceived,
+ 8 * 1024);
+ min_required_frames_tester_.AddTest(2, sample_type, kSampleFrequency22k,
+ onMinRequiredFramesForWebAudioReceived,
+ 4 * 1024);
+ min_required_frames_tester_.Start();
+}
- // Currently, cobalt only use |min_required_frames_| for web audio, which
- // only supports 48k mono or stereo sound. It should be fine now to only
- // test 48k stereo sound.
- min_required_frames_tester_.StartTest(2, sample_type, 48000,
- onMinRequiredFramesForWebAudioReceived);
+int AudioTrackAudioSinkType::GetMinBufferSizeInFramesInternal(
+ int channels,
+ SbMediaAudioSampleType sample_type,
+ int sampling_frequency_hz) {
+ if (sampling_frequency_hz <= kSampleFrequency22k) {
+ ScopedLock lock(min_required_frames_map_mutex_);
+ if (min_required_frames_map_.find(kSampleFrequency22k) !=
+ min_required_frames_map_.end()) {
+ return min_required_frames_map_[kSampleFrequency22k];
+ }
+ } else if (sampling_frequency_hz <= kSampleFrequency48k) {
+ ScopedLock lock(min_required_frames_map_mutex_);
+ if (min_required_frames_map_.find(kSampleFrequency48k) !=
+ min_required_frames_map_.end()) {
+ return min_required_frames_map_[kSampleFrequency48k];
+ }
+ }
+ return kMaxRequiredFrames;
}
} // namespace shared
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index 821a146..966fa27 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -17,6 +17,7 @@
#include <atomic>
#include <functional>
+#include <map>
#include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
#include "starboard/android/shared/jni_env_ext.h"
@@ -68,8 +69,14 @@
void TestMinRequiredFrames();
private:
- std::atomic_int min_required_frames_;
+ int GetMinBufferSizeInFramesInternal(int channels,
+ SbMediaAudioSampleType sample_type,
+ int sampling_frequency_hz);
+
MinRequiredFramesTester min_required_frames_tester_;
+ Mutex min_required_frames_map_mutex_;
+ // The minimum frames required to avoid underruns of different frequencies.
+ std::map<int, int> min_required_frames_map_;
};
class AudioTrackAudioSink : public SbAudioSinkPrivate {
diff --git a/src/starboard/android/shared/configuration_constants.cc b/src/starboard/android/shared/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/android/shared/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h
index 7abc208..c5110f8 100644
--- a/src/starboard/android/shared/configuration_public.h
+++ b/src/starboard/android/shared/configuration_public.h
@@ -291,9 +291,6 @@
// specify that.
#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
// Determines the threshhold of allocation size that should be done with mmap
// (if available), rather than allocated within the core heap.
#define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -330,9 +327,6 @@
// to not include here to decrease symbol pollution.
#define SB_MAX_THREAD_LOCAL_KEYS 128
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
// --- Tuneable Parameters ---------------------------------------------------
// Specifies the network receive buffer size in bytes, set via
diff --git a/src/starboard/android/shared/directory_get_next.cc b/src/starboard/android/shared/directory_get_next.cc
index f1e5b02..525fdf4 100644
--- a/src/starboard/android/shared/directory_get_next.cc
+++ b/src/starboard/android/shared/directory_get_next.cc
@@ -15,21 +15,24 @@
#include "starboard/directory.h"
#include <android/asset_manager.h>
+#include <string.h>
#include "starboard/android/shared/directory_internal.h"
#include "starboard/shared/iso/impl/directory_get_next.h"
-bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
- if (directory && directory->asset_dir && out_entry) {
+bool SbDirectoryGetNext(SbDirectory directory,
+ char* out_entry,
+ size_t out_entry_size) {
+ if (directory && directory->asset_dir && out_entry &&
+ out_entry_size >= SB_FILE_MAX_NAME) {
const char* file_name = AAssetDir_getNextFileName(directory->asset_dir);
if (file_name == NULL) {
return false;
}
- size_t size = SB_ARRAY_SIZE_INT(out_entry->name);
- SbStringCopy(out_entry->name, file_name, size);
+ SbStringCopy(out_entry, file_name, out_entry_size);
return true;
}
- return ::starboard::shared::iso::impl::SbDirectoryGetNext(directory,
- out_entry);
+ return ::starboard::shared::iso::impl::SbDirectoryGetNext(
+ directory, out_entry, out_entry_size);
}
diff --git a/src/starboard/android/shared/gyp_configuration.gypi b/src/starboard/android/shared/gyp_configuration.gypi
index c06cc60..d5f23e0 100644
--- a/src/starboard/android/shared/gyp_configuration.gypi
+++ b/src/starboard/android/shared/gyp_configuration.gypi
@@ -20,7 +20,8 @@
'target_os': 'android',
'final_executable_type': 'shared_library',
'gtest_target_type': 'shared_library',
- 'sb_widevine_platform' : 'android',
+ 'sb_widevine_platform': 'android',
+ 'sb_enable_benchmark': 1,
'gl_type': 'system_gles2',
'enable_remote_debugging': 0,
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index f1a3dfb..24ed8a4 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -343,7 +343,7 @@
std::vector<int>* input_buffer_indices) {
SB_DCHECK(media_codec_bridge_);
- // During secure playback, and only secure playback, is is possible that our
+ // During secure playback, and only secure playback, it is possible that our
// attempt to enqueue an input buffer will be rejected by MediaCodec because
// we do not have a key yet. In this case, we hold on to the input buffer
// that we have already set up, and repeatedly attempt to enqueue it until
diff --git a/src/starboard/android/shared/player_components_impl.cc b/src/starboard/android/shared/player_components_impl.cc
index c2f52aa..117fd97 100644
--- a/src/starboard/android/shared/player_components_impl.cc
+++ b/src/starboard/android/shared/player_components_impl.cc
@@ -58,8 +58,11 @@
return audio_decoder_impl.PassAs<AudioDecoder>();
}
} else if (audio_sample_info.codec == kSbMediaAudioCodecOpus) {
- return scoped_ptr<AudioDecoder>(
+ scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
new OpusAudioDecoder(audio_sample_info));
+ if (audio_decoder_impl->is_valid()) {
+ return audio_decoder_impl.PassAs<AudioDecoder>();
+ }
} else {
SB_NOTREACHED();
}
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index ad89435..d8fb10e 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -80,6 +80,7 @@
'bionic/private/bionic_macros.h',
'bionic/private/ErrnoRestorer.h',
'configuration_public.h',
+ 'configuration_constants.cc',
'decode_target_create.cc',
'decode_target_create.h',
'decode_target_get_info.cc',
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index 227b54c..b3d542b 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -524,21 +524,16 @@
if (has_new_texture) {
updateTexImage(decode_target_->data->surface_texture);
+ decode_target_->data->info.planes[0].width = frame_width_;
+ decode_target_->data->info.planes[0].height = frame_height_;
+ decode_target_->data->info.width = frame_width_;
+ decode_target_->data->info.height = frame_height_;
+
float matrix4x4[16];
getTransformMatrix(decode_target_->data->surface_texture, matrix4x4);
SetDecodeTargetContentRegionFromMatrix(
- &decode_target_->data->info.planes[0].content_region, 1, 1,
- matrix4x4);
-
- // Mark the decode target's width and height as 1, so that the
- // |content_region|'s coordinates will be interpreted as normalized
- // coordinates. This is nice because on Android we're never explicitly
- // told the texture width/height, and we are only provided the content
- // region via normalized coordinates.
- decode_target_->data->info.planes[0].width = 1;
- decode_target_->data->info.planes[0].height = 1;
- decode_target_->data->info.width = 1;
- decode_target_->data->info.height = 1;
+ &decode_target_->data->info.planes[0].content_region, frame_width_,
+ frame_height_, matrix4x4);
if (!first_texture_received_) {
first_texture_received_ = true;
diff --git a/src/starboard/benchmark/benchmark.gyp b/src/starboard/benchmark/benchmark.gyp
new file mode 100644
index 0000000..3e626a2
--- /dev/null
+++ b/src/starboard/benchmark/benchmark.gyp
@@ -0,0 +1,47 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'benchmark',
+ 'type': '<(final_executable_type)',
+ 'defines': [
+ # This allows the benchmarks to include internal only header files.
+ 'STARBOARD_IMPLEMENTATION',
+ ],
+ 'sources': [
+ '<(DEPTH)/starboard/benchmark/memory_benchmark.cc',
+ '<(DEPTH)/starboard/benchmark/thread_benchmark.cc',
+ '<(DEPTH)/starboard/common/benchmark_main.cc',
+ ],
+ 'dependencies': [
+ '<@(cobalt_platform_dependencies)',
+ '<(DEPTH)/starboard/starboard.gyp:starboard',
+ '<(DEPTH)/third_party/google_benchmark/google_benchmark.gyp:google_benchmark',
+ ],
+ },
+ {
+ 'target_name': 'benchmark_deploy',
+ 'type': 'none',
+ 'dependencies': [
+ 'benchmark',
+ ],
+ 'variables': {
+ 'executable_name': 'benchmark',
+ },
+ 'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+ },
+ ],
+}
diff --git a/src/starboard/benchmark/memory_benchmark.cc b/src/starboard/benchmark/memory_benchmark.cc
new file mode 100644
index 0000000..24e3cf1
--- /dev/null
+++ b/src/starboard/benchmark/memory_benchmark.cc
@@ -0,0 +1,62 @@
+// 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/memory.h"
+
+#include "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace starboard {
+namespace benchmark {
+namespace {
+
+void BM_MemoryCopy(::benchmark::State& state) {
+ void* memory1 = SbMemoryAllocate(state.range(0));
+ void* memory2 = SbMemoryAllocate(state.range(0));
+
+ for (auto _ : state) {
+ SbMemoryCopy(memory1, memory2, state.range(0));
+ ::benchmark::ClobberMemory();
+ }
+ state.SetBytesProcessed(int64_t(state.iterations()) *
+ int64_t(state.range(0)));
+
+ SbMemoryDeallocate(memory1);
+ SbMemoryDeallocate(memory2);
+}
+
+void BM_MemoryMove(::benchmark::State& state) {
+ void* memory1 = SbMemoryAllocate(state.range(0));
+ void* memory2 = SbMemoryAllocate(state.range(0));
+
+ for (auto _ : state) {
+ SbMemoryMove(memory1, memory2, state.range(0));
+ ::benchmark::ClobberMemory();
+ }
+ state.SetBytesProcessed(int64_t(state.iterations()) *
+ int64_t(state.range(0)));
+
+ SbMemoryDeallocate(memory1);
+ SbMemoryDeallocate(memory2);
+}
+
+BENCHMARK(BM_MemoryCopy)->RangeMultiplier(4)->Range(16, 1024 * 1024);
+BENCHMARK(BM_MemoryCopy)
+ ->Arg(1024 * 1024)
+ ->DenseThreadRange(1, 4)
+ ->UseRealTime();
+BENCHMARK(BM_MemoryMove)->Arg(1024 * 1024);
+
+} // namespace
+} // namespace benchmark
+} // namespace starboard
diff --git a/src/starboard/benchmark/thread_benchmark.cc b/src/starboard/benchmark/thread_benchmark.cc
new file mode 100644
index 0000000..d112ef2
--- /dev/null
+++ b/src/starboard/benchmark/thread_benchmark.cc
@@ -0,0 +1,36 @@
+// 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 "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace starboard {
+namespace benchmark {
+namespace {
+
+void BM_IntegerAccumulation(::benchmark::State& state) {
+ const int kIterations = 1024 * 1024;
+ for (auto _ : state) {
+ int x = 0;
+ for (int i = 0; i < kIterations; ++i) {
+ ::benchmark::DoNotOptimize(x += i);
+ }
+ }
+ state.SetItemsProcessed(kIterations);
+}
+
+BENCHMARK(BM_IntegerAccumulation)->DenseThreadRange(1, 8)->UseRealTime();
+
+} // namespace
+} // namespace benchmark
+} // namespace starboard
diff --git a/src/starboard/build/base_configuration.gypi b/src/starboard/build/base_configuration.gypi
index 38b074f..c0dfb8b 100644
--- a/src/starboard/build/base_configuration.gypi
+++ b/src/starboard/build/base_configuration.gypi
@@ -121,6 +121,9 @@
# Used to indicate that the player is filter based.
'sb_filter_based_player%': 1,
+ # Used to enable benchmarks.
+ 'sb_enable_benchmark%': 0,
+
# This variable dictates whether a given gyp target should be compiled with
# optimization flags for size vs. speed.
'optimize_target_for_speed%': 0,
diff --git a/src/starboard/common/benchmark_main.cc b/src/starboard/common/benchmark_main.cc
new file mode 100644
index 0000000..d100a56
--- /dev/null
+++ b/src/starboard/common/benchmark_main.cc
@@ -0,0 +1,33 @@
+// 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/client_porting/wrap_main/wrap_main.h"
+#include "starboard/event.h"
+#include "starboard/system.h"
+#include "third_party/google_benchmark/include/benchmark/benchmark.h"
+
+namespace {
+int RunAllBenchmarks(int argc, char** argv) {
+ ::benchmark::Initialize(&argc, argv);
+ ::benchmark::RunSpecifiedBenchmarks();
+ return 0;
+}
+} // namespace
+
+// When we are building Evergreen we need to export SbEventHandle so that the
+// ELF loader can find and invoke it.
+#if SB_IS(EVERGREEN)
+SB_EXPORT
+#endif // SB_IS(EVERGREEN)
+STARBOARD_WRAP_SIMPLE_MAIN(RunAllBenchmarks);
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index d650414..607a24f 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -180,6 +180,36 @@
// values are consumed, take a look at //starboard/sabi.
#define SB_SABI_FILE_VERSION SB_EXPERIMENTAL_API_VERSION
+// Updates the API guarantees of SbMutexAcquireTry.
+// SbMutexAcquireTry now has undefined behavior when it is invoked on a mutex
+// that has already been locked by the calling thread. In addition, since
+// SbMutexAcquireTry was used in SbMutexDestroy, SbMutexDestroy now has
+// undefined behavior when invoked on a locked mutex.
+#define SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION SB_EXPERIMENTAL_API_VERSION
+
+// Migrate the Starboard configuration variables from macros to extern consts.
+//
+// The migration allows Cobalt to make platform level decisions at runtime
+// instead of compile time which lets us create a more comprehensive Cobalt
+// binary.
+//
+// This means Cobalt must remove all references to these macros that would not
+// translate well to constants, i.e. in compile time references or initializing
+// arrays. Therefore, we needed to change the functionality of the function
+// `SbDirectoryGetNext` in "starboard/directory.h". Because we do not want to
+// use variable length arrays, we pass in a c-string and length to the function
+// to achieve the same result as before when passing in a `SbDirectoryEntry`.
+//
+// A platform will define the extern constants declared in
+// "starboard/configuration_constants.h". The definitions are done in
+// "starboard/<PLATFORM_PATH>/configuration_constants.cc".
+//
+// The exact mapping between macros and extern variables can be found in
+// "starboard/shared/starboard/configuration_constants_compatibility_defines.h"
+// though the naming scheme is very nearly the same: the old SB_FOO macro will
+// always become the constant kSbFoo.
+#define SB_FEATURE_RUNTIME_CONFIGS_VERSION SB_EXPERIMENTAL_API_VERSION
+
// --- Release Candidate Feature Defines -------------------------------------
// --- Common Detected Features ----------------------------------------------
@@ -265,6 +295,14 @@
// and all configurations.
#include STARBOARD_CONFIGURATION_INCLUDE
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
+// After SB_FEATURE_RUNTIME_CONFIGS_VERSION, we start to use runtime constants
+// instead of macros for certain platform dependent configurations. This file
+// substitutes configuration macros for the corresponding runtime constants so
+// we don't reference these constants when they aren't defined.
+#include "starboard/shared/starboard/configuration_constants_compatibility_defines.h"
+#endif // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
// --- Overridable Helper Macros ---------------------------------------------
// The following macros can be overridden in STARBOARD_CONFIGURATION_INCLUDE
@@ -586,10 +624,30 @@
#error "Your platform must define SB_MAX_THREAD_LOCAL_KEYS."
#endif
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#if defined(SB_MALLOC_ALIGNMENT)
+#error \
+ "SB_MALLOC_ALIGNMENT should not be defined in Starboard " \
+"versions 12 and later. Instead, define kSbMallocAlignment in " \
+"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+#endif
+
+#if defined(SB_MAX_THREAD_NAME_LENGTH)
+#error \
+ "SB_MAX_THREAD_NAME_LENGTH should not be defined in Starboard " \
+"versions 12 and later. Instead, define kSbMaxThreadNameLength in " \
+"starboard/<PLATFORM_PATH>/configuration_constants.cc."
+#endif
+
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
#if !defined(SB_MAX_THREAD_NAME_LENGTH)
#error "Your platform must define SB_MAX_THREAD_NAME_LENGTH."
#endif
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
#if (SB_API_VERSION < 12 && !defined(SB_HAS_MICROPHONE))
#error \
"Your platform must define SB_HAS_MICROPHONE in API versions 11 or earlier."
diff --git a/src/starboard/configuration_constants.h b/src/starboard/configuration_constants.h
new file mode 100644
index 0000000..1d772da
--- /dev/null
+++ b/src/starboard/configuration_constants.h
@@ -0,0 +1,36 @@
+// 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.
+
+// Module Overview: Starboard Configuration Variables module
+//
+// Declares all configuration variables we will need to use at runtime.
+// These variables describe the current platform in detail to allow cobalt to
+// make runtime decisions based on per platform configurations.
+
+#ifndef STARBOARD_CONFIGURATION_CONSTANTS_H_
+#define STARBOARD_CONFIGURATION_CONSTANTS_H_
+
+#include "starboard/types.h"
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+// Determines the alignment that allocations should have on this platform.
+extern const size_t kSbMallocAlignment;
+
+// The maximum length of the name for a thread, including the NULL-terminator.
+extern const int32_t kSbMaxThreadNameLength;
+
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#endif // STARBOARD_CONFIGURATION_CONSTANTS_H_
diff --git a/src/starboard/directory.h b/src/starboard/directory.h
index c9032b2..97ef448 100644
--- a/src/starboard/directory.h
+++ b/src/starboard/directory.h
@@ -34,11 +34,13 @@
// A handle to an open directory stream.
typedef struct SbDirectoryPrivate* SbDirectory;
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Represents a directory entry.
typedef struct SbDirectoryEntry {
// The name of this directory entry.
char name[SB_FILE_MAX_NAME];
} SbDirectoryEntry;
+#endif // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Well-defined value for an invalid directory stream handle.
#define kSbDirectoryInvalid ((SbDirectory)NULL)
@@ -64,6 +66,24 @@
// |directory|: The directory stream handle to close.
SB_EXPORT bool SbDirectoryClose(SbDirectory directory);
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+// Populates |out_entry| with the next entry in the specified directory stream,
+// and moves the stream forward by one entry.
+//
+// This function returns |true| if there was a next directory, and |false|
+// at the end of the directory stream or if |out_entry_size| is smaller than
+// SB_FILE_MAX_NAME.
+//
+// |directory|: The directory stream from which to retrieve the next directory.
+// |out_entry|: The null terminated string to be populated with the next
+// directory entry. The space allocated for this string should be
+// equal to |out_entry_size|.
+// |out_entry_size|: The size of the space allocated for |out_entry|. This
+// should be at least equal to SB_FILE_MAX_NAME.
+SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory,
+ char* out_entry,
+ size_t out_entry_size);
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Populates |out_entry| with the next entry in the specified directory stream,
// and moves the stream forward by one entry.
//
@@ -74,6 +94,7 @@
// |out_entry|: The variable to be populated with the next directory entry.
SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory,
SbDirectoryEntry* out_entry);
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Indicates whether SbDirectoryOpen is allowed for the given |path|.
//
diff --git a/src/starboard/linux/shared/configuration_constants.cc b/src/starboard/linux/shared/configuration_constants.cc
new file mode 100644
index 0000000..973063a
--- /dev/null
+++ b/src/starboard/linux/shared/configuration_constants.cc
@@ -0,0 +1,27 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
+
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h
index b3aeff7..485753c 100644
--- a/src/starboard/linux/shared/configuration_public.h
+++ b/src/starboard/linux/shared/configuration_public.h
@@ -287,8 +287,10 @@
// specify that.
#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Determines the alignment that allocations should have on this platform.
#define SB_MALLOC_ALIGNMENT ((size_t)16U)
+#endif // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// Determines the threshhold of allocation size that should be done with mmap
// (if available), rather than allocated within the core heap.
@@ -319,8 +321,10 @@
// The maximum number of thread local storage keys supported by this platform.
#define SB_MAX_THREAD_LOCAL_KEYS 512
+#if SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// The maximum length of the name for a thread, including the NULL-terminator.
#define SB_MAX_THREAD_NAME_LENGTH 16
+#endif // SB_API_VERSION < SB_FEATURE_RUNTIME_CONFIGS_VERSION
// --- Tuneable Parameters ---------------------------------------------------
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi
index b224935..b565543 100644
--- a/src/starboard/linux/shared/gyp_configuration.gypi
+++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -25,6 +25,8 @@
'target_os': 'linux',
'yasm_exists': 1,
'sb_widevine_platform' : 'linux',
+ 'sb_disable_opus_sse': 1,
+ 'sb_enable_benchmark': 1,
'platform_libraries': [
'-lasound',
@@ -45,14 +47,14 @@
'use_dlmalloc_allocator%': 0,
},
'conditions': [
- ['sb_evergreen != 1', {
- # TODO: allow starboard_platform to use system libc/libc++ in the
- # future. For now, if this flags is enabled, a warning emerge saying
- # it's unused anyway.
- 'linker_flags': [
- '-static-libstdc++',
- ],
- }],
+ ['sb_evergreen != 1', {
+ # TODO: allow starboard_platform to use system libc/libc++ in the
+ # future. For now, if this flags is enabled, a warning emerge saying
+ # it's unused anyway.
+ 'linker_flags': [
+ '-static-libstdc++',
+ ],
+ }],
],
},
diff --git a/src/starboard/linux/shared/launcher.py b/src/starboard/linux/shared/launcher.py
index 662e68d..e5ba8f1 100644
--- a/src/starboard/linux/shared/launcher.py
+++ b/src/starboard/linux/shared/launcher.py
@@ -25,6 +25,7 @@
import _env # pylint: disable=unused-import
from starboard.tools import abstract_launcher
+from starboard.tools import send_link
STATUS_CHANGE_TIMEOUT = 15
@@ -124,6 +125,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/linux/shared/player_components_impl.cc b/src/starboard/linux/shared/player_components_impl.cc
index 53652ef..20baaaf 100644
--- a/src/starboard/linux/shared/player_components_impl.cc
+++ b/src/starboard/linux/shared/player_components_impl.cc
@@ -25,6 +25,7 @@
#include "starboard/shared/libaom/aom_video_decoder.h"
#include "starboard/shared/libde265/de265_video_decoder.h"
#include "starboard/shared/libvpx/vpx_video_decoder.h"
+#include "starboard/shared/opus/opus_audio_decoder.h"
#include "starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h"
#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
#include "starboard/shared/starboard/player/filter/audio_renderer_sink.h"
@@ -52,15 +53,25 @@
SB_DCHECK(audio_decoder);
SB_DCHECK(audio_renderer_sink);
+ typedef ::starboard::shared::ffmpeg::AudioDecoder FfmpegAudioDecoder;
+ typedef ::starboard::shared::opus::OpusAudioDecoder OpusAudioDecoder;
+
#if SB_API_VERSION >= 11
auto decoder_creator = [](const SbMediaAudioSampleInfo& audio_sample_info,
SbDrmSystem drm_system) {
- typedef ::starboard::shared::ffmpeg::AudioDecoder AudioDecoderImpl;
-
- scoped_ptr<AudioDecoderImpl> audio_decoder_impl(
- AudioDecoderImpl::Create(audio_sample_info.codec, audio_sample_info));
- if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
- return audio_decoder_impl.PassAs<AudioDecoder>();
+ if (audio_sample_info.codec == kSbMediaAudioCodecOpus) {
+ scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
+ new OpusAudioDecoder(audio_sample_info));
+ if (audio_decoder_impl->is_valid()) {
+ return audio_decoder_impl.PassAs<AudioDecoder>();
+ }
+ } else {
+ scoped_ptr<FfmpegAudioDecoder> audio_decoder_impl(
+ FfmpegAudioDecoder::Create(audio_sample_info.codec,
+ audio_sample_info));
+ if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+ return audio_decoder_impl.PassAs<AudioDecoder>();
+ }
}
return scoped_ptr<AudioDecoder>();
};
@@ -69,14 +80,23 @@
new AdaptiveAudioDecoder(audio_parameters.audio_sample_info,
audio_parameters.drm_system, decoder_creator));
#else // SB_API_VERSION >= 11
- typedef ::starboard::shared::ffmpeg::AudioDecoder AudioDecoderImpl;
-
- scoped_ptr<AudioDecoderImpl> audio_decoder_impl(AudioDecoderImpl::Create(
- audio_parameters.audio_codec, audio_parameters.audio_sample_info));
- if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
- audio_decoder->reset(audio_decoder_impl.release());
+ if (audio_parameters.audio_codec == kSbMediaAudioCodecOpus) {
+ scoped_ptr<OpusAudioDecoder> audio_decoder_impl(
+ new OpusAudioDecoder(audio_parameters.audio_sample_info));
+ if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+ audio_decoder->reset(audio_decoder_impl.release());
+ } else {
+ audio_decoder->reset();
+ }
} else {
- audio_decoder->reset();
+ scoped_ptr<FfmpegAudioDecoder> audio_decoder_impl(
+ FfmpegAudioDecoder::Create(audio_parameters.audio_codec,
+ audio_parameters.audio_sample_info));
+ if (audio_decoder_impl && audio_decoder_impl->is_valid()) {
+ audio_decoder->reset(audio_decoder_impl.release());
+ } else {
+ audio_decoder->reset();
+ }
}
#endif // SB_API_VERSION >= 11
audio_renderer_sink->reset(new AudioRendererSinkImpl);
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index d335946..b9286b3 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -31,6 +31,7 @@
'<(DEPTH)/starboard/linux/shared/command_line_defaults.cc',
'<(DEPTH)/starboard/linux/shared/command_line_defaults.h',
'<(DEPTH)/starboard/linux/shared/configuration_public.h',
+ '<(DEPTH)/starboard/linux/shared/configuration_constants.cc',
'<(DEPTH)/starboard/linux/shared/decode_target_get_info.cc',
'<(DEPTH)/starboard/linux/shared/decode_target_internal.cc',
'<(DEPTH)/starboard/linux/shared/decode_target_internal.h',
@@ -125,6 +126,8 @@
'<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
'<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
'<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+ '<(DEPTH)/starboard/shared/opus/opus_audio_decoder.cc',
+ '<(DEPTH)/starboard/shared/opus/opus_audio_decoder.h',
'<(DEPTH)/starboard/shared/posix/directory_create.cc',
'<(DEPTH)/starboard/shared/posix/file_atomic_replace.cc',
'<(DEPTH)/starboard/shared/posix/file_can_open.cc',
@@ -382,6 +385,7 @@
'<(DEPTH)/third_party/de265_includes/de265_includes.gyp:de265',
'<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc',
'<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+ '<(DEPTH)/third_party/opus/opus.gyp:opus',
'<(DEPTH)/third_party/pulseaudio_includes/pulseaudio_includes.gyp:pulseaudio',
],
'conditions': [
diff --git a/src/starboard/mutex.h b/src/starboard/mutex.h
index 0f6226e..afd8927 100644
--- a/src/starboard/mutex.h
+++ b/src/starboard/mutex.h
@@ -54,10 +54,17 @@
// |out_mutex|: The handle to the newly created mutex.
SB_EXPORT bool SbMutexCreate(SbMutex* out_mutex);
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Destroys a mutex. The return value indicates whether the destruction was
+// successful. Destroying a locked mutex results in undefined behavior.
+//
+// |mutex|: The mutex to be invalidated.
+#else // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
// Destroys a mutex. The return value indicates whether the destruction was
// successful.
//
// |mutex|: The mutex to be invalidated.
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
SB_EXPORT bool SbMutexDestroy(SbMutex* mutex);
// Acquires |mutex|, blocking indefinitely. The return value identifies
@@ -67,11 +74,19 @@
// |mutex|: The mutex to be acquired.
SB_EXPORT SbMutexResult SbMutexAcquire(SbMutex* mutex);
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Acquires |mutex|, without blocking. The return value identifies
+// the acquisition result. SbMutexes are not reentrant, so a recursive
+// acquisition has undefined behavior.
+//
+// |mutex|: The mutex to be acquired.
+#else // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
// Acquires |mutex|, without blocking. The return value identifies
// the acquisition result. SbMutexes are not reentrant, so a recursive
// acquisition always fails.
//
// |mutex|: The mutex to be acquired.
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
SB_EXPORT SbMutexResult SbMutexAcquireTry(SbMutex* mutex);
// Releases |mutex| held by the current thread. The return value indicates
diff --git a/src/starboard/nplb/audio_sink_helpers.cc b/src/starboard/nplb/audio_sink_helpers.cc
index 082ce62..b0a0d9c 100644
--- a/src/starboard/nplb/audio_sink_helpers.cc
+++ b/src/starboard/nplb/audio_sink_helpers.cc
@@ -14,6 +14,8 @@
#include "starboard/nplb/audio_sink_helpers.h"
+#include <algorithm>
+
#include "starboard/common/log.h"
namespace starboard {
@@ -107,13 +109,12 @@
void AudioSinkTestEnvironment::AppendFrame(int frames_to_append) {
ScopedLock lock(mutex_);
- frames_appended_ += frames_to_append;
+ AppendFrame_Locked(frames_to_append);
}
-int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceAmount() const {
+int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceInFrames() const {
ScopedLock lock(mutex_);
- int frames_in_buffer = frames_appended_ - frames_consumed_;
- return frame_buffers_.frames_per_channel() - frames_in_buffer;
+ return GetFrameBufferFreeSpaceInFrames_Locked();
}
bool AudioSinkTestEnvironment::WaitUntilUpdateStatusCalled() {
@@ -147,20 +148,48 @@
}
bool AudioSinkTestEnvironment::WaitUntilAllFramesAreConsumed() {
+ const int kMaximumFramesPerAppend = 1024;
+
ScopedLock lock(mutex_);
is_eos_reached_ = true;
+ int frames_appended_before_eos = frames_appended_;
SbTimeMonotonic start = SbTimeGetMonotonicNow();
- while (frames_appended_ != frames_consumed_) {
+ int silence_frames_appended = 0;
+
+ while (frames_consumed_ < frames_appended_before_eos) {
SbTime time_elapsed = SbTimeGetMonotonicNow() - start;
if (time_elapsed >= kTimeToTry) {
return false;
}
SbTime time_to_wait = kTimeToTry - time_elapsed;
+
+ // Append silence as some audio sink implementations won't be able to finish
+ // playback to the last frames filled.
+ int silence_frames_to_append =
+ std::min({GetFrameBufferFreeSpaceInFrames_Locked(),
+ frame_buffers_.frames_per_channel() - silence_frames_appended,
+ kMaximumFramesPerAppend});
+ AppendFrame_Locked(silence_frames_to_append);
+ silence_frames_appended += silence_frames_to_append;
+
condition_variable_.WaitTimed(time_to_wait);
}
return true;
}
+void AudioSinkTestEnvironment::AppendFrame_Locked(int frames_to_append) {
+ mutex_.DCheckAcquired();
+
+ frames_appended_ += frames_to_append;
+}
+
+int AudioSinkTestEnvironment::GetFrameBufferFreeSpaceInFrames_Locked() const {
+ mutex_.DCheckAcquired();
+
+ int frames_in_buffer = frames_appended_ - frames_consumed_;
+ return frame_buffers_.frames_per_channel() - frames_in_buffer;
+}
+
void AudioSinkTestEnvironment::OnUpdateSourceStatus(int* frames_in_buffer,
int* offset_in_frames,
bool* is_playing,
diff --git a/src/starboard/nplb/audio_sink_helpers.h b/src/starboard/nplb/audio_sink_helpers.h
index 215ce94..72921e0 100644
--- a/src/starboard/nplb/audio_sink_helpers.h
+++ b/src/starboard/nplb/audio_sink_helpers.h
@@ -81,7 +81,7 @@
}
void SetIsPlaying(bool is_playing);
void AppendFrame(int frames_to_append);
- int GetFrameBufferFreeSpaceAmount() const;
+ int GetFrameBufferFreeSpaceInFrames() const;
// The following functions return true when the expected condition are met.
// Return false on timeout.
@@ -90,6 +90,8 @@
bool WaitUntilAllFramesAreConsumed();
private:
+ void AppendFrame_Locked(int frames_to_append);
+ int GetFrameBufferFreeSpaceInFrames_Locked() const;
void OnUpdateSourceStatus(int* frames_in_buffer,
int* offset_in_frames,
bool* is_playing,
diff --git a/src/starboard/nplb/audio_sink_test.cc b/src/starboard/nplb/audio_sink_test.cc
index caa7870..e6c059b 100644
--- a/src/starboard/nplb/audio_sink_test.cc
+++ b/src/starboard/nplb/audio_sink_test.cc
@@ -66,8 +66,8 @@
environment.AppendFrame(frames_to_append);
EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
- ASSERT_GT(environment.GetFrameBufferFreeSpaceAmount(), 0);
- environment.AppendFrame(environment.GetFrameBufferFreeSpaceAmount());
+ ASSERT_GT(environment.GetFrameBufferFreeSpaceInFrames(), 0);
+ environment.AppendFrame(environment.GetFrameBufferFreeSpaceInFrames());
EXPECT_TRUE(environment.WaitUntilAllFramesAreConsumed());
}
@@ -82,10 +82,10 @@
int frames_to_append = frame_buffers.frames_per_channel();
environment.AppendFrame(frames_to_append);
- int free_space = environment.GetFrameBufferFreeSpaceAmount();
+ int free_space = environment.GetFrameBufferFreeSpaceInFrames();
EXPECT_TRUE(environment.WaitUntilUpdateStatusCalled());
EXPECT_TRUE(environment.WaitUntilUpdateStatusCalled());
- EXPECT_EQ(free_space, environment.GetFrameBufferFreeSpaceAmount());
+ EXPECT_EQ(free_space, environment.GetFrameBufferFreeSpaceInFrames());
environment.SetIsPlaying(true);
EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
}
@@ -101,8 +101,8 @@
EXPECT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
SbThreadSleep(250 * kSbTimeMillisecond);
- ASSERT_GT(environment.GetFrameBufferFreeSpaceAmount(), 0);
- environment.AppendFrame(environment.GetFrameBufferFreeSpaceAmount());
+ ASSERT_GT(environment.GetFrameBufferFreeSpaceInFrames(), 0);
+ environment.AppendFrame(environment.GetFrameBufferFreeSpaceInFrames());
EXPECT_TRUE(environment.WaitUntilAllFramesAreConsumed());
}
@@ -117,7 +117,7 @@
int frames_to_append = sample_rate / 4;
while (frames_to_append > 0) {
- int free_space = environment.GetFrameBufferFreeSpaceAmount();
+ int free_space = environment.GetFrameBufferFreeSpaceInFrames();
environment.AppendFrame(std::min(free_space, frames_to_append));
frames_to_append -= std::min(free_space, frames_to_append);
ASSERT_TRUE(environment.WaitUntilSomeFramesAreConsumed());
diff --git a/src/starboard/nplb/condition_variable_wait_test.cc b/src/starboard/nplb/condition_variable_wait_test.cc
index 3e42109..6586d6c 100644
--- a/src/starboard/nplb/condition_variable_wait_test.cc
+++ b/src/starboard/nplb/condition_variable_wait_test.cc
@@ -52,7 +52,7 @@
const int kMany = SB_MAX_THREADS > 64 ? 64 : SB_MAX_THREADS;
WaiterContext context;
- SbThread threads[kMany];
+ std::vector<SbThread> threads(kMany);
for (int i = 0; i < kMany; ++i) {
threads[i] = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
true, NULL, WaiterEntryPoint, &context);
diff --git a/src/starboard/nplb/directory_get_next_test.cc b/src/starboard/nplb/directory_get_next_test.cc
index 24c05ba..80a61ea 100644
--- a/src/starboard/nplb/directory_get_next_test.cc
+++ b/src/starboard/nplb/directory_get_next_test.cc
@@ -50,17 +50,26 @@
StringSet names_to_find(names);
int count = 0;
while (true) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+ if (!SbDirectoryGetNext(directory, entry.data(), entry.size())) {
+ break;
+ }
+ const char* entry_name = entry.data();
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry = {0};
if (!SbDirectoryGetNext(directory, &entry)) {
break;
}
+ const char* entry_name = entry.name;
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
// SbDirectoryEntry just contains the last component of the absolute path to
// the file, but ScopedRandomFile::filename() returns the full path.
std::string filename;
filename += directory_name;
filename += SB_FILE_SEP_CHAR;
- filename += entry.name;
+ filename += entry_name;
StringSet::iterator iterator = names_to_find.find(filename);
if (iterator != names_to_find.end()) {
@@ -68,7 +77,7 @@
} else {
// If it isn't in |names_to_find|, make sure it's some external entry and
// not one of ours. Otherwise, an entry must have shown up twice.
- EXPECT_TRUE(names.find(entry.name) == names.end());
+ EXPECT_TRUE(names.find(entry_name) == names.end());
}
}
@@ -79,8 +88,14 @@
}
TEST(SbDirectoryGetNextTest, FailureInvalidSbDirectory) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+ EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, entry.data(),
+ entry.size()));
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry = {0};
EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, &entry));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
}
TEST(SbDirectoryGetNextTest, FailureNullEntry) {
@@ -95,12 +110,20 @@
SbDirectory directory = SbDirectoryOpen(path.c_str(), &error);
EXPECT_TRUE(SbDirectoryIsValid(directory));
EXPECT_EQ(kSbFileOk, error);
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ EXPECT_FALSE(SbDirectoryGetNext(directory, NULL, SB_FILE_MAX_NAME));
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
EXPECT_FALSE(SbDirectoryGetNext(directory, NULL));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
EXPECT_TRUE(SbDirectoryClose(directory));
}
TEST(SbDirectoryGetNextTest, FailureInvalidAndNull) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, NULL, SB_FILE_MAX_NAME));
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
EXPECT_FALSE(SbDirectoryGetNext(kSbDirectoryInvalid, NULL));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
}
TEST(SbDirectoryGetNextTest, FailureOnEmptyDirectory) {
@@ -112,11 +135,42 @@
ASSERT_TRUE(SbDirectoryIsValid(directory));
ASSERT_EQ(kSbFileOk, error);
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME, 0);
+ EXPECT_FALSE(SbDirectoryGetNext(directory, entry.data(), entry.size()));
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry = {0};
EXPECT_FALSE(SbDirectoryGetNext(directory, &entry));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
ASSERT_TRUE(SbDirectoryClose(directory));
}
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+TEST(SbDirectoryGetNextTest, FailureOnInsufficientSize) {
+ ScopedRandomFile file;
+ std::string directory_name = file.filename();
+ directory_name.resize(directory_name.find_last_of(SB_FILE_SEP_CHAR));
+ EXPECT_TRUE(SbFileExists(directory_name.c_str()))
+ << "Directory_name is " << directory_name;
+
+ SbFileError error = kSbFileErrorMax;
+ SbDirectory directory = SbDirectoryOpen(directory_name.c_str(), &error);
+ EXPECT_TRUE(SbDirectoryIsValid(directory));
+ EXPECT_EQ(kSbFileOk, error);
+
+ std::vector<char> entry(SB_FILE_MAX_NAME);
+ for (int i = 0; i < SB_FILE_MAX_NAME; i++)
+ entry[i] = i;
+ std::vector<char> entry_copy = entry;
+ EXPECT_EQ(SbDirectoryGetNext(directory, entry.data(), 0), false);
+ EXPECT_EQ(entry.size(), SB_FILE_MAX_NAME);
+ for (int i = 0; i < SB_FILE_MAX_NAME; i++)
+ EXPECT_EQ(entry[i], entry_copy[i]);
+
+ EXPECT_TRUE(SbDirectoryClose(directory));
+}
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
} // namespace
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/directory_open_test.cc b/src/starboard/nplb/directory_open_test.cc
index 5b43367..f9d3155 100644
--- a/src/starboard/nplb/directory_open_test.cc
+++ b/src/starboard/nplb/directory_open_test.cc
@@ -55,16 +55,16 @@
EXPECT_FILE_EXISTS(path);
const int kMany = SB_FILE_MAX_OPEN;
- SbDirectory directories[kMany] = {0};
+ std::vector<SbDirectory> directories(kMany, 0);
- for (int i = 0; i < SB_ARRAY_SIZE_INT(directories); ++i) {
+ for (int i = 0; i < directories.size(); ++i) {
SbFileError error = kSbFileErrorMax;
directories[i] = SbDirectoryOpen(path.c_str(), &error);
EXPECT_TRUE(SbDirectoryIsValid(directories[i]));
EXPECT_EQ(kSbFileOk, error);
}
- for (int i = 0; i < SB_ARRAY_SIZE_INT(directories); ++i) {
+ for (int i = 0; i < directories.size(); ++i) {
EXPECT_TRUE(SbDirectoryClose(directories[i]));
}
}
diff --git a/src/starboard/nplb/flat_map_test.cc b/src/starboard/nplb/flat_map_test.cc
index dde8625..4564290 100644
--- a/src/starboard/nplb/flat_map_test.cc
+++ b/src/starboard/nplb/flat_map_test.cc
@@ -90,11 +90,14 @@
}
SbTimeMonotonic GetThreadTimeMonotonicNow() {
-#if SB_HAS(TIME_THREAD_NOW)
- return SbTimeGetMonotonicThreadNow();
-#else
- return SbTimeGetMonotonicNow();
+#if SB_API_VERSION >= SB_TIME_THREAD_NOW_REQUIRED_VERSION || \
+ SB_HAS(TIME_THREAD_NOW)
+#if SB_API_VERSION >= SB_TIME_THREAD_NOW_REQUIRED_VERSION
+ if (SbTimeIsTimeThreadNowSupported())
#endif
+ return SbTimeGetMonotonicThreadNow();
+#endif
+ return SbTimeGetMonotonicNow();
}
// Generic stringification of the input map type. This allows good error
diff --git a/src/starboard/nplb/mutex_acquire_try_test.cc b/src/starboard/nplb/mutex_acquire_try_test.cc
index d424f1c..806f5f8 100644
--- a/src/starboard/nplb/mutex_acquire_try_test.cc
+++ b/src/starboard/nplb/mutex_acquire_try_test.cc
@@ -16,10 +16,30 @@
#include "starboard/configuration.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+#include "starboard/nplb/thread_helpers.h"
+#include "starboard/thread.h"
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+
namespace starboard {
namespace nplb {
namespace {
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+struct TestContext {
+ explicit TestContext(SbMutex* mutex) : was_locked_(false), mutex_(mutex) {}
+ bool was_locked_;
+ SbMutex* mutex_;
+};
+
+void* EntryPoint(void* parameter) {
+ TestContext* context = static_cast<TestContext*>(parameter);
+ context->was_locked_ =
+ (SbMutexAcquireTry(context->mutex_) == kSbMutexAcquired);
+ return NULL;
+}
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+
TEST(SbMutexAcquireTryTest, SunnyDayUncontended) {
SbMutex mutex;
EXPECT_TRUE(SbMutexCreate(&mutex));
@@ -46,9 +66,20 @@
EXPECT_EQ(result, kSbMutexAcquired);
EXPECT_TRUE(SbMutexIsSuccess(result));
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+ TestContext context(&mutex);
+ SbThread thread =
+ SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true,
+ nplb::kThreadName, &EntryPoint, &context);
+
+ EXPECT_TRUE(SbThreadIsValid(thread));
+ EXPECT_TRUE(SbThreadJoin(thread, NULL));
+ EXPECT_FALSE(context.was_locked_);
+#else // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
result = SbMutexAcquireTry(&mutex);
EXPECT_EQ(result, kSbMutexBusy);
EXPECT_FALSE(SbMutexIsSuccess(result));
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
EXPECT_TRUE(SbMutexRelease(&mutex));
EXPECT_TRUE(SbMutexDestroy(&mutex));
diff --git a/src/starboard/nplb/mutex_destroy_test.cc b/src/starboard/nplb/mutex_destroy_test.cc
index 92395b6..02e072d 100644
--- a/src/starboard/nplb/mutex_destroy_test.cc
+++ b/src/starboard/nplb/mutex_destroy_test.cc
@@ -27,6 +27,10 @@
EXPECT_TRUE(SbMutexDestroy(&mutex));
}
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+// Destroying a mutex that has already been destroyed is undefined behavior
+// and cannot be tested.
+#else // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
TEST(SbMutexDestroyTest, RainyDayDestroyHeld) {
SbMutex mutex;
EXPECT_TRUE(SbMutexCreate(&mutex));
@@ -39,6 +43,7 @@
EXPECT_TRUE(SbMutexRelease(&mutex));
EXPECT_TRUE(SbMutexDestroy(&mutex));
}
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
TEST(SbMutexDestroyTest, RainyDayNull) {
EXPECT_FALSE(SbMutexDestroy(NULL));
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index cd280b6..6739ebe 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -165,6 +165,7 @@
'memory_map_test.cc',
'memory_move_test.cc',
'memory_reallocate_test.cc',
+ 'memory_reporter_test.cc',
'memory_set_test.cc',
'microphone_close_test.cc',
'microphone_create_test.cc',
@@ -314,8 +315,6 @@
'conditions': [
['sb_evergreen != 1', {
'sources': [
- # Segfaults for Cobalt Evergreen.
- 'memory_reporter_test.cc',
# Segfaults or causes unresolved symbols for Cobalt Evergreen.
'media_set_audio_write_duration_test.cc',
],
diff --git a/src/starboard/nplb/once_test.cc b/src/starboard/nplb/once_test.cc
index 0885860..b2743b8 100644
--- a/src/starboard/nplb/once_test.cc
+++ b/src/starboard/nplb/once_test.cc
@@ -98,7 +98,7 @@
// routine got called exactly one time.
TEST(SbOnceTest, SunnyDayMultipleThreadsInit) {
const int kMany = SB_MAX_THREADS;
- SbThread threads[kMany];
+ std::vector<SbThread> threads(kMany);
const int kIterationCount = 10;
for (int i = 0; i < kIterationCount; ++i) {
diff --git a/src/starboard/nplb/sabi/sabi.gypi b/src/starboard/nplb/sabi/sabi.gypi
index 3d76231..19c69ed 100644
--- a/src/starboard/nplb/sabi/sabi.gypi
+++ b/src/starboard/nplb/sabi/sabi.gypi
@@ -17,6 +17,7 @@
'sabi_sources': [
'<(DEPTH)/starboard/nplb/sabi/alignment_test.cc',
'<(DEPTH)/starboard/nplb/sabi/endianness_test.cc',
+ '<(DEPTH)/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc',
'<(DEPTH)/starboard/nplb/sabi/signedness_of_char_test.cc',
'<(DEPTH)/starboard/nplb/sabi/size_test.cc',
'<(DEPTH)/starboard/nplb/sabi/struct_alignment_test.cc',
diff --git a/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc b/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc
new file mode 100644
index 0000000..bf66cd8
--- /dev/null
+++ b/src/starboard/nplb/sabi/signedness_and_size_of_enum_test.cc
@@ -0,0 +1,37 @@
+// 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/configuration.h"
+
+#if SB_API_VERSION >= SB_SABI_FILE_VERSION
+
+namespace starboard {
+namespace sabi {
+namespace {
+
+typedef enum GenericEnumType {
+ kOnlyTag,
+} GenericEnumType;
+
+SB_COMPILE_ASSERT((static_cast<GenericEnumType>(-1) < 0) == SB_HAS_SIGNED_ENUM,
+ SB_HAS_SIGNED_ENUM_is_inconsistent_with_sign_of_enum);
+
+SB_COMPILE_ASSERT(sizeof(GenericEnumType) == SB_SIZE_OF_ENUM,
+ SB_SIZE_OF_ENUM_is_inconsistent_with_sizeof_enum);
+
+} // namespace
+} // namespace sabi
+} // namespace starboard
+
+#endif // SB_API_VERSION >= SB_SABI_FILE_VERSION
diff --git a/src/starboard/nplb/socket_waiter_add_test.cc b/src/starboard/nplb/socket_waiter_add_test.cc
index b56395c..a942c11 100644
--- a/src/starboard/nplb/socket_waiter_add_test.cc
+++ b/src/starboard/nplb/socket_waiter_add_test.cc
@@ -67,7 +67,7 @@
EXPECT_TRUE(SbSocketWaiterIsValid(waiter));
const int kMany = SB_FILE_MAX_OPEN;
- SbSocket sockets[kMany] = {0};
+ std::vector<SbSocket> sockets(kMany, 0);
for (int i = 0; i < kMany; ++i) {
sockets[i] = SbSocketCreate(GetAddressType(), kSbSocketProtocolTcp);
ASSERT_TRUE(SbSocketIsValid(sockets[i]));
diff --git a/src/starboard/nplb/speech_recognizer_cancel_test.cc b/src/starboard/nplb/speech_recognizer_cancel_test.cc
index 0743dc9..630e45a 100644
--- a/src/starboard/nplb/speech_recognizer_cancel_test.cc
+++ b/src/starboard/nplb/speech_recognizer_cancel_test.cc
@@ -20,9 +20,12 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
TEST_F(SpeechRecognizerTest, CancelTestSunnyDay) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, true, 1};
@@ -38,6 +41,8 @@
}
TEST_F(SpeechRecognizerTest, CancelIsCalledMultipleTimes) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, false, 1};
@@ -55,6 +60,8 @@
}
TEST_F(SpeechRecognizerTest, CancelTestStartIsNotCalled) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechRecognizerCancel(recognizer);
@@ -62,10 +69,13 @@
}
TEST_F(SpeechRecognizerTest, CancelWithInvalidSpeechRecognizer) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizerCancel(kSbSpeechRecognizerInvalid);
}
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_create_test.cc b/src/starboard/nplb/speech_recognizer_create_test.cc
index adfbc20..52da0c7 100644
--- a/src/starboard/nplb/speech_recognizer_create_test.cc
+++ b/src/starboard/nplb/speech_recognizer_create_test.cc
@@ -19,15 +19,19 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
TEST_F(SpeechRecognizerTest, CreateTestSunnyDay) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechRecognizerDestroy(recognizer);
}
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_destroy_test.cc b/src/starboard/nplb/speech_recognizer_destroy_test.cc
index 857fc96..f2f5fe1 100644
--- a/src/starboard/nplb/speech_recognizer_destroy_test.cc
+++ b/src/starboard/nplb/speech_recognizer_destroy_test.cc
@@ -19,13 +19,18 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
TEST_F(SpeechRecognizerTest, DestroyInvalidSpeechRecognizer) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizerDestroy(kSbSpeechRecognizerInvalid);
}
TEST_F(SpeechRecognizerTest, DestroyRecognizerWithoutStopping) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, true, 1};
@@ -33,7 +38,8 @@
SbSpeechRecognizerDestroy(recognizer);
}
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_helper.h b/src/starboard/nplb/speech_recognizer_helper.h
index cba631c..5ab23b0 100644
--- a/src/starboard/nplb/speech_recognizer_helper.h
+++ b/src/starboard/nplb/speech_recognizer_helper.h
@@ -25,7 +25,8 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
class SpeechRecognizerTest : public ::testing::Test {
public:
@@ -48,6 +49,26 @@
}
protected:
+ bool isTestFixtureSupported;
+ virtual void SetUp() {
+ // We include all API tests at compile time after Starboard version 12, so
+ // we must do a runtime check to determine whether or not that API (and
+ // thus the test fixture) is supported.
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION
+ isTestFixtureSupported = SbSpeechRecognizerIsSupported();
+#else
+ isTestFixtureSupported = true;
+#endif
+ }
+
+ // TODO: Use GTEST_SKIP in |SetUp| when we have a newer version of gtest.
+ bool SkipLocale() {
+ if (!isTestFixtureSupported) {
+ SB_LOG(INFO) << "Speech recognizer not supported. Test skipped.";
+ }
+ return !isTestFixtureSupported;
+ }
+
// Per test teardown.
virtual void TearDown() {
// Wait for the speech recognizer server to tear down in order to start
@@ -62,7 +83,8 @@
SbSpeechRecognizerHandler handler_;
};
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_start_test.cc b/src/starboard/nplb/speech_recognizer_start_test.cc
index 15922f6..85acebc 100644
--- a/src/starboard/nplb/speech_recognizer_start_test.cc
+++ b/src/starboard/nplb/speech_recognizer_start_test.cc
@@ -19,9 +19,12 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
TEST_F(SpeechRecognizerTest, StartTestSunnyDay) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {false, false, 1};
@@ -36,6 +39,8 @@
}
TEST_F(SpeechRecognizerTest, StartRecognizerWithContinuousRecognition) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, false, 1};
@@ -50,6 +55,8 @@
}
TEST_F(SpeechRecognizerTest, StartRecognizerWithInterimResults) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {false, true, 1};
@@ -64,6 +71,8 @@
}
TEST_F(SpeechRecognizerTest, StartRecognizerWith10MaxAlternatives) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, true, 10};
@@ -78,6 +87,8 @@
}
TEST_F(SpeechRecognizerTest, StartIsCalledMultipleTimes) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, true, 1};
@@ -95,13 +106,16 @@
}
TEST_F(SpeechRecognizerTest, StartWithInvalidSpeechRecognizer) {
+ if (SkipLocale())
+ return;
SbSpeechConfiguration configuration = {true, true, 1};
bool success =
SbSpeechRecognizerStart(kSbSpeechRecognizerInvalid, &configuration);
EXPECT_FALSE(success);
}
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_recognizer_stop_test.cc b/src/starboard/nplb/speech_recognizer_stop_test.cc
index bbad125..d8808be 100644
--- a/src/starboard/nplb/speech_recognizer_stop_test.cc
+++ b/src/starboard/nplb/speech_recognizer_stop_test.cc
@@ -19,9 +19,12 @@
namespace starboard {
namespace nplb {
-#if SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#if SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
TEST_F(SpeechRecognizerTest, StopIsCalledMultipleTimes) {
+ if (SkipLocale())
+ return;
SbSpeechRecognizer recognizer = SbSpeechRecognizerCreate(handler());
EXPECT_TRUE(SbSpeechRecognizerIsValid(recognizer));
SbSpeechConfiguration configuration = {true, true, 1};
@@ -37,7 +40,8 @@
SbSpeechRecognizerDestroy(recognizer);
}
-#endif // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
+#endif // SB_API_VERSION >= SB_SPEECH_RECOGNIZER_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION >= 5
} // namespace nplb
} // namespace starboard
diff --git a/src/starboard/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc
index e6b09e3..65a2f1d 100644
--- a/src/starboard/nplb/speech_synthesis_basic_test.cc
+++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -19,14 +19,22 @@
namespace nplb {
namespace {
-#if SB_HAS(SPEECH_SYNTHESIS)
+#if SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION || \
+ SB_HAS(SPEECH_SYNTHESIS)
TEST(SbSpeechSynthesisBasicTest, Basic) {
+#if SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION
+ if (!SbSpeechSynthesisIsSupported()) {
+ SB_LOG(INFO) << "Speech synthesis not supported. Test skipped.";
+ return;
+ }
+#endif
SbSpeechSynthesisSpeak("Hello");
SbSpeechSynthesisCancel();
}
-#endif // SB_HAS(SPEECH_SYNTHESIS)
+#endif // SB_API_VERSION >= SB_SPEECH_SYNTHESIS_REQUIRED_VERSION ||
+ // SB_HAS(SPEECH_SYNTHESIS)
} // namespace
} // namespace nplb
diff --git a/src/starboard/nplb/thread_create_test.cc b/src/starboard/nplb/thread_create_test.cc
index 716e001..74d4bef 100644
--- a/src/starboard/nplb/thread_create_test.cc
+++ b/src/starboard/nplb/thread_create_test.cc
@@ -142,7 +142,7 @@
TEST(SbThreadCreateTest, Summertime) {
const int kMany = SB_MAX_THREADS;
- SbThread threads[kMany];
+ std::vector<SbThread> threads(kMany);
for (int i = 0; i < kMany; ++i) {
threads[i] = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
true, nplb::kThreadName, nplb::AddOneEntryPoint,
diff --git a/src/starboard/nplb/thread_local_value_test.cc b/src/starboard/nplb/thread_local_value_test.cc
index dcede6c..4c94f0f 100644
--- a/src/starboard/nplb/thread_local_value_test.cc
+++ b/src/starboard/nplb/thread_local_value_test.cc
@@ -177,7 +177,7 @@
TEST(SbThreadLocalValueTest, SunnyDayMany) {
const int kMany = (2 * SB_MAX_THREAD_LOCAL_KEYS) / 3;
- SbThreadLocalKey keys[kMany];
+ std::vector<SbThreadLocalKey> keys(kMany);
for (int i = 0; i < kMany; ++i) {
keys[i] = SbThreadCreateLocalKey(NULL);
diff --git a/src/starboard/raspi/shared/configuration_constants.cc b/src/starboard/raspi/shared/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/raspi/shared/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h
index bbeb085..13cd368 100644
--- a/src/starboard/raspi/shared/configuration_public.h
+++ b/src/starboard/raspi/shared/configuration_public.h
@@ -288,9 +288,6 @@
// specify that.
#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
// Determines the threshhold of allocation size that should be done with mmap
// (if available), rather than allocated within the core heap.
#define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -329,9 +326,6 @@
// The maximum number of thread local storage keys supported by this platform.
#define SB_MAX_THREAD_LOCAL_KEYS 512
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
// --- Timing API ------------------------------------------------------------
// Whether this platform has an API to retrieve how long the current thread
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index 17498ec..d43a9aa 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -43,6 +43,7 @@
'<@(filter_based_player_sources)',
'<(DEPTH)/starboard/linux/shared/atomic_public.h',
'<(DEPTH)/starboard/linux/shared/configuration_public.h',
+ '<(DEPTH)/starboard/linux/shared/configuration_constants.cc',
'<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
'<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
'<(DEPTH)/starboard/linux/shared/system_get_path.cc',
diff --git a/src/starboard/shared/iso/directory_get_next.cc b/src/starboard/shared/iso/directory_get_next.cc
index e478a39..101ef08 100644
--- a/src/starboard/shared/iso/directory_get_next.cc
+++ b/src/starboard/shared/iso/directory_get_next.cc
@@ -16,7 +16,16 @@
#include "starboard/shared/iso/impl/directory_get_next.h"
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+bool SbDirectoryGetNext(SbDirectory directory,
+ char* out_entry,
+ size_t out_entry_size) {
+ return ::starboard::shared::iso::impl::SbDirectoryGetNext(
+ directory, out_entry, out_entry_size);
+}
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
return ::starboard::shared::iso::impl::SbDirectoryGetNext(directory,
out_entry);
}
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
diff --git a/src/starboard/shared/iso/impl/directory_get_next.h b/src/starboard/shared/iso/impl/directory_get_next.h
index 1bb4a1d..74d3c39 100644
--- a/src/starboard/shared/iso/impl/directory_get_next.h
+++ b/src/starboard/shared/iso/impl/directory_get_next.h
@@ -31,7 +31,16 @@
namespace iso {
namespace impl {
-bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
+bool SbDirectoryGetNext(SbDirectory directory,
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ char* out_entry,
+ size_t out_entry_size) {
+ if (out_entry_size < SB_FILE_MAX_NAME) {
+ return false;
+ }
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ SbDirectoryEntry* out_entry) {
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
if (!directory || !directory->directory || !out_entry) {
return false;
}
@@ -54,8 +63,13 @@
}
} while (true);
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ SbStringCopy(out_entry, dirent->d_name, out_entry_size);
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbStringCopy(out_entry->name, dirent->d_name,
SB_ARRAY_SIZE_INT(out_entry->name));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
return true;
}
diff --git a/src/starboard/shared/linux/dev_input/dev_input.cc b/src/starboard/shared/linux/dev_input/dev_input.cc
index 4e76bb4..2acca58 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.cc
+++ b/src/starboard/shared/linux/dev_input/dev_input.cc
@@ -769,7 +769,19 @@
}
while (true) {
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<char> entry(SB_FILE_MAX_NAME);
+
+ if (!SbDirectoryGetNext(directory, entry.data(), SB_FILE_MAX_NAME)) {
+ break;
+ }
+
+ std::string path = kDevicePath;
+ path += "/";
+ path += entry.data();
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbDirectoryEntry entry;
+
if (!SbDirectoryGetNext(directory, &entry)) {
break;
}
@@ -777,6 +789,7 @@
std::string path = kDevicePath;
path += "/";
path += entry.name;
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
if (SbDirectoryCanOpen(path.c_str())) {
// This is a subdirectory. Skip.
diff --git a/src/starboard/shared/linux/thread_set_name.cc b/src/starboard/shared/linux/thread_set_name.cc
index f6066ec..be97f15 100644
--- a/src/starboard/shared/linux/thread_set_name.cc
+++ b/src/starboard/shared/linux/thread_set_name.cc
@@ -22,13 +22,15 @@
#include "starboard/common/log.h"
#include "starboard/common/string.h"
+#include "starboard/configuration_constants.h"
+
void SbThreadSetName(const char* name) {
// We don't want to rename the main thread.
if (SbThreadGetId() == getpid()) {
return;
}
- char buffer[SB_MAX_THREAD_NAME_LENGTH] = {};
+ char buffer[kSbMaxThreadNameLength];
if (SbStringGetLength(name) >= SB_ARRAY_SIZE_INT(buffer)) {
SbStringCopy(buffer, name, SB_ARRAY_SIZE_INT(buffer));
diff --git a/src/starboard/shared/opus/opus_audio_decoder.cc b/src/starboard/shared/opus/opus_audio_decoder.cc
index 04dd0a6..5ca3d94 100644
--- a/src/starboard/shared/opus/opus_audio_decoder.cc
+++ b/src/starboard/shared/opus/opus_audio_decoder.cc
@@ -27,7 +27,6 @@
namespace opus {
namespace {
-const int kMaxOpusFramesPerAU = 9600;
typedef struct {
int nb_streams;
@@ -52,15 +51,6 @@
OpusAudioDecoder::OpusAudioDecoder(
const SbMediaAudioSampleInfo& audio_sample_info)
: audio_sample_info_(audio_sample_info) {
-#if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
- working_buffer_.resize(kMaxOpusFramesPerAU *
- audio_sample_info_.number_of_channels *
- sizeof(opus_int16));
-#else // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
- working_buffer_.resize(kMaxOpusFramesPerAU *
- audio_sample_info_.number_of_channels * sizeof(float));
-#endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
-
int error;
int channels = audio_sample_info_.number_of_channels;
if (channels > 8 || channels < 1) {
@@ -106,28 +96,44 @@
SB_DCHECK(input_buffer);
SB_DCHECK(output_cb_);
- Schedule(consumed_cb);
-
if (stream_ended_) {
SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
return;
}
+ scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+ audio_sample_info_.number_of_channels, GetSampleType(),
+ kSbMediaAudioFrameStorageTypeInterleaved, input_buffer->timestamp(),
+ audio_sample_info_.number_of_channels * frames_per_au_ *
+ starboard::media::GetBytesPerSample(GetSampleType()));
+
#if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
const char kDecodeFunctionName[] = "opus_multistream_decode";
int decoded_frames = opus_multistream_decode(
decoder_, static_cast<const unsigned char*>(input_buffer->data()),
input_buffer->size(),
- reinterpret_cast<opus_int16*>(working_buffer_.data()),
- kMaxOpusFramesPerAU, 0);
+ reinterpret_cast<opus_int16*>(decoded_audio->buffer()), frames_per_au_,
+ 0);
#else // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
const char kDecodeFunctionName[] = "opus_multistream_decode_float";
int decoded_frames = opus_multistream_decode_float(
decoder_, static_cast<const unsigned char*>(input_buffer->data()),
- input_buffer->size(), reinterpret_cast<float*>(working_buffer_.data()),
- kMaxOpusFramesPerAU, 0);
+ input_buffer->size(), reinterpret_cast<float*>(decoded_audio->buffer()),
+ frames_per_au_, 0);
#endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
+ if (decoded_frames == OPUS_BUFFER_TOO_SMALL &&
+ frames_per_au_ < kMaxOpusFramesPerAU) {
+ frames_per_au_ = kMaxOpusFramesPerAU;
+ // Send to decode again with the new |frames_per_au_|.
+ Decode(input_buffer, consumed_cb);
+ return;
+ }
if (decoded_frames <= 0) {
+ // When the following check fails, it indicates that |frames_per_au_| is
+ // greater than or equal to |kMaxOpusFramesPerAU|, which should never happen
+ // for Opus.
+ SB_DCHECK(decoded_frames != OPUS_BUFFER_TOO_SMALL);
+
// TODO: Consider fill it with silence.
SB_LOG(ERROR) << kDecodeFunctionName
<< "() failed with error code: " << decoded_frames;
@@ -141,14 +147,13 @@
return;
}
- scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
- audio_sample_info_.number_of_channels, GetSampleType(),
- kSbMediaAudioFrameStorageTypeInterleaved, input_buffer->timestamp(),
- audio_sample_info_.number_of_channels * decoded_frames *
- starboard::media::GetBytesPerSample(GetSampleType()));
- SbMemoryCopy(decoded_audio->buffer(), working_buffer_.data(),
- decoded_audio->size());
+ frames_per_au_ = decoded_frames;
+ decoded_audio->ShrinkTo(audio_sample_info_.number_of_channels *
+ frames_per_au_ *
+ starboard::media::GetBytesPerSample(GetSampleType()));
+
decoded_audios_.push(decoded_audio);
+ Schedule(consumed_cb);
Schedule(output_cb_);
}
diff --git a/src/starboard/shared/opus/opus_audio_decoder.h b/src/starboard/shared/opus/opus_audio_decoder.h
index 689c33d..2c0f2d8 100644
--- a/src/starboard/shared/opus/opus_audio_decoder.h
+++ b/src/starboard/shared/opus/opus_audio_decoder.h
@@ -48,6 +48,8 @@
void Reset() override;
private:
+ static const int kMaxOpusFramesPerAU = 9600;
+
SbMediaAudioSampleType GetSampleType() const;
OutputCB output_cb_;
@@ -57,7 +59,7 @@
bool stream_ended_ = false;
std::queue<scoped_refptr<DecodedAudio> > decoded_audios_;
SbMediaAudioSampleInfo audio_sample_info_;
- std::vector<uint8_t> working_buffer_;
+ int frames_per_au_ = kMaxOpusFramesPerAU;
};
} // namespace opus
diff --git a/src/starboard/shared/pthread/mutex_destroy.cc b/src/starboard/shared/pthread/mutex_destroy.cc
index 799c83d..aa85d9c 100644
--- a/src/starboard/shared/pthread/mutex_destroy.cc
+++ b/src/starboard/shared/pthread/mutex_destroy.cc
@@ -17,6 +17,7 @@
#include <pthread.h>
#include "starboard/common/log.h"
+#include "starboard/configuration.h"
#include "starboard/shared/pthread/is_success.h"
bool SbMutexDestroy(SbMutex* mutex) {
@@ -24,6 +25,11 @@
return false;
}
+#if SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
+ // Both trying to recursively acquire a mutex that is locked by the calling
+ // thread, as well as deleting a locked mutex, result in undefined behavior.
+ return IsSuccess(pthread_mutex_destroy(mutex));
+#else // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
// Destroying a locked mutex is undefined, so fail if the mutex is
// already locked,
if (!IsSuccess(pthread_mutex_trylock(mutex))) {
@@ -33,4 +39,5 @@
return IsSuccess(pthread_mutex_unlock(mutex)) &&
IsSuccess(pthread_mutex_destroy(mutex));
+#endif // SB_API_VERSION >= SB_MUTEX_ACQUIRE_TRY_API_CHANGE_VERSION
}
diff --git a/src/starboard/shared/pulse/pulse_audio_sink_type.cc b/src/starboard/shared/pulse/pulse_audio_sink_type.cc
index 3bfa883..ff87de2 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 {
@@ -424,7 +437,13 @@
return false;
}
// Create pulse context.
+#if HAS_LEAK_SANITIZER
+ __lsan_disable();
+#endif
context_ = pa_context_new(pa_mainloop_get_api(mainloop_), "cobalt_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/configuration_constants_compatibility_defines.h b/src/starboard/shared/starboard/configuration_constants_compatibility_defines.h
new file mode 100644
index 0000000..3485e21
--- /dev/null
+++ b/src/starboard/shared/starboard/configuration_constants_compatibility_defines.h
@@ -0,0 +1,36 @@
+// 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.
+
+// This file defines macros to provide convenience for backwards compatibility
+// after the change that migrated certain configuration macros to extern
+// variables.
+
+#ifndef STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
+#define STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
+
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#error \
+ "This file is only relevant for Starboard versions before 12. Please do " \
+"not include this file otherwise."
+
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#define kSbMallocAlignment SB_MALLOC_ALIGNMENT
+
+#define kSbMaxThreadNameLength SB_MAX_THREAD_NAME_LENGTH
+
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+
+#endif // STARBOARD_SHARED_STARBOARD_CONFIGURATION_CONSTANTS_COMPATIBILITY_DEFINES_H_
diff --git a/src/starboard/shared/starboard/player/job_queue.cc b/src/starboard/shared/starboard/player/job_queue.cc
index de6f160..5f87bc3 100644
--- a/src/starboard/shared/starboard/player/job_queue.cc
+++ b/src/starboard/shared/starboard/player/job_queue.cc
@@ -153,8 +153,10 @@
JobToken job_token(current_job_token_);
JobRecord job_record = {job_token, job, owner};
#if ENABLE_JOB_QUEUE_PROFILING
- job_record.stack_size =
- SbSystemGetStack(job_record.stack, kProfileStackDepth);
+ if (kProfileStackDepth > 0) {
+ job_record.stack_size =
+ SbSystemGetStack(job_record.stack, kProfileStackDepth);
+ }
#endif // ENABLE_JOB_QUEUE_PROFILING
SbTimeMonotonic time_to_run_job = SbTimeGetMonotonicNow() + delay;
@@ -202,6 +204,9 @@
if (time_to_job_record_map_.empty() && wait_for_next_job) {
// |kSbTimeMax| makes more sense here, but |kSbTimeDay| is much safer.
condition_.WaitTimed(kSbTimeDay);
+#if ENABLE_JOB_QUEUE_PROFILING
+ ++wait_times_;
+#endif // ENABLE_JOB_QUEUE_PROFILING
}
if (time_to_job_record_map_.empty()) {
return false;
@@ -212,6 +217,9 @@
if (delay > 0) {
if (wait_for_next_job) {
condition_.WaitTimed(delay);
+#if ENABLE_JOB_QUEUE_PROFILING
+ ++wait_times_;
+#endif // ENABLE_JOB_QUEUE_PROFILING
if (time_to_job_record_map_.empty()) {
return false;
}
@@ -239,6 +247,8 @@
job_record.job();
#if ENABLE_JOB_QUEUE_PROFILING
+ ++jobs_processed_;
+
auto now = SbTimeGetMonotonicNow();
auto elapsed = now - start;
if (elapsed > max_job_interval_) {
@@ -246,7 +256,10 @@
max_job_interval_ = elapsed;
}
if (now - last_reset_time_ > kProfileResetInterval) {
- SB_LOG(INFO) << "================ Max job takes " << max_job_interval_;
+ SB_LOG(INFO) << "================ " << jobs_processed_
+ << " jobs processed, and waited for " << wait_times_
+ << " times since last reset on 0x" << this
+ << ", max job takes " << max_job_interval_;
for (int i = 0; i < job_record.stack_size; ++i) {
char function_name[1024];
if (SbSystemSymbolize(job_record.stack[i], function_name,
@@ -258,6 +271,8 @@
}
last_reset_time_ = now;
max_job_interval_ = 0;
+ jobs_processed_ = 0;
+ wait_times_ = 0;
}
#endif // ENABLE_JOB_QUEUE_PROFILING
return true;
diff --git a/src/starboard/shared/starboard/player/job_queue.h b/src/starboard/shared/starboard/player/job_queue.h
index 231c1d4..b236fcd 100644
--- a/src/starboard/shared/starboard/player/job_queue.h
+++ b/src/starboard/shared/starboard/player/job_queue.h
@@ -163,6 +163,8 @@
SbTimeMonotonic last_reset_time_ = SbTimeGetMonotonicNow();
JobRecord job_record_with_max_interval_;
SbTimeMonotonic max_job_interval_ = 0;
+ int jobs_processed_ = 0;
+ int wait_times_ = 0;
#endif // ENABLE_JOB_QUEUE_PROFILING
};
diff --git a/src/starboard/shared/starboard/thread_local_storage_internal.cc b/src/starboard/shared/starboard/thread_local_storage_internal.cc
index 0e28828..f98994b 100644
--- a/src/starboard/shared/starboard/thread_local_storage_internal.cc
+++ b/src/starboard/shared/starboard/thread_local_storage_internal.cc
@@ -229,7 +229,12 @@
KeyRecord* record = &data_->key_table_[key->index];
record->destructor = destructor;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ SbMemorySet(record->values.data(), 0,
+ record->values.size() * sizeof(record->values[0]));
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
SbMemorySet(record->values, 0, sizeof(record->values));
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
record->valid = true;
return key;
diff --git a/src/starboard/shared/starboard/thread_local_storage_internal.h b/src/starboard/shared/starboard/thread_local_storage_internal.h
index 9438fa9..312b98b 100644
--- a/src/starboard/shared/starboard/thread_local_storage_internal.h
+++ b/src/starboard/shared/starboard/thread_local_storage_internal.h
@@ -15,6 +15,8 @@
#ifndef STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_INTERNAL_H_
#define STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_INTERNAL_H_
+#include <vector>
+
#include "starboard/common/mutex.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/shared/internal_only.h"
@@ -55,7 +57,11 @@
struct KeyRecord {
bool valid;
SbThreadLocalDestructor destructor;
+#if SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
+ std::vector<void*> values(kMaxThreads);
+#else // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
void* values[kMaxThreads];
+#endif // SB_API_VERSION >= SB_FEATURE_RUNTIME_CONFIGS_VERSION
};
// Sets up the specified key.
diff --git a/src/starboard/shared/stub/directory_get_next.cc b/src/starboard/shared/stub/directory_get_next.cc
index 5173d4e..8987598 100644
--- a/src/starboard/shared/stub/directory_get_next.cc
+++ b/src/starboard/shared/stub/directory_get_next.cc
@@ -15,6 +15,7 @@
#include "starboard/directory.h"
bool SbDirectoryGetNext(SbDirectory /*directory*/,
- SbDirectoryEntry* /*out_entry*/) {
+ char* /*out_entry*/,
+ size_t /* out_entry_size */) {
return false;
}
diff --git a/src/starboard/starboard_all.gyp b/src/starboard/starboard_all.gyp
index 76bb448..bfc58b3 100644
--- a/src/starboard/starboard_all.gyp
+++ b/src/starboard/starboard_all.gyp
@@ -89,6 +89,11 @@
'<(DEPTH)/starboard/shared/starboard/player/filter/tools/tools.gyp:*',
],
}],
+ ['sb_enable_benchmark==1', {
+ 'dependencies': [
+ '<(DEPTH)/starboard/benchmark/benchmark.gyp:*',
+ ],
+ }],
],
},
],
diff --git a/src/starboard/starboard_headers_only.gyp b/src/starboard/starboard_headers_only.gyp
index 6546f76..db954f5 100644
--- a/src/starboard/starboard_headers_only.gyp
+++ b/src/starboard/starboard_headers_only.gyp
@@ -33,6 +33,7 @@
'character.h',
'condition_variable.h',
'configuration.h',
+ 'configuration_constants.h',
'cpu_features.h',
'decode_target.h',
'directory.h',
diff --git a/src/starboard/stub/configuration_constants.cc b/src/starboard/stub/configuration_constants.cc
new file mode 100644
index 0000000..5b3c886
--- /dev/null
+++ b/src/starboard/stub/configuration_constants.cc
@@ -0,0 +1,23 @@
+// 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.
+
+// This file defines all configuration constants for a platform.
+
+#include "starboard/configuration_constants.h"
+
+// Determines the alignment that allocations should have on this platform.
+const size_t kSbMallocAlignment = 16;
+
+// The maximum length of a name for a thread, including the NULL-terminator.
+const int32_t kSbMaxThreadNameLength = 16;
diff --git a/src/starboard/stub/configuration_public.h b/src/starboard/stub/configuration_public.h
index 265be2a..95eaf1f 100644
--- a/src/starboard/stub/configuration_public.h
+++ b/src/starboard/stub/configuration_public.h
@@ -367,9 +367,6 @@
// specify that.
#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
-// Determines the alignment that allocations should have on this platform.
-#define SB_MALLOC_ALIGNMENT ((size_t)16U)
-
// Determines the threshhold of allocation size that should be done with mmap
// (if available), rather than allocated within the core heap.
#define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
@@ -398,9 +395,6 @@
// The maximum number of thread local storage keys supported by this platform.
#define SB_MAX_THREAD_LOCAL_KEYS 512
-// The maximum length of the name for a thread, including the NULL-terminator.
-#define SB_MAX_THREAD_NAME_LENGTH 16
-
// --- Timing API ------------------------------------------------------------
// Whether this platform has an API to retrieve how long the current thread
diff --git a/src/starboard/stub/starboard_platform.gyp b/src/starboard/stub/starboard_platform.gyp
index 5971d03..5baf0ca 100644
--- a/src/starboard/stub/starboard_platform.gyp
+++ b/src/starboard/stub/starboard_platform.gyp
@@ -24,6 +24,7 @@
'application_stub.cc',
'application_stub.h',
'atomic_public.h',
+ 'configuration_constants.cc',
'main.cc',
'thread_types_public.h',
# Include private stubs, if present.
diff --git a/src/starboard/tools/abstract_launcher.py b/src/starboard/tools/abstract_launcher.py
index 90dc288..a9b5063 100644
--- a/src/starboard/tools/abstract_launcher.py
+++ b/src/starboard/tools/abstract_launcher.py
@@ -172,6 +172,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/package.py b/src/starboard/tools/package.py
index 8e3245c..716596b 100644
--- a/src/starboard/tools/package.py
+++ b/src/starboard/tools/package.py
@@ -33,7 +33,8 @@
path: Path to the platform
root_module: An already-loaded module
module_name: Name of a python module to load. If None, load the platform
- directory as a python module.
+ directory as a python module.
+
Returns:
A module loaded with importlib.import_module
Throws:
@@ -95,8 +96,9 @@
except Exception as e: # pylint: disable=broad-except
# Catch all exceptions to avoid an error in one platform's Packager
# halting the script for other platforms' packagers.
- logging.warning('Exception iterating supported platform for platform '
- '%s: %s.', platform_info.name, e)
+ logging.warning(
+ 'Exception iterating supported platform for platform '
+ '%s: %s.', platform_info.name, e)
return packager_modules
@@ -121,10 +123,9 @@
Args:
targets: A list of targets to install the package to, or None on platforms
- that support installing to a default target.
-
- This method can be overridden to implement platform-specific steps to
- install the package for that platform.
+ that support installing to a default target. This method can be
+ overridden to implement platform-specific steps to install the package
+ for that platform.
"""
del targets
@@ -148,6 +149,7 @@
constructor.
Args:
options: A namespace object returned from ArgumentParser.parse_args
+
Returns:
A dict of kwargs to be passed to the Package constructor.
"""
@@ -168,12 +170,12 @@
def GetPlatformInfo(self, platform_name):
return self.platform_infos.get(platform_name, None)
- def GetApplicationPackageInfo(self, platform_name, applciation_name):
+ def GetApplicationPackageInfo(self, platform_name, application_name):
"""Get application-specific packaging information."""
platform_info = self.GetPlatformInfo(platform_name)
try:
return _ImportModule(platform_info.path, starboard,
- '%s.package' % applciation_name)
+ '%s.package' % application_name)
except ImportError as e:
# No package parameters specified for this platform.
logging.debug('Failed to import cobalt.package: %s', e)
@@ -187,6 +189,7 @@
source_dir: The directory containing the application to be packaged.
output_dir: The directory into which the package files should be placed.
**kwargs: Platform-specific arguments.
+
Returns:
A PackageBase instance.
"""
diff --git a/src/starboard/tools/port_symlink.py b/src/starboard/tools/port_symlink.py
index afd1202..bd7a7a1 100644
--- a/src/starboard/tools/port_symlink.py
+++ b/src/starboard/tools/port_symlink.py
@@ -136,6 +136,11 @@
formatter_class = argparse.RawDescriptionHelpFormatter
parser = MyParser(epilog=help_msg, formatter_class=formatter_class)
parser.add_argument(
+ '-a',
+ '--use_absolute_symlinks',
+ action='store_true',
+ help='Generated symlinks are stored as absolute paths.')
+ parser.add_argument(
'-f',
'--force',
action='store_true',
@@ -155,6 +160,9 @@
args = parser.parse_args()
folder_path, link_path = args.link
+ if args.use_absolute_symlinks:
+ folder_path = os.path.abspath(folder_path)
+ link_path = os.path.abspath(link_path)
if '.' in folder_path:
d1 = os.path.abspath(folder_path)
else:
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/starboard/tools/symbolize/_env.py b/src/starboard/tools/symbolize/_env.py
new file mode 100644
index 0000000..021908e
--- /dev/null
+++ b/src/starboard/tools/symbolize/_env.py
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+"""Ask the parent directory to load the project environment."""
+
+from imp import load_source
+from os import path
+import sys
+
+_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
+if not path.exists(_ENV):
+ print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
+ sys.exit(1)
+load_source('', _ENV)
diff --git a/src/starboard/tools/symbolize/symbolize.py b/src/starboard/tools/symbolize/symbolize.py
new file mode 100644
index 0000000..cd135a6
--- /dev/null
+++ b/src/starboard/tools/symbolize/symbolize.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+
+# 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.
+"""Lightweight utility to simplify resolving stack traces and crashes.
+
+This tool supports three different formats for crashes and stack traces, but can
+easily be expanded for addition cases. Examples of current formats are as
+follows:
+
+ Address Sanitizer
+ #1 0x7fdc59bbaa6b (<unknown module>)
+
+ Cobalt
+ <unknown> [0x7efcdf1fd52b]
+
+ Raw
+ 0x7efcdf1fd52b
+
+The results of the symbolizer will only be included if it was able to find the
+name of the symbol, and it does not appear to be malformed. The only exception
+is when the line was matched with the |_RAW| regular expression in which case it
+will always output the results of the symbolizer.
+"""
+
+import _env # pylint: disable=unused-import
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+
+from starboard.build import clang
+from starboard.tools import build
+
+_SYMBOLIZER = os.path.join(
+ build.GetToolchainsDir(), 'x86_64-linux-gnu-clang-chromium-{}'.format(
+ clang.GetClangSpecification().revision), 'bin', 'llvm-symbolizer')
+
+_RE_ASAN = re.compile(
+ r'\s*(#[0-9]{1,3})\s*(0x[a-z0-9]*)\s*\(<unknown\smodule>\)')
+_RE_COBALT = re.compile(r'\s*<unknown> \[(0x[a-z0-9]*)\]\s*')
+_RE_RAW = re.compile(r'^(0x[a-z0-9]*)$')
+
+
+def _Symbolize(filename, library, base_address):
+ """Attempts to resolve memory addresses within the file specified.
+
+ This function iterates through the file specified line by line. When a line is
+ found that matches one of our regular expressions it will stop and invoke
+ llvm-symbolizer with the offset of the symbol and the library specified. The
+ results are verified and the output formatted to match whichever crash-style
+ is being used.
+
+ Args:
+ filename: The path to the file containing the stack trace.
+ library: The path to the library that is believed to have the symbol.
+ base_address: The base address of the library when it was loaded and
+ crashed, typically found in the logs.
+ """
+ if not os.path.exists(filename):
+ raise ValueError('File not found: {}.'.format(filename))
+ if not os.path.exists(library):
+ raise ValueError('Library not found: {}.'.format(library))
+ with open(filename) as f:
+ for line in f:
+ # Address Sanitizer
+ match = _RE_ASAN.match(line)
+ if match:
+ offset = int(match.group(2), 0) - int(base_address, 0)
+ results = _RunSymbolizer(library, str(offset))
+ if results and '?' not in results[0] and '?' not in results[1]:
+ sys.stdout.write(' {} {} in {} {}\n'.format(
+ match.group(1), hex(offset), results[0], results[1]))
+ continue
+ # Cobalt
+ match = _RE_COBALT.match(line)
+ if match:
+ offset = int(match.group(1), 0) - int(base_address, 0)
+ results = _RunSymbolizer(library, str(offset))
+ if results and '?' not in results[0]:
+ sys.stdout.write(' {} [{}]\n'.format(hex(offset), results[0]))
+ continue
+ # Raw
+ match = _RE_RAW.match(line)
+ if match:
+ offset = int(match.group(1), 0) - int(base_address, 0)
+ results = _RunSymbolizer(library, str(offset))
+ if results:
+ sys.stdout.write('{} {} in {}\n'.format(
+ hex(offset), results[0], results[1]))
+ continue
+ sys.stdout.write(line)
+
+
+def _RunSymbolizer(library, offset):
+ """Uses a external symbolizer tool to resolve symbol names.
+
+ Args:
+ library: The path to the library that is believed to have the symbol.
+ offset: The offset into the library of the symbol we are looking for.
+ """
+ if int(offset) >= 0:
+ command = subprocess.Popen([_SYMBOLIZER, '-e', library, offset, '-f'],
+ stdout=subprocess.PIPE)
+ results = command.communicate()
+ if command.returncode == 0:
+ return results[0].split(os.linesep)
+ return None
+
+
+def main():
+ arg_parser = argparse.ArgumentParser()
+ arg_parser.add_argument(
+ '-f',
+ '--filename',
+ required=True,
+ help='The path to the file that contains the stack traces, crashes, or raw addresses.'
+ )
+ arg_parser.add_argument(
+ '-l',
+ '--library',
+ required=True,
+ help='The path to the library that is believed to contain the addresses.')
+ arg_parser.add_argument(
+ 'base_address',
+ type=str,
+ nargs=1,
+ help='The base address of the library.')
+ args, _ = arg_parser.parse_known_args()
+
+ if not os.path.exists(_SYMBOLIZER):
+ raise ValueError(
+ 'Please update {} with a valid llvm-symbolizer path.'.format(__file__))
+
+ return _Symbolize(args.filename, args.library, args.base_address[0])
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/src/starboard/tools/testing/test_runner.py b/src/starboard/tools/testing/test_runner.py
index ba8f2b6..ac03457 100755
--- a/src/starboard/tools/testing/test_runner.py
+++ b/src/starboard/tools/testing/test_runner.py
@@ -416,8 +416,7 @@
coverage_directory=self.coverage_directory,
env_variables=env,
loader_platform=self.loader_platform,
- loader_config=self.loader_config,
- loader_out_directory=self.loader_out_directory)
+ loader_config=self.loader_config)
test_reader = TestLineReader(read_pipe)
test_launcher = TestLauncher(launcher)
diff --git a/src/third_party/QR-Code-generator/cpp/QrCode.cpp b/src/third_party/QR-Code-generator/cpp/QrCode.cpp
index 0f78920..0dbc086 100644
--- a/src/third_party/QR-Code-generator/cpp/QrCode.cpp
+++ b/src/third_party/QR-Code-generator/cpp/QrCode.cpp
@@ -54,9 +54,9 @@
}
-QrCode QrCode::encodeText(const char *text, Ecc ecl) {
+QrCode QrCode::encodeText(const char *text, Ecc ecl, int minVersion) {
vector<QrSegment> segs = QrSegment::makeSegments(text);
- return encodeSegments(segs, ecl);
+ return encodeSegments(segs, ecl, minVersion);
}
diff --git a/src/third_party/QR-Code-generator/cpp/QrCode.hpp b/src/third_party/QR-Code-generator/cpp/QrCode.hpp
index 14a3f61..31dd1d7 100644
--- a/src/third_party/QR-Code-generator/cpp/QrCode.hpp
+++ b/src/third_party/QR-Code-generator/cpp/QrCode.hpp
@@ -64,7 +64,7 @@
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than
* the ecl argument if it can be done without increasing the version.
*/
- public: static QrCode encodeText(const char *text, Ecc ecl);
+ public: static QrCode encodeText(const char *text, Ecc ecl, int minVersion);
/*
diff --git a/src/third_party/angle/include/angle_hdr.h b/src/third_party/angle/include/angle_hdr.h
new file mode 100644
index 0000000..8a9dd6c
--- /dev/null
+++ b/src/third_party/angle/include/angle_hdr.h
@@ -0,0 +1,29 @@
+// 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 ANGLE_HDR_H_
+#define ANGLE_HDR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void SetHdrAngleModeEnabled(bool flag);
+bool IsHdrAngleModeEnabled();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANGLE_HDR_H_
diff --git a/src/third_party/angle/src/common/angle_hdr.cpp b/src/third_party/angle/src/common/angle_hdr.cpp
new file mode 100644
index 0000000..f975cb5
--- /dev/null
+++ b/src/third_party/angle/src/common/angle_hdr.cpp
@@ -0,0 +1,42 @@
+// 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.
+
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+
+#include "starboard/atomic.h"
+#include "starboard/common/log.h"
+
+namespace angle
+{
+
+starboard::atomic_int32_t hdr_angle_mode_enable(0);
+
+}
+
+void SetHdrAngleModeEnabled(bool flag)
+{
+ if (!flag && angle::hdr_angle_mode_enable.load() == 0)
+ {
+ return;
+ }
+ angle::hdr_angle_mode_enable.fetch_add(flag ? 1 : -1);
+ SB_DCHECK(angle::hdr_angle_mode_enable.load() >= 0);
+}
+
+bool IsHdrAngleModeEnabled()
+{
+ return angle::hdr_angle_mode_enable.load() > 0;
+}
+#endif // STARBOARD
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index b4366e7..b669e8c 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -26,6 +26,63 @@
namespace
{
+#if defined(STARBOARD)
+// The following numbers are received from Recommendation ITU - R BT .2100 - 2(07 / 2018),
+// table 4 - PQ system reference non linear transfer functions
+// c1 = 0.8359375;
+// c2 = 18.8515625;
+// c3 = 18.6875;
+// m1 = 0.159301758125;
+// m2 = 78.84375;
+const std::string BT709_TO_BT2020_SHADER =
+ "struct PS_OUTPUT\n"
+ "{\n"
+ " float4 gl_Color0 : SV_TARGET0;\n"
+ "};\n"
+ "#define kRefWhiteLevelSRGB 290.0f\n"
+ "#define kRefWhiteLevelPQ 10000.0f\n"
+ "static const float3x3 BT709_TO_BT2020 = { // ref: ARIB STD-B62 and BT.2087\n"
+ " 0.6274, 0.3293, 0.0433,\n"
+ " 0.0691, 0.9195, 0.0114,\n"
+ " 0.0164, 0.0880, 0.8956\n"
+ "};\n"
+ "float3 SRGB_EOTF(float3 E)\n"
+ "{\n"
+ " float3 dark = E/12.92;\n"
+ " float3 light = pow(abs((E+0.055)/(1+0.055)), 2.4);\n"
+ " bool3 cri = E <= 0.04045;\n"
+ " float3 cri_float = (float3)cri;\n"
+ " float3 r = lerp(light, dark, cri_float);\n"
+ " r = r * kRefWhiteLevelSRGB;\n"
+ " return r;\n"
+ "}\n"
+ "//input: normalized L in units of RefWhite (1.0=100nits), output: normalized E\n"
+ "float3 PQ_OETF(float3 L)\n"
+ "{\n"
+ " const float c1 = 0.8359375;\n"
+ " const float c2 = 18.8515625;\n"
+ " const float c3 = 18.6875;\n"
+ " const float m1 = 0.159301758125;\n"
+ " const float m2 = 78.84375;\n"
+ " L = L / kRefWhiteLevelPQ;\n"
+ " float3 Lm1 = pow(abs(L), m1);\n"
+ " float3 X = (c1 + c2 * Lm1) / (1 + c3 * Lm1);\n"
+ " float3 res = pow(abs(X), m2);\n"
+ " return res;\n"
+ "}\n"
+ "PS_OUTPUT generateOutput()\n"
+ "{\n"
+ " PS_OUTPUT output;\n"
+ " \n"
+ " float3 input_colors = gl_Color[0].rgb;\n"
+ " float3 lin_osd_graphics = SRGB_EOTF(input_colors);\n"
+ " lin_osd_graphics = mul(BT709_TO_BT2020, lin_osd_graphics);\n"
+ " output.gl_Color0.rgb = PQ_OETF(lin_osd_graphics);\n"
+ " output.gl_Color0.a = gl_Color[0].a;\n"
+ " return output;\n"
+ "}\n";
+#endif // STARBOARD
+
std::string HLSLComponentTypeString(GLenum componentType)
{
switch (componentType)
@@ -322,6 +379,21 @@
return pixelHLSL;
}
+#if defined(STARBOARD)
+std::string DynamicHLSL::generatePixelShaderForHdrOutputSignature(
+ const std::string &sourceShader,
+ const std::vector<PixelShaderOutputVariable> &outputVariables,
+ bool usesFragDepth,
+ const std::vector<GLenum> &outputLayout) const
+{
+ std::string pixelHLSL(sourceShader);
+ size_t outputInsertionPos = pixelHLSL.find(PIXEL_OUTPUT_STUB_STRING);
+ pixelHLSL.replace(outputInsertionPos, strlen(PIXEL_OUTPUT_STUB_STRING), BT709_TO_BT2020_SHADER);
+
+ return pixelHLSL;
+}
+#endif // STARBOARD
+
void DynamicHLSL::generateVaryingLinkHLSL(const VaryingPacking &varyingPacking,
const BuiltinInfo &builtins,
bool programUsesPointSize,
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
index 0972a62..c0bfc05 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.h
@@ -112,6 +112,13 @@
const std::string &sourceShader,
const gl::InputLayout &inputLayout,
const std::vector<sh::Attribute> &shaderAttributes) const;
+#if defined(STARBOARD)
+ std::string generatePixelShaderForHdrOutputSignature(
+ const std::string &sourceShader,
+ const std::vector<PixelShaderOutputVariable> &outputVariables,
+ bool usesFragDepth,
+ const std::vector<GLenum> &outputLayout) const;
+#endif // STARBOARD
std::string generatePixelShaderForOutputSignature(
const std::string &sourceShader,
const std::vector<PixelShaderOutputVariable> &outputVariables,
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index db531ee..789bced 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1148,6 +1148,76 @@
{
}
+#if defined(STARBOARD)
+gl::Error ProgramD3D::getPixelExecutableForHdrFramebuffer(const gl::Framebuffer *fbo,
+ ShaderExecutableD3D **outExecutable)
+{
+ mPixelShaderOutputFormatCache.clear();
+
+ const FramebufferD3D *fboD3D = GetImplAs<FramebufferD3D>(fbo);
+ const gl::AttachmentList &colorbuffers = fboD3D->getColorAttachmentsForRender();
+
+ for (size_t colorAttachment = 0; colorAttachment < colorbuffers.size(); ++colorAttachment)
+ {
+ const gl::FramebufferAttachment *colorbuffer = colorbuffers[colorAttachment];
+
+ if (colorbuffer)
+ {
+ mPixelShaderOutputFormatCache.push_back(colorbuffer->getBinding() == GL_BACK
+ ? GL_COLOR_ATTACHMENT0
+ : colorbuffer->getBinding());
+ }
+ else
+ {
+ mPixelShaderOutputFormatCache.push_back(GL_NONE);
+ }
+ }
+
+ return getPixelExecutableForHdrOutputLayout(mPixelShaderOutputFormatCache, outExecutable,
+ nullptr);
+}
+
+gl::Error ProgramD3D::getPixelExecutableForHdrOutputLayout(
+ const std::vector<GLenum> &outputSignature,
+ ShaderExecutableD3D **outExecutable,
+ gl::InfoLog *infoLog)
+{
+ if (mPixelHdrExecutable)
+ {
+ *outExecutable = mPixelHdrExecutable->shaderExecutable();
+ return gl::NoError();
+ }
+
+ std::string finalPixelHLSL = mDynamicHLSL->generatePixelShaderForHdrOutputSignature(
+ mPixelHLSL, mPixelShaderKey, mUsesFragDepth, outputSignature);
+
+ // Generate new pixel executable
+ ShaderExecutableD3D *pixelExecutable = nullptr;
+
+ gl::InfoLog tempInfoLog;
+ gl::InfoLog *currentInfoLog = infoLog ? infoLog : &tempInfoLog;
+
+ ANGLE_TRY(mRenderer->compileToExecutable(
+ *currentInfoLog, finalPixelHLSL, SHADER_PIXEL, mStreamOutVaryings,
+ (mState.getTransformFeedbackBufferMode() == GL_SEPARATE_ATTRIBS), mPixelWorkarounds,
+ &pixelExecutable));
+
+ if (pixelExecutable)
+ {
+ mPixelHdrExecutable =
+ std::unique_ptr<PixelExecutable>(new PixelExecutable(outputSignature, pixelExecutable));
+ }
+ else if (!infoLog)
+ {
+ ERR() << "Error compiling BT709 to BT2020 pixel executable:" << std::endl
+ << tempInfoLog.str() << std::endl;
+ }
+
+ *outExecutable = pixelExecutable;
+ return gl::NoError();
+}
+#endif // STARBOARD
+
gl::Error ProgramD3D::getPixelExecutableForFramebuffer(const gl::Framebuffer *fbo,
ShaderExecutableD3D **outExecutable)
{
@@ -2328,6 +2398,9 @@
{
mVertexExecutables.clear();
mPixelExecutables.clear();
+#if defined(STARBOARD)
+ mPixelHdrExecutable.reset();
+#endif // STARBOARD
for (auto &geometryExecutable : mGeometryExecutables)
{
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
index 19b7dbf..d6015fb 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -170,6 +170,13 @@
gl::Error getPixelExecutableForOutputLayout(const std::vector<GLenum> &outputLayout,
ShaderExecutableD3D **outExectuable,
gl::InfoLog *infoLog);
+#if defined(STARBOARD)
+ gl::Error getPixelExecutableForHdrFramebuffer(const gl::Framebuffer *fbo,
+ ShaderExecutableD3D **outExectuable);
+ gl::Error getPixelExecutableForHdrOutputLayout(const std::vector<GLenum> &outputLayout,
+ ShaderExecutableD3D **outExectuable,
+ gl::InfoLog *infoLog);
+#endif // STARBOARD
gl::Error getVertexExecutableForInputLayout(const gl::InputLayout &inputLayout,
ShaderExecutableD3D **outExectuable,
gl::InfoLog *infoLog);
@@ -396,7 +403,9 @@
std::vector<std::unique_ptr<PixelExecutable>> mPixelExecutables;
std::vector<std::unique_ptr<ShaderExecutableD3D>> mGeometryExecutables;
std::unique_ptr<ShaderExecutableD3D> mComputeExecutable;
-
+#if defined(STARBOARD)
+ std::unique_ptr<PixelExecutable> mPixelHdrExecutable;
+#endif // STARBOARD
std::string mVertexHLSL;
angle::CompilerWorkaroundsD3D mVertexWorkarounds;
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index d9b0b27..1a8601d 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -13,6 +13,9 @@
#include <versionhelpers.h>
#include <sstream>
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+#endif // STARBOARD
#include "common/tls.h"
#include "common/utilities.h"
#include "libANGLE/Buffer.h"
@@ -378,6 +381,35 @@
const uint32_t ScratchMemoryBufferLifetime = 1000;
+#if defined(STARBOARD)
+angle::Format::ID GetTextureFormatId(const gl::ContextState &data)
+{
+ const auto &glState = data.getState();
+ ProgramD3D *programD3D = GetImplAs<ProgramD3D>(glState.getProgram());
+
+ gl::SamplerType type = gl::SAMPLER_PIXEL;
+ unsigned int samplerRange = programD3D->getUsedSamplerRange(type);
+ for (unsigned int i = 0; i < samplerRange; i++)
+ {
+ GLint textureUnit = programD3D->getSamplerMapping(type, i, data.getCaps());
+ if (textureUnit != -1)
+ {
+ gl::Texture *texture = data.getState().getSamplerTexture(
+ textureUnit, programD3D->getSamplerTextureType(type, i));
+ ASSERT(texture);
+ rx::TextureD3D *textureD3D = GetImplAs<TextureD3D>(texture);
+ TextureStorage *texStorage = nullptr;
+ textureD3D->getNativeTexture(&texStorage);
+ if (texStorage)
+ {
+ return GetAs<TextureStorage11_2D>(texStorage)->getFormat().id;
+ }
+ }
+ }
+ return angle::Format::ID::NONE;
+}
+#endif // STARBOARD
+
} // anonymous namespace
Renderer11::Renderer11(egl::Display *display)
@@ -2455,7 +2487,30 @@
const gl::Framebuffer *drawFramebuffer = glState.getDrawFramebuffer();
ShaderExecutableD3D *pixelExe = nullptr;
+#if defined(STARBOARD)
+ // While 10-bit HDR video is playing we run the pixel shader to apply color space for all UI
+ // elements conversion from 8-bit to 10-bit for all draw calls that do not involve the HDR video
+ // texture (look at spec ITU - R BT .2100 - 2(07 / 2018) for BT709 to BT2020 transform). This
+ // conversion is applicable only once when we draw to the display - drawFramebuffer->id() is 0.
+ if (IsHdrAngleModeEnabled() && drawFramebuffer->id() == 0)
+ {
+ if (GetTextureFormatId(data) == angle::Format::ID::R10G10B10A2_UNORM ||
+ GetTextureFormatId(data) == angle::Format::ID::R16_UNORM)
+ {
+ ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+ }
+ else
+ {
+ ANGLE_TRY(programD3D->getPixelExecutableForHdrFramebuffer(drawFramebuffer, &pixelExe));
+ }
+ }
+ else
+ {
+ ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+ }
+#else
ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(drawFramebuffer, &pixelExe));
+#endif // STARBOARD
ShaderExecutableD3D *geometryExe = nullptr;
ANGLE_TRY(
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
index a8f4627..4d693f0 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.cpp
@@ -10,6 +10,9 @@
#include <EGL/eglext.h>
+#if defined(STARBOARD)
+#include "angle_hdr.h"
+#endif // STARBOARD
#include "libANGLE/features.h"
#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
#include "libANGLE/renderer/d3d/d3d11/NativeWindow11.h"
@@ -23,6 +26,12 @@
#include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2d11ps.h"
#include "libANGLE/renderer/d3d/d3d11/shaders/compiled/passthroughrgba2dms11ps.h"
+#if defined(STARBOARD)
+#include <initguid.h>
+#include <dxgi1_4.h>
+#include <dxgi1_6.h>
+#endif // STARBOARD
+
#ifdef ANGLE_ENABLE_KEYEDMUTEX
#define ANGLE_RESOURCE_SHARE_TYPE D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX
#else
@@ -753,6 +762,37 @@
return result;
}
+#if defined(STARBOARD)
+ if (IsHdrAngleModeEnabled())
+ {
+ if (mCurrentColorSpace != DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
+ {
+ IDXGISwapChain3 *swapChain3 = static_cast<IDXGISwapChain3 *>(mSwapChain);
+ result = swapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+ if (FAILED(result))
+ {
+ ERR() << "Color space DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 setup failed.";
+ return EGL_BAD_CONFIG;
+ }
+ mCurrentColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+ }
+ }
+ else
+ {
+ if (mCurrentColorSpace != DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709)
+ {
+ IDXGISwapChain3 *swapChain3 = static_cast<IDXGISwapChain3 *>(mSwapChain);
+ result = swapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ if (FAILED(result))
+ {
+ ERR() << "Color space DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 setup failed.";
+ return EGL_BAD_CONFIG;
+ }
+ mCurrentColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ }
+ }
+#endif // STARBOARD
+
mRenderer->onSwap();
return EGL_SUCCESS;
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
index 85bd35d..71b9ad4 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/SwapChain11.h
@@ -114,6 +114,9 @@
EGLint mEGLSamples;
LONGLONG mQPCFrequency;
+#if defined(STARBOARD)
+ DXGI_COLOR_SPACE_TYPE mCurrentColorSpace = DXGI_COLOR_SPACE_CUSTOM;
+#endif // STARBOARD
};
} // namespace rx
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
index 5edc65d..449e811 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
@@ -264,6 +264,15 @@
return gl::NoError();
}
+#if defined(STARBOARD)
+const angle::Format &TextureStorage11_2D::getFormat()
+{
+ D3D11_TEXTURE2D_DESC desc = {0};
+ mTexture->GetDesc(&desc);
+ return d3d11_angle::GetFormat(desc.Format);
+}
+#endif // STARBOARD
+
gl::Error TextureStorage11::getCachedOrCreateSRV(const SRVKey &key,
ID3D11ShaderResourceView **outSRV)
{
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
index 366c7ea3..9712194 100644
--- a/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
+++ b/src/third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
@@ -173,6 +173,9 @@
gl::Error releaseAssociatedImage(const gl::ImageIndex &index, Image11 *incomingImage) override;
gl::Error useLevelZeroWorkaroundTexture(bool useLevelZeroTexture) override;
+#if defined(STARBOARD)
+ const angle::Format &getFormat();
+#endif // STARBOARD
protected:
gl::Error getSwizzleTexture(ID3D11Resource **outTexture) override;
diff --git a/src/third_party/angle/src/libGLESv2.gypi b/src/third_party/angle/src/libGLESv2.gypi
index aa05e4c..b691558 100644
--- a/src/third_party/angle/src/libGLESv2.gypi
+++ b/src/third_party/angle/src/libGLESv2.gypi
@@ -14,6 +14,7 @@
'<(DEPTH)/third_party/angle/src/common/MemoryBuffer.cpp',
'<(DEPTH)/third_party/angle/src/common/MemoryBuffer.h',
'<(DEPTH)/third_party/angle/src/common/Optional.h',
+ '<(DEPTH)/third_party/angle/src/common/angle_hdr.cpp',
'<(DEPTH)/third_party/angle/src/common/angleutils.cpp',
'<(DEPTH)/third_party/angle/src/common/angleutils.h',
'<(DEPTH)/third_party/angle/src/common/bitset_utils.h',
@@ -92,6 +93,7 @@
'libangle_includes':
[
'<(DEPTH)/third_party/angle/include/angle_gl.h',
+ '<(DEPTH)/third_party/angle/include/angle_hdr.h',
'<(DEPTH)/third_party/angle/include/export.h',
'<(DEPTH)/third_party/angle/include/EGL/egl.h',
'<(DEPTH)/third_party/angle/include/EGL/eglext.h',
diff --git a/src/third_party/dlmalloc/dlmalloc_config.h b/src/third_party/dlmalloc/dlmalloc_config.h
index 8b8543f..9335f50 100644
--- a/src/third_party/dlmalloc/dlmalloc_config.h
+++ b/src/third_party/dlmalloc/dlmalloc_config.h
@@ -19,6 +19,7 @@
#if defined(STARBOARD)
#include <sys/types.h> // for ssize_t, maybe should add to starboard/types.h
#include "starboard/configuration.h"
+#include "starboard/configuration_constants.h"
#include "starboard/mutex.h"
// Define STARBOARD_IMPLEMENTATION to allow inclusion of an internal Starboard
// header. This is "okay" because dlmalloc is essentially an implementation
@@ -132,7 +133,7 @@
#define DEFAULT_MMAP_THRESHOLD SB_DEFAULT_MMAP_THRESHOLD
#endif
-#define MALLOC_ALIGNMENT SB_MALLOC_ALIGNMENT
+#define MALLOC_ALIGNMENT kSbMallocAlignment
#define FORCEINLINE SB_C_FORCE_INLINE
#define NOINLINE SB_C_NOINLINE
#define LACKS_UNISTD_H 1
diff --git a/src/third_party/google_benchmark/.clang-format b/src/third_party/google_benchmark/.clang-format
new file mode 100644
index 0000000..e7d00fe
--- /dev/null
+++ b/src/third_party/google_benchmark/.clang-format
@@ -0,0 +1,5 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+PointerAlignment: Left
+...
diff --git a/src/third_party/google_benchmark/.travis-libcxx-setup.sh b/src/third_party/google_benchmark/.travis-libcxx-setup.sh
new file mode 100644
index 0000000..a591743
--- /dev/null
+++ b/src/third_party/google_benchmark/.travis-libcxx-setup.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# Install a newer CMake version
+curl -sSL https://cmake.org/files/v3.6/cmake-3.6.1-Linux-x86_64.sh -o install-cmake.sh
+chmod +x install-cmake.sh
+sudo ./install-cmake.sh --prefix=/usr/local --skip-license
+
+# Checkout LLVM sources
+git clone --depth=1 https://github.com/llvm-mirror/llvm.git llvm-source
+git clone --depth=1 https://github.com/llvm-mirror/libcxx.git llvm-source/projects/libcxx
+git clone --depth=1 https://github.com/llvm-mirror/libcxxabi.git llvm-source/projects/libcxxabi
+
+# Setup libc++ options
+if [ -z "$BUILD_32_BITS" ]; then
+ export BUILD_32_BITS=OFF && echo disabling 32 bit build
+fi
+
+# Build and install libc++ (Use unstable ABI for better sanitizer coverage)
+mkdir llvm-build && cd llvm-build
+cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr \
+ -DLIBCXX_ABI_UNSTABLE=ON \
+ -DLLVM_USE_SANITIZER=${LIBCXX_SANITIZER} \
+ -DLLVM_BUILD_32_BITS=${BUILD_32_BITS} \
+ ../llvm-source
+make cxx -j2
+sudo make install-cxxabi install-cxx
+cd ../
diff --git a/src/third_party/google_benchmark/.travis.yml b/src/third_party/google_benchmark/.travis.yml
new file mode 100644
index 0000000..6b6cfc7
--- /dev/null
+++ b/src/third_party/google_benchmark/.travis.yml
@@ -0,0 +1,235 @@
+sudo: required
+dist: trusty
+language: cpp
+
+env:
+ global:
+ - /usr/local/bin:$PATH
+
+matrix:
+ include:
+ - compiler: gcc
+ addons:
+ apt:
+ packages:
+ - lcov
+ env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Coverage
+ - compiler: gcc
+ env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Debug
+ - compiler: gcc
+ env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Release
+ - compiler: gcc
+ addons:
+ apt:
+ packages:
+ - g++-multilib
+ - libc6:i386
+ env:
+ - COMPILER=g++
+ - C_COMPILER=gcc
+ - BUILD_TYPE=Debug
+ - BUILD_32_BITS=ON
+ - EXTRA_FLAGS="-m32"
+ - compiler: gcc
+ addons:
+ apt:
+ packages:
+ - g++-multilib
+ - libc6:i386
+ env:
+ - COMPILER=g++
+ - C_COMPILER=gcc
+ - BUILD_TYPE=Release
+ - BUILD_32_BITS=ON
+ - EXTRA_FLAGS="-m32"
+ - compiler: gcc
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=g++-6 C_COMPILER=gcc-6 BUILD_TYPE=Debug
+ - ENABLE_SANITIZER=1
+ - EXTRA_FLAGS="-fno-omit-frame-pointer -g -O2 -fsanitize=undefined,address -fuse-ld=gold"
+ - compiler: clang
+ env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Debug
+ - compiler: clang
+ env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Release
+ # Clang w/ libc++
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ clang-3.8
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+ - LIBCXX_BUILD=1
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ clang-3.8
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release
+ - LIBCXX_BUILD=1
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ # Clang w/ 32bit libc++
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ - clang-3.8
+ - g++-multilib
+ - libc6:i386
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+ - LIBCXX_BUILD=1
+ - BUILD_32_BITS=ON
+ - EXTRA_FLAGS="-m32"
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ # Clang w/ 32bit libc++
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ - clang-3.8
+ - g++-multilib
+ - libc6:i386
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release
+ - LIBCXX_BUILD=1
+ - BUILD_32_BITS=ON
+ - EXTRA_FLAGS="-m32"
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ # Clang w/ libc++, ASAN, UBSAN
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ clang-3.8
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+ - LIBCXX_BUILD=1 LIBCXX_SANITIZER="Undefined;Address"
+ - ENABLE_SANITIZER=1
+ - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=undefined,address -fno-sanitize-recover=all"
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ - UBSAN_OPTIONS=print_stacktrace=1
+ # Clang w/ libc++ and MSAN
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ clang-3.8
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug
+ - LIBCXX_BUILD=1 LIBCXX_SANITIZER=MemoryWithOrigins
+ - ENABLE_SANITIZER=1
+ - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins"
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ # Clang w/ libc++ and MSAN
+ - compiler: clang
+ dist: xenial
+ addons:
+ apt:
+ packages:
+ clang-3.8
+ env:
+ - INSTALL_GCC6_FROM_PPA=1
+ - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=RelWithDebInfo
+ - LIBCXX_BUILD=1 LIBCXX_SANITIZER=Thread
+ - ENABLE_SANITIZER=1
+ - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=thread -fno-sanitize-recover=all"
+ - EXTRA_CXX_FLAGS="-stdlib=libc++"
+ - os: osx
+ osx_image: xcode8.3
+ compiler: clang
+ env:
+ - COMPILER=clang++ BUILD_TYPE=Debug
+ - os: osx
+ osx_image: xcode8.3
+ compiler: clang
+ env:
+ - COMPILER=clang++ BUILD_TYPE=Release
+ - os: osx
+ osx_image: xcode8.3
+ compiler: clang
+ env:
+ - COMPILER=clang++
+ - BUILD_TYPE=Release
+ - BUILD_32_BITS=ON
+ - EXTRA_FLAGS="-m32"
+ - os: osx
+ osx_image: xcode8.3
+ compiler: gcc
+ env:
+ - COMPILER=g++-7 C_COMPILER=gcc-7 BUILD_TYPE=Debug
+
+before_script:
+ - if [ -n "${LIBCXX_BUILD}" ]; then
+ source .travis-libcxx-setup.sh;
+ fi
+ - if [ -n "${ENABLE_SANITIZER}" ]; then
+ export EXTRA_OPTIONS="-DBENCHMARK_ENABLE_ASSEMBLY_TESTS=OFF";
+ else
+ export EXTRA_OPTIONS="";
+ fi
+ - mkdir -p build && cd build
+
+before_install:
+ - if [ -z "$BUILD_32_BITS" ]; then
+ export BUILD_32_BITS=OFF && echo disabling 32 bit build;
+ fi
+ - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
+ sudo add-apt-repository -y "ppa:ubuntu-toolchain-r/test";
+ sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60";
+ fi
+
+install:
+ - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then
+ travis_wait sudo -E apt-get -yq --no-install-suggests --no-install-recommends install g++-6;
+ fi
+ - if [ "${TRAVIS_OS_NAME}" == "linux" -a "${BUILD_32_BITS}" == "OFF" ]; then
+ travis_wait sudo -E apt-get -y --no-install-suggests --no-install-recommends install llvm-3.9-tools;
+ sudo cp /usr/lib/llvm-3.9/bin/FileCheck /usr/local/bin/;
+ fi
+ - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
+ PATH=~/.local/bin:${PATH};
+ pip install --user --upgrade pip;
+ travis_wait pip install --user cpp-coveralls;
+ fi
+ - if [ "${C_COMPILER}" == "gcc-7" -a "${TRAVIS_OS_NAME}" == "osx" ]; then
+ rm -f /usr/local/include/c++;
+ brew update;
+ travis_wait brew install gcc@7;
+ fi
+ - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+ sudo apt-get update -qq;
+ sudo apt-get install -qq unzip cmake3;
+ wget https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-linux-x86_64.sh --output-document bazel-installer.sh;
+ travis_wait sudo bash bazel-installer.sh;
+ fi
+ - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
+ curl -L -o bazel-installer.sh https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-darwin-x86_64.sh;
+ travis_wait sudo bash bazel-installer.sh;
+ fi
+
+script:
+ - cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_C_FLAGS="${EXTRA_FLAGS}" -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} ${EXTRA_CXX_FLAGS}" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_BUILD_32_BITS=${BUILD_32_BITS} ${EXTRA_OPTIONS} ..
+ - make
+ - ctest -C ${BUILD_TYPE} --output-on-failure
+ - bazel test -c dbg --define google_benchmark.have_regex=posix --announce_rc --verbose_failures --test_output=errors --keep_going //test/...
+
+after_success:
+ - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then
+ coveralls --include src --include include --gcov-options '\-lp' --root .. --build-root .;
+ fi
diff --git a/src/third_party/google_benchmark/.ycm_extra_conf.py b/src/third_party/google_benchmark/.ycm_extra_conf.py
new file mode 100644
index 0000000..5649ddc
--- /dev/null
+++ b/src/third_party/google_benchmark/.ycm_extra_conf.py
@@ -0,0 +1,115 @@
+import os
+import ycm_core
+
+# These are the compilation flags that will be used in case there's no
+# compilation database set (by default, one is not set).
+# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
+flags = [
+'-Wall',
+'-Werror',
+'-pedantic-errors',
+'-std=c++0x',
+'-fno-strict-aliasing',
+'-O3',
+'-DNDEBUG',
+# ...and the same thing goes for the magic -x option which specifies the
+# language that the files to be compiled are written in. This is mostly
+# relevant for c++ headers.
+# For a C project, you would set this to 'c' instead of 'c++'.
+'-x', 'c++',
+'-I', 'include',
+'-isystem', '/usr/include',
+'-isystem', '/usr/local/include',
+]
+
+
+# Set this to the absolute path to the folder (NOT the file!) containing the
+# compile_commands.json file to use that instead of 'flags'. See here for
+# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
+#
+# Most projects will NOT need to set this to anything; you can just change the
+# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
+compilation_database_folder = ''
+
+if os.path.exists( compilation_database_folder ):
+ database = ycm_core.CompilationDatabase( compilation_database_folder )
+else:
+ database = None
+
+SOURCE_EXTENSIONS = [ '.cc' ]
+
+def DirectoryOfThisScript():
+ return os.path.dirname( os.path.abspath( __file__ ) )
+
+
+def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
+ if not working_directory:
+ return list( flags )
+ new_flags = []
+ make_next_absolute = False
+ path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
+ for flag in flags:
+ new_flag = flag
+
+ if make_next_absolute:
+ make_next_absolute = False
+ if not flag.startswith( '/' ):
+ new_flag = os.path.join( working_directory, flag )
+
+ for path_flag in path_flags:
+ if flag == path_flag:
+ make_next_absolute = True
+ break
+
+ if flag.startswith( path_flag ):
+ path = flag[ len( path_flag ): ]
+ new_flag = path_flag + os.path.join( working_directory, path )
+ break
+
+ if new_flag:
+ new_flags.append( new_flag )
+ return new_flags
+
+
+def IsHeaderFile( filename ):
+ extension = os.path.splitext( filename )[ 1 ]
+ return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
+
+
+def GetCompilationInfoForFile( filename ):
+ # The compilation_commands.json file generated by CMake does not have entries
+ # for header files. So we do our best by asking the db for flags for a
+ # corresponding source file, if any. If one exists, the flags for that file
+ # should be good enough.
+ if IsHeaderFile( filename ):
+ basename = os.path.splitext( filename )[ 0 ]
+ for extension in SOURCE_EXTENSIONS:
+ replacement_file = basename + extension
+ if os.path.exists( replacement_file ):
+ compilation_info = database.GetCompilationInfoForFile(
+ replacement_file )
+ if compilation_info.compiler_flags_:
+ return compilation_info
+ return None
+ return database.GetCompilationInfoForFile( filename )
+
+
+def FlagsForFile( filename, **kwargs ):
+ if database:
+ # Bear in mind that compilation_info.compiler_flags_ does NOT return a
+ # python list, but a "list-like" StringVec object
+ compilation_info = GetCompilationInfoForFile( filename )
+ if not compilation_info:
+ return None
+
+ final_flags = MakeRelativePathsInFlagsAbsolute(
+ compilation_info.compiler_flags_,
+ compilation_info.compiler_working_dir_ )
+ else:
+ relative_to = DirectoryOfThisScript()
+ final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
+
+ return {
+ 'flags': final_flags,
+ 'do_cache': True
+ }
diff --git a/src/third_party/google_benchmark/AUTHORS b/src/third_party/google_benchmark/AUTHORS
new file mode 100644
index 0000000..35c4c8c
--- /dev/null
+++ b/src/third_party/google_benchmark/AUTHORS
@@ -0,0 +1,54 @@
+# This is the official list of benchmark authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+#
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Alex Steele <steeleal123@gmail.com>
+Andriy Berestovskyy <berestovskyy@gmail.com>
+Arne Beer <arne@twobeer.de>
+Carto
+Christopher Seymour <chris.j.seymour@hotmail.com>
+Colin Braley <braley.colin@gmail.com>
+Daniel Harvey <danielharvey458@gmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Deniz Evrenci <denizevrenci@gmail.com>
+Dirac Research
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Backus <eric_backus@alum.mit.edu>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Federico Ficarelli <federico.ficarelli@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Google Inc.
+International Business Machines Corporation
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Kishan Kumar <kumar.kishan@outlook.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+MongoDB Inc.
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Ori Livneh <ori.livneh@gmail.com>
+Paul Redmond <paul.redmond@gmail.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Roman Lebedev <lebedev.ri@gmail.com>
+Sayan Bhattacharjee <aero.sayan@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Steinar H. Gunderson <sgunderson@bigfoot.com>
+Stripe, Inc.
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
diff --git a/src/third_party/google_benchmark/BUILD.bazel b/src/third_party/google_benchmark/BUILD.bazel
new file mode 100644
index 0000000..d97a019
--- /dev/null
+++ b/src/third_party/google_benchmark/BUILD.bazel
@@ -0,0 +1,44 @@
+licenses(["notice"])
+
+config_setting(
+ name = "windows",
+ values = {
+ "cpu": "x64_windows",
+ },
+ visibility = [":__subpackages__"],
+)
+
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+cc_library(
+ name = "benchmark",
+ srcs = glob(
+ [
+ "src/*.cc",
+ "src/*.h",
+ ],
+ exclude = ["src/benchmark_main.cc"],
+ ),
+ hdrs = ["include/benchmark/benchmark.h"],
+ linkopts = select({
+ ":windows": ["-DEFAULTLIB:shlwapi.lib"],
+ "//conditions:default": ["-pthread"],
+ }),
+ strip_include_prefix = "include",
+ visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "benchmark_main",
+ srcs = ["src/benchmark_main.cc"],
+ hdrs = ["include/benchmark/benchmark.h"],
+ strip_include_prefix = "include",
+ visibility = ["//visibility:public"],
+ deps = [":benchmark"],
+)
+
+cc_library(
+ name = "benchmark_internal_headers",
+ hdrs = glob(["src/*.h"]),
+ visibility = ["//test:__pkg__"],
+)
diff --git a/src/third_party/google_benchmark/CMakeLists.txt b/src/third_party/google_benchmark/CMakeLists.txt
new file mode 100644
index 0000000..8cfe125
--- /dev/null
+++ b/src/third_party/google_benchmark/CMakeLists.txt
@@ -0,0 +1,277 @@
+cmake_minimum_required (VERSION 3.5.1)
+
+foreach(p
+ CMP0048 # OK to clear PROJECT_VERSION on project()
+ CMP0054 # CMake 3.1
+ CMP0056 # export EXE_LINKER_FLAGS to try_run
+ CMP0057 # Support no if() IN_LIST operator
+ CMP0063 # Honor visibility properties for all targets
+ )
+ if(POLICY ${p})
+ cmake_policy(SET ${p} NEW)
+ endif()
+endforeach()
+
+project (benchmark CXX)
+
+option(BENCHMARK_ENABLE_TESTING "Enable testing of the benchmark library." ON)
+option(BENCHMARK_ENABLE_EXCEPTIONS "Enable the use of exceptions in the benchmark library." ON)
+option(BENCHMARK_ENABLE_LTO "Enable link time optimisation of the benchmark library." OFF)
+option(BENCHMARK_USE_LIBCXX "Build and test using libc++ as the standard library." OFF)
+if(NOT MSVC)
+ option(BENCHMARK_BUILD_32_BITS "Build a 32 bit version of the library." OFF)
+else()
+ set(BENCHMARK_BUILD_32_BITS OFF CACHE BOOL "Build a 32 bit version of the library - unsupported when using MSVC)" FORCE)
+endif()
+option(BENCHMARK_ENABLE_INSTALL "Enable installation of benchmark. (Projects embedding benchmark may want to turn this OFF.)" ON)
+
+# Allow unmet dependencies to be met using CMake's ExternalProject mechanics, which
+# may require downloading the source code.
+option(BENCHMARK_DOWNLOAD_DEPENDENCIES "Allow the downloading and in-tree building of unmet dependencies" OFF)
+
+# This option can be used to disable building and running unit tests which depend on gtest
+# in cases where it is not possible to build or find a valid version of gtest.
+option(BENCHMARK_ENABLE_GTEST_TESTS "Enable building the unit tests which depend on gtest" ON)
+
+set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+set(ENABLE_ASSEMBLY_TESTS_DEFAULT OFF)
+function(should_enable_assembly_tests)
+ if(CMAKE_BUILD_TYPE)
+ string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER)
+ if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage")
+ # FIXME: The --coverage flag needs to be removed when building assembly
+ # tests for this to work.
+ return()
+ endif()
+ endif()
+ if (MSVC)
+ return()
+ elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ return()
+ elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
+ # FIXME: Make these work on 32 bit builds
+ return()
+ elseif(BENCHMARK_BUILD_32_BITS)
+ # FIXME: Make these work on 32 bit builds
+ return()
+ endif()
+ find_program(LLVM_FILECHECK_EXE FileCheck)
+ if (LLVM_FILECHECK_EXE)
+ set(LLVM_FILECHECK_EXE "${LLVM_FILECHECK_EXE}" CACHE PATH "llvm filecheck" FORCE)
+ message(STATUS "LLVM FileCheck Found: ${LLVM_FILECHECK_EXE}")
+ else()
+ message(STATUS "Failed to find LLVM FileCheck")
+ return()
+ endif()
+ set(ENABLE_ASSEMBLY_TESTS_DEFAULT ON PARENT_SCOPE)
+endfunction()
+should_enable_assembly_tests()
+
+# This option disables the building and running of the assembly verification tests
+option(BENCHMARK_ENABLE_ASSEMBLY_TESTS "Enable building and running the assembly tests"
+ ${ENABLE_ASSEMBLY_TESTS_DEFAULT})
+
+# Make sure we can import out CMake functions
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+
+# Read the git tags to determine the project version
+include(GetGitVersion)
+get_git_version(GIT_VERSION)
+
+# Tell the user what versions we are using
+string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" VERSION ${GIT_VERSION})
+message(STATUS "Version: ${VERSION}")
+
+# The version of the libraries
+set(GENERIC_LIB_VERSION ${VERSION})
+string(SUBSTRING ${VERSION} 0 1 GENERIC_LIB_SOVERSION)
+
+# Import our CMake modules
+include(CheckCXXCompilerFlag)
+include(AddCXXCompilerFlag)
+include(CXXFeatureCheck)
+
+if (BENCHMARK_BUILD_32_BITS)
+ add_required_cxx_compiler_flag(-m32)
+endif()
+
+if (MSVC)
+ # Turn compiler warnings up to 11
+ string(REGEX REPLACE "[-/]W[1-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+
+ if (NOT BENCHMARK_ENABLE_EXCEPTIONS)
+ add_cxx_compiler_flag(-EHs-)
+ add_cxx_compiler_flag(-EHa-)
+ add_definitions(-D_HAS_EXCEPTIONS=0)
+ endif()
+ # Link time optimisation
+ if (BENCHMARK_ENABLE_LTO)
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")
+ set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG")
+ set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG")
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
+
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL")
+ string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}")
+ set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}")
+ set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
+ set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+
+ set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /GL")
+ set(CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL "${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL} /LTCG")
+ set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL} /LTCG")
+ set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /LTCG")
+ endif()
+else()
+ # Try and enable C++11. Don't use C++14 because it doesn't work in some
+ # configurations.
+ add_cxx_compiler_flag(-std=c++11)
+ if (NOT HAVE_CXX_FLAG_STD_CXX11)
+ add_cxx_compiler_flag(-std=c++0x)
+ endif()
+
+ # Turn compiler warnings up to 11
+ add_cxx_compiler_flag(-Wall)
+ add_cxx_compiler_flag(-Wextra)
+ add_cxx_compiler_flag(-Wshadow)
+ add_cxx_compiler_flag(-Werror RELEASE)
+ add_cxx_compiler_flag(-Werror RELWITHDEBINFO)
+ add_cxx_compiler_flag(-Werror MINSIZEREL)
+ add_cxx_compiler_flag(-pedantic)
+ add_cxx_compiler_flag(-pedantic-errors)
+ add_cxx_compiler_flag(-Wshorten-64-to-32)
+ add_cxx_compiler_flag(-fstrict-aliasing)
+ # Disable warnings regarding deprecated parts of the library while building
+ # and testing those parts of the library.
+ add_cxx_compiler_flag(-Wno-deprecated-declarations)
+ if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+ # Intel silently ignores '-Wno-deprecated-declarations',
+ # warning no. 1786 must be explicitly disabled.
+ # See #631 for rationale.
+ add_cxx_compiler_flag(-wd1786)
+ endif()
+ # Disable deprecation warnings for release builds (when -Werror is enabled).
+ add_cxx_compiler_flag(-Wno-deprecated RELEASE)
+ add_cxx_compiler_flag(-Wno-deprecated RELWITHDEBINFO)
+ add_cxx_compiler_flag(-Wno-deprecated MINSIZEREL)
+ if (NOT BENCHMARK_ENABLE_EXCEPTIONS)
+ add_cxx_compiler_flag(-fno-exceptions)
+ endif()
+
+ if (HAVE_CXX_FLAG_FSTRICT_ALIASING)
+ if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") #ICC17u2: Many false positives for Wstrict-aliasing
+ add_cxx_compiler_flag(-Wstrict-aliasing)
+ endif()
+ endif()
+ # ICC17u2: overloaded virtual function "benchmark::Fixture::SetUp" is only partially overridden
+ # (because of deprecated overload)
+ add_cxx_compiler_flag(-wd654)
+ add_cxx_compiler_flag(-Wthread-safety)
+ if (HAVE_CXX_FLAG_WTHREAD_SAFETY)
+ cxx_feature_check(THREAD_SAFETY_ATTRIBUTES)
+ endif()
+
+ # On most UNIX like platforms g++ and clang++ define _GNU_SOURCE as a
+ # predefined macro, which turns on all of the wonderful libc extensions.
+ # However g++ doesn't do this in Cygwin so we have to define it ourselfs
+ # since we depend on GNU/POSIX/BSD extensions.
+ if (CYGWIN)
+ add_definitions(-D_GNU_SOURCE=1)
+ endif()
+
+ if (QNXNTO)
+ add_definitions(-D_QNX_SOURCE)
+ endif()
+
+ # Link time optimisation
+ if (BENCHMARK_ENABLE_LTO)
+ add_cxx_compiler_flag(-flto)
+ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ find_program(GCC_AR gcc-ar)
+ if (GCC_AR)
+ set(CMAKE_AR ${GCC_AR})
+ endif()
+ find_program(GCC_RANLIB gcc-ranlib)
+ if (GCC_RANLIB)
+ set(CMAKE_RANLIB ${GCC_RANLIB})
+ endif()
+ elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+ include(llvm-toolchain)
+ endif()
+ endif()
+
+ # Coverage build type
+ set(BENCHMARK_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG}"
+ CACHE STRING "Flags used by the C++ compiler during coverage builds."
+ FORCE)
+ set(BENCHMARK_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG}"
+ CACHE STRING "Flags used for linking binaries during coverage builds."
+ FORCE)
+ set(BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}"
+ CACHE STRING "Flags used by the shared libraries linker during coverage builds."
+ FORCE)
+ mark_as_advanced(
+ BENCHMARK_CXX_FLAGS_COVERAGE
+ BENCHMARK_EXE_LINKER_FLAGS_COVERAGE
+ BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE)
+ set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
+ "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage.")
+ add_cxx_compiler_flag(--coverage COVERAGE)
+endif()
+
+if (BENCHMARK_USE_LIBCXX)
+ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+ add_cxx_compiler_flag(-stdlib=libc++)
+ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR
+ "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
+ add_cxx_compiler_flag(-nostdinc++)
+ message(WARNING "libc++ header path must be manually specified using CMAKE_CXX_FLAGS")
+ # Adding -nodefaultlibs directly to CMAKE_<TYPE>_LINKER_FLAGS will break
+ # configuration checks such as 'find_package(Threads)'
+ list(APPEND BENCHMARK_CXX_LINKER_FLAGS -nodefaultlibs)
+ # -lc++ cannot be added directly to CMAKE_<TYPE>_LINKER_FLAGS because
+ # linker flags appear before all linker inputs and -lc++ must appear after.
+ list(APPEND BENCHMARK_CXX_LIBRARIES c++)
+ else()
+ message(FATAL_ERROR "-DBENCHMARK_USE_LIBCXX:BOOL=ON is not supported for compiler")
+ endif()
+endif(BENCHMARK_USE_LIBCXX)
+
+# C++ feature checks
+# Determine the correct regular expression engine to use
+cxx_feature_check(STD_REGEX)
+cxx_feature_check(GNU_POSIX_REGEX)
+cxx_feature_check(POSIX_REGEX)
+if(NOT HAVE_STD_REGEX AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX)
+ message(FATAL_ERROR "Failed to determine the source files for the regular expression backend")
+endif()
+if (NOT BENCHMARK_ENABLE_EXCEPTIONS AND HAVE_STD_REGEX
+ AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX)
+ message(WARNING "Using std::regex with exceptions disabled is not fully supported")
+endif()
+cxx_feature_check(STEADY_CLOCK)
+# Ensure we have pthreads
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
+
+# Set up directories
+include_directories(${PROJECT_SOURCE_DIR}/include)
+
+# Build the targets
+add_subdirectory(src)
+
+if (BENCHMARK_ENABLE_TESTING)
+ enable_testing()
+ if (BENCHMARK_ENABLE_GTEST_TESTS AND
+ NOT (TARGET gtest AND TARGET gtest_main AND
+ TARGET gmock AND TARGET gmock_main))
+ include(GoogleTest)
+ endif()
+ add_subdirectory(test)
+endif()
diff --git a/src/third_party/google_benchmark/CONTRIBUTING.md b/src/third_party/google_benchmark/CONTRIBUTING.md
new file mode 100644
index 0000000..43de4c9
--- /dev/null
+++ b/src/third_party/google_benchmark/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+# How to contribute #
+
+We'd love to accept your patches and contributions to this project. There are
+a just a few small guidelines you need to follow.
+
+
+## Contributor License Agreement ##
+
+Contributions to any Google project must be accompanied by a Contributor
+License Agreement. This is not a copyright **assignment**, it simply gives
+Google permission to use and redistribute your contributions as part of the
+project.
+
+ * If you are an individual writing original source code and you're sure you
+ own the intellectual property, then you'll need to sign an [individual
+ CLA][].
+
+ * If you work for a company that wants to allow you to contribute your work,
+ then you'll need to sign a [corporate CLA][].
+
+You generally only need to submit a CLA once, so if you've already submitted
+one (even if it was for a different project), you probably don't need to do it
+again.
+
+[individual CLA]: https://developers.google.com/open-source/cla/individual
+[corporate CLA]: https://developers.google.com/open-source/cla/corporate
+
+Once your CLA is submitted (or if you already submitted one for
+another Google project), make a commit adding yourself to the
+[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part
+of your first [pull request][].
+
+[AUTHORS]: AUTHORS
+[CONTRIBUTORS]: CONTRIBUTORS
+
+
+## Submitting a patch ##
+
+ 1. It's generally best to start by opening a new issue describing the bug or
+ feature you're intending to fix. Even if you think it's relatively minor,
+ it's helpful to know what people are working on. Mention in the initial
+ issue that you are planning to work on that bug or feature so that it can
+ be assigned to you.
+
+ 1. Follow the normal process of [forking][] the project, and setup a new
+ branch to work in. It's important that each group of changes be done in
+ separate branches in order to ensure that a pull request only includes the
+ commits related to that bug or feature.
+
+ 1. Do your best to have [well-formed commit messages][] for each change.
+ This provides consistency throughout the project, and ensures that commit
+ messages are able to be formatted properly by various git tools.
+
+ 1. Finally, push the commits to your fork and submit a [pull request][].
+
+[forking]: https://help.github.com/articles/fork-a-repo
+[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+[pull request]: https://help.github.com/articles/creating-a-pull-request
diff --git a/src/third_party/google_benchmark/CONTRIBUTORS b/src/third_party/google_benchmark/CONTRIBUTORS
new file mode 100644
index 0000000..6b64a00
--- /dev/null
+++ b/src/third_party/google_benchmark/CONTRIBUTORS
@@ -0,0 +1,76 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# Names should be added to this file only after verifying that
+# the individual or the individual's organization has agreed to
+# the appropriate Contributor License Agreement, found here:
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# The agreement for individuals can be filled out on the web.
+#
+# When adding J Random Contributor's name to this file,
+# either J's name or J's organization's name should be
+# added to the AUTHORS file, depending on whether the
+# individual or corporate CLA was used.
+#
+# Names should be added to this file as:
+# Name <email address>
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Alex Steele <steelal123@gmail.com>
+Andriy Berestovskyy <berestovskyy@gmail.com>
+Arne Beer <arne@twobeer.de>
+Billy Robert O'Neal III <billy.oneal@gmail.com> <bion@microsoft.com>
+Chris Kennelly <ckennelly@google.com> <ckennelly@ckennelly.com>
+Christopher Seymour <chris.j.seymour@hotmail.com>
+Colin Braley <braley.colin@gmail.com>
+Cyrille Faucheux <cyrille.faucheux@gmail.com>
+Daniel Harvey <danielharvey458@gmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Deniz Evrenci <denizevrenci@gmail.com>
+Dominic Hamon <dma@stripysock.com> <dominic@google.com>
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Backus <eric_backus@alum.mit.edu>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Federico Ficarelli <federico.ficarelli@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Geoffrey Martin-Noble <gcmn@google.com> <gmngeoffrey@gmail.com>
+Hannes Hauswedell <h2@fsfe.org>
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+John Millikin <jmillikin@stripe.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kai Wolf <kai.wolf@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Kishan Kumar <kumar.kishan@outlook.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Ori Livneh <ori.livneh@gmail.com>
+Pascal Leroy <phl@google.com>
+Paul Redmond <paul.redmond@gmail.com>
+Pierre Phaneuf <pphaneuf@google.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Raul Marin <rmrodriguez@cartodb.com>
+Ray Glover <ray.glover@uk.ibm.com>
+Robert Guo <robert.guo@mongodb.com>
+Roman Lebedev <lebedev.ri@gmail.com>
+Sayan Bhattacharjee <aero.sayan@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Tobias Ulvgård <tobias.ulvgard@dirac.se>
+Tom Madams <tom.ej.madams@gmail.com> <tmadams@google.com>
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
diff --git a/src/third_party/google_benchmark/LICENSE b/src/third_party/google_benchmark/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/src/third_party/google_benchmark/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/src/third_party/google_benchmark/README.md b/src/third_party/google_benchmark/README.md
new file mode 100644
index 0000000..d972ab0
--- /dev/null
+++ b/src/third_party/google_benchmark/README.md
@@ -0,0 +1,1270 @@
+# Benchmark
+
+[](https://travis-ci.org/google/benchmark)
+[](https://ci.appveyor.com/project/google/benchmark/branch/master)
+[](https://coveralls.io/r/google/benchmark)
+[](https://slackin-iqtfqnpzxd.now.sh/)
+
+A library to benchmark code snippets, similar to unit tests. Example:
+
+```c++
+#include <benchmark/benchmark.h>
+
+static void BM_SomeFunction(benchmark::State& state) {
+ // Perform setup here
+ for (auto _ : state) {
+ // This code gets timed
+ SomeFunction();
+ }
+}
+// Register the function as a benchmark
+BENCHMARK(BM_SomeFunction);
+// Run the benchmark
+BENCHMARK_MAIN();
+```
+
+To get started, see [Requirements](#requirements) and
+[Installation](#installation). See [Usage](#usage) for a full example and the
+[User Guide](#user-guide) for a more comprehensive feature overview.
+
+It may also help to read the [Google Test documentation](https://github.com/google/googletest/blob/master/googletest/docs/primer.md)
+as some of the structural aspects of the APIs are similar.
+
+### Resources
+
+[Discussion group](https://groups.google.com/d/forum/benchmark-discuss)
+
+IRC channel: [freenode](https://freenode.net) #googlebenchmark
+
+[Additional Tooling Documentation](docs/tools.md)
+
+[Assembly Testing Documentation](docs/AssemblyTests.md)
+
+## Requirements
+
+The library can be used with C++03. However, it requires C++11 to build,
+including compiler and standard library support.
+
+The following minimum versions are required to build the library:
+
+* GCC 4.8
+* Clang 3.4
+* Visual Studio 14 2015
+* Intel 2015 Update 1
+
+See [Platform-Specific Build Instructions](#platform-specific-build-instructions).
+
+## Installation
+
+This describes the installation process using cmake. As pre-requisites, you'll
+need git and cmake installed.
+
+_See [dependencies.md](dependencies.md) for more details regarding supported
+versions of build tools._
+
+```bash
+# Check out the library.
+$ git clone https://github.com/google/benchmark.git
+# Benchmark requires Google Test as a dependency. Add the source tree as a subdirectory.
+$ git clone https://github.com/google/googletest.git benchmark/googletest
+# Go to the library root directory
+$ cd benchmark
+# Make a build directory to place the build output.
+$ mkdir build && cd build
+# Generate a Makefile with cmake.
+# Use cmake -G <generator> to generate a different file type.
+$ cmake ../
+# Build the library.
+# Use make -j<number_of_parallel_jobs> to speed up the build process, e.g. make -j8 .
+$ make
+```
+This builds the `benchmark` and `benchmark_main` libraries and tests.
+On a unix system, the build directory should now look something like this:
+
+```
+/benchmark
+ /build
+ /src
+ /libbenchmark.a
+ /libbenchmark_main.a
+ /test
+ ...
+```
+
+Next, you can run the tests to check the build.
+
+```bash
+$ make test
+```
+
+If you want to install the library globally, also run:
+
+```
+sudo make install
+```
+
+Note that Google Benchmark requires Google Test to build and run the tests. This
+dependency can be provided two ways:
+
+* Checkout the Google Test sources into `benchmark/googletest` as above.
+* Otherwise, if `-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON` is specified during
+ configuration, the library will automatically download and build any required
+ dependencies.
+
+If you do not wish to build and run the tests, add `-DBENCHMARK_ENABLE_GTEST_TESTS=OFF`
+to `CMAKE_ARGS`.
+
+### Debug vs Release
+
+By default, benchmark builds as a debug library. You will see a warning in the
+output when this is the case. To build it as a release library instead, use:
+
+```
+cmake -DCMAKE_BUILD_TYPE=Release
+```
+
+To enable link-time optimisation, use
+
+```
+cmake -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_LTO=true
+```
+
+If you are using gcc, you might need to set `GCC_AR` and `GCC_RANLIB` cmake
+cache variables, if autodetection fails.
+
+If you are using clang, you may need to set `LLVMAR_EXECUTABLE`,
+`LLVMNM_EXECUTABLE` and `LLVMRANLIB_EXECUTABLE` cmake cache variables.
+
+
+### Stable and Experimental Library Versions
+
+The main branch contains the latest stable version of the benchmarking library;
+the API of which can be considered largely stable, with source breaking changes
+being made only upon the release of a new major version.
+
+Newer, experimental, features are implemented and tested on the
+[`v2` branch](https://github.com/google/benchmark/tree/v2). Users who wish
+to use, test, and provide feedback on the new features are encouraged to try
+this branch. However, this branch provides no stability guarantees and reserves
+the right to change and break the API at any time.
+
+## Usage
+
+### Basic usage
+
+Define a function that executes the code to measure, register it as a benchmark
+function using the `BENCHMARK` macro, and ensure an appropriate `main` function
+is available:
+
+```c++
+#include <benchmark/benchmark.h>
+
+static void BM_StringCreation(benchmark::State& state) {
+ for (auto _ : state)
+ std::string empty_string;
+}
+// Register the function as a benchmark
+BENCHMARK(BM_StringCreation);
+
+// Define another benchmark
+static void BM_StringCopy(benchmark::State& state) {
+ std::string x = "hello";
+ for (auto _ : state)
+ std::string copy(x);
+}
+BENCHMARK(BM_StringCopy);
+
+BENCHMARK_MAIN();
+```
+
+To run the benchmark, compile and link against the `benchmark` library
+(libbenchmark.a/.so). If you followed the build steps above, this
+library will be under the build directory you created.
+
+```bash
+# Example on linux after running the build steps above. Assumes the
+# `benchmark` and `build` directories are under the current directory.
+$ g++ mybenchmark.cc -std=c++11 -isystem benchmark/include \
+ -Lbenchmark/build/src -lbenchmark -lpthread -o mybenchmark
+```
+
+Alternatively, link against the `benchmark_main` library and remove
+`BENCHMARK_MAIN();` above to get the same behavior.
+
+The compiled executable will run all benchmarks by default. Pass the `--help`
+flag for option information or see the guide below.
+
+## Platform Specific Build Instructions
+
+### Building with GCC
+
+When the library is built using GCC it is necessary to link with the pthread
+library due to how GCC implements `std::thread`. Failing to link to pthread will
+lead to runtime exceptions (unless you're using libc++), not linker errors. See
+[issue #67](https://github.com/google/benchmark/issues/67) for more details. You
+can link to pthread by adding `-pthread` to your linker command. Note, you can
+also use `-lpthread`, but there are potential issues with ordering of command
+line parameters if you use that.
+
+### Building with Visual Studio 2015 or 2017
+
+The `shlwapi` library (`-lshlwapi`) is required to support a call to `CPUInfo` which reads the registry. Either add `shlwapi.lib` under `[ Configuration Properties > Linker > Input ]`, or use the following:
+
+```
+// Alternatively, can add libraries using linker options.
+#ifdef _WIN32
+#pragma comment ( lib, "Shlwapi.lib" )
+#ifdef _DEBUG
+#pragma comment ( lib, "benchmarkd.lib" )
+#else
+#pragma comment ( lib, "benchmark.lib" )
+#endif
+#endif
+```
+
+Can also use the graphical version of CMake:
+* Open `CMake GUI`.
+* Under `Where to build the binaries`, same path as source plus `build`.
+* Under `CMAKE_INSTALL_PREFIX`, same path as source plus `install`.
+* Click `Configure`, `Generate`, `Open Project`.
+* If build fails, try deleting entire directory and starting again, or unticking options to build less.
+
+### Building with Intel 2015 Update 1 or Intel System Studio Update 4
+
+See instructions for building with Visual Studio. Once built, right click on the solution and change the build to Intel.
+
+### Building on Solaris
+
+If you're running benchmarks on solaris, you'll want the kstat library linked in
+too (`-lkstat`).
+
+## User Guide
+
+### Command Line
+
+[Output Formats](#output-formats)
+
+[Output Files](#output-files)
+
+[Running Benchmarks](#running-benchmarks)
+
+[Running a Subset of Benchmarks](#running-a-subset-of-benchmarks)
+
+[Result Comparison](#result-comparison)
+
+### Library
+
+[Runtime and Reporting Considerations](#runtime-and-reporting-considerations)
+
+[Passing Arguments](#passing-arguments)
+
+[Calculating Asymptotic Complexity](#asymptotic-complexity)
+
+[Templated Benchmarks](#templated-benchmarks)
+
+[Fixtures](#fixtures)
+
+[Custom Counters](#custom-counters)
+
+[Multithreaded Benchmarks](#multithreaded-benchmarks)
+
+[CPU Timers](#cpu-timers)
+
+[Manual Timing](#manual-timing)
+
+[Setting the Time Unit](#setting-the-time-unit)
+
+[Preventing Optimization](#preventing-optimization)
+
+[Reporting Statistics](#reporting-statistics)
+
+[Custom Statistics](#custom-statistics)
+
+[Using RegisterBenchmark](#using-register-benchmark)
+
+[Exiting with an Error](#exiting-with-an-error)
+
+[A Faster KeepRunning Loop](#a-faster-keep-running-loop)
+
+[Disabling CPU Frequency Scaling](#disabling-cpu-frequency-scaling)
+
+
+<a name="output-formats" />
+
+### Output Formats
+
+The library supports multiple output formats. Use the
+`--benchmark_format=<console|json|csv>` flag (or set the
+`BENCHMARK_FORMAT=<console|json|csv>` environment variable) to set
+the format type. `console` is the default format.
+
+The Console format is intended to be a human readable format. By default
+the format generates color output. Context is output on stderr and the
+tabular data on stdout. Example tabular output looks like:
+
+```
+Benchmark Time(ns) CPU(ns) Iterations
+----------------------------------------------------------------------
+BM_SetInsert/1024/1 28928 29349 23853 133.097kB/s 33.2742k items/s
+BM_SetInsert/1024/8 32065 32913 21375 949.487kB/s 237.372k items/s
+BM_SetInsert/1024/10 33157 33648 21431 1.13369MB/s 290.225k items/s
+```
+
+The JSON format outputs human readable json split into two top level attributes.
+The `context` attribute contains information about the run in general, including
+information about the CPU and the date.
+The `benchmarks` attribute contains a list of every benchmark run. Example json
+output looks like:
+
+```json
+{
+ "context": {
+ "date": "2015/03/17-18:40:25",
+ "num_cpus": 40,
+ "mhz_per_cpu": 2801,
+ "cpu_scaling_enabled": false,
+ "build_type": "debug"
+ },
+ "benchmarks": [
+ {
+ "name": "BM_SetInsert/1024/1",
+ "iterations": 94877,
+ "real_time": 29275,
+ "cpu_time": 29836,
+ "bytes_per_second": 134066,
+ "items_per_second": 33516
+ },
+ {
+ "name": "BM_SetInsert/1024/8",
+ "iterations": 21609,
+ "real_time": 32317,
+ "cpu_time": 32429,
+ "bytes_per_second": 986770,
+ "items_per_second": 246693
+ },
+ {
+ "name": "BM_SetInsert/1024/10",
+ "iterations": 21393,
+ "real_time": 32724,
+ "cpu_time": 33355,
+ "bytes_per_second": 1199226,
+ "items_per_second": 299807
+ }
+ ]
+}
+```
+
+The CSV format outputs comma-separated values. The `context` is output on stderr
+and the CSV itself on stdout. Example CSV output looks like:
+
+```
+name,iterations,real_time,cpu_time,bytes_per_second,items_per_second,label
+"BM_SetInsert/1024/1",65465,17890.7,8407.45,475768,118942,
+"BM_SetInsert/1024/8",116606,18810.1,9766.64,3.27646e+06,819115,
+"BM_SetInsert/1024/10",106365,17238.4,8421.53,4.74973e+06,1.18743e+06,
+```
+
+<a name="output-files" />
+
+### Output Files
+
+Write benchmark results to a file with the `--benchmark_out=<filename>` option
+(or set `BENCHMARK_OUT`). Specify the output format with
+`--benchmark_out_format={json|console|csv}` (or set
+`BENCHMARK_OUT_FORMAT={json|console|csv}`). Note that specifying
+`--benchmark_out` does not suppress the console output.
+
+<a name="running-benchmarks" />
+
+### Running Benchmarks
+
+Benchmarks are executed by running the produced binaries. Benchmarks binaries,
+by default, accept options that may be specified either through their command
+line interface or by setting environment variables before execution. For every
+`--option_flag=<value>` CLI switch, a corresponding environment variable
+`OPTION_FLAG=<value>` exist and is used as default if set (CLI switches always
+ prevails). A complete list of CLI options is available running benchmarks
+ with the `--help` switch.
+
+<a name="running-a-subset-of-benchmarks" />
+
+### Running a Subset of Benchmarks
+
+The `--benchmark_filter=<regex>` option (or `BENCHMARK_FILTER=<regex>`
+environment variable) can be used to only run the benchmarks that match
+the specified `<regex>`. For example:
+
+```bash
+$ ./run_benchmarks.x --benchmark_filter=BM_memcpy/32
+Run on (1 X 2300 MHz CPU )
+2016-06-25 19:34:24
+Benchmark Time CPU Iterations
+----------------------------------------------------
+BM_memcpy/32 11 ns 11 ns 79545455
+BM_memcpy/32k 2181 ns 2185 ns 324074
+BM_memcpy/32 12 ns 12 ns 54687500
+BM_memcpy/32k 1834 ns 1837 ns 357143
+```
+
+<a name="result-comparison" />
+
+### Result comparison
+
+It is possible to compare the benchmarking results.
+See [Additional Tooling Documentation](docs/tools.md)
+
+<a name="runtime-and-reporting-considerations" />
+
+### Runtime and Reporting Considerations
+
+When the benchmark binary is executed, each benchmark function is run serially.
+The number of iterations to run is determined dynamically by running the
+benchmark a few times and measuring the time taken and ensuring that the
+ultimate result will be statistically stable. As such, faster benchmark
+functions will be run for more iterations than slower benchmark functions, and
+the number of iterations is thus reported.
+
+In all cases, the number of iterations for which the benchmark is run is
+governed by the amount of time the benchmark takes. Concretely, the number of
+iterations is at least one, not more than 1e9, until CPU time is greater than
+the minimum time, or the wallclock time is 5x minimum time. The minimum time is
+set per benchmark by calling `MinTime` on the registered benchmark object.
+
+Average timings are then reported over the iterations run. If multiple
+repetitions are requested using the `--benchmark_repetitions` command-line
+option, or at registration time, the benchmark function will be run several
+times and statistical results across these repetitions will also be reported.
+
+As well as the per-benchmark entries, a preamble in the report will include
+information about the machine on which the benchmarks are run.
+
+<a name="passing-arguments" />
+
+### Passing Arguments
+
+Sometimes a family of benchmarks can be implemented with just one routine that
+takes an extra argument to specify which one of the family of benchmarks to
+run. For example, the following code defines a family of benchmarks for
+measuring the speed of `memcpy()` calls of different lengths:
+
+```c++
+static void BM_memcpy(benchmark::State& state) {
+ char* src = new char[state.range(0)];
+ char* dst = new char[state.range(0)];
+ memset(src, 'x', state.range(0));
+ for (auto _ : state)
+ memcpy(dst, src, state.range(0));
+ state.SetBytesProcessed(int64_t(state.iterations()) *
+ int64_t(state.range(0)));
+ delete[] src;
+ delete[] dst;
+}
+BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10);
+```
+
+The preceding code is quite repetitive, and can be replaced with the following
+short-hand. The following invocation will pick a few appropriate arguments in
+the specified range and will generate a benchmark for each such argument.
+
+```c++
+BENCHMARK(BM_memcpy)->Range(8, 8<<10);
+```
+
+By default the arguments in the range are generated in multiples of eight and
+the command above selects [ 8, 64, 512, 4k, 8k ]. In the following code the
+range multiplier is changed to multiples of two.
+
+```c++
+BENCHMARK(BM_memcpy)->RangeMultiplier(2)->Range(8, 8<<10);
+```
+
+Now arguments generated are [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ].
+
+The preceding code shows a method of defining a sparse range. The following
+example shows a method of defining a dense range. It is then used to benchmark
+the performance of `std::vector` initialization for uniformly increasing sizes.
+
+```c++
+static void BM_DenseRange(benchmark::State& state) {
+ for(auto _ : state) {
+ std::vector<int> v(state.range(0), state.range(0));
+ benchmark::DoNotOptimize(v.data());
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_DenseRange)->DenseRange(0, 1024, 128);
+```
+
+Now arguments generated are [ 0, 128, 256, 384, 512, 640